diff --git a/.codacy.yml b/.codacy.yml deleted file mode 100644 index 55c2b7a4..00000000 --- a/.codacy.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -exclude_paths: - - test*.py - - examples/ - - doc/ - - paper/ \ No newline at end of file diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 968098c0..00000000 --- a/.coveragerc +++ /dev/null @@ -1,16 +0,0 @@ -[report] -omit = - doc/* - build/* - dist/* - examples/* - tests/* - **/test*.py - *__init__* - setup.py - -exclude_lines = - pragma: no cover - raise NotImplementedError - raise ImportError - def __len__ \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..655eda72 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,64 @@ +name: CICD + +on: + push: + pull_request: + types: [opened] + workflow_dispatch: + release: + types: [created] + +defaults: + run: + shell: bash + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: pip install poetry + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + architecture: 'x64' + cache: 'poetry' + - run: make install + - run: make precommit + + docs: + runs-on: ubuntu-latest + needs: [test] + steps: + - uses: actions/checkout@v4 + - run: pip install poetry + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + architecture: 'x64' + cache: 'poetry' + - run: make install + - run: make docs + - run: make pushdocs + if: | + github.event_name == 'release' || github.ref == 'refs/heads/master' + + build_and_publish: + runs-on: ubuntu-latest + needs: [test] + steps: + - uses: actions/checkout@v4 + - run: pip install poetry + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + architecture: 'x64' + cache: 'poetry' + - run: make install + - run: make build + - name: Publish + run: make publish + if: github.event_name == 'release' + env: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + diff --git a/.gitignore b/.gitignore index 33678b93..504b1c9d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,99 +1,63 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ +# Compiled source # +################### +*.com +*.class +*.dll +*.exe +*.o +*.so + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# PYTHON # +__pycache__ *.py[cod] -*$py.class +*.py~ -# PyCharm -.idea/* +# envs +.env +.venv -# C extensions -*.so +# To use VS code +.vscode -# Distribution / packaging -.Python -env/ +# Setup.py build build/ -develop-eggs/ +*egg-info/ dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# Sphinx doco -doc/out/** - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ +.pytest +.pytest_cache +.mypy_cache +id_rsa +*.ipynb +coverage* .coverage .coverage.* -.cache -nosetests.xml -coverage.xml -*,cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# IPython Notebook +.dmypy.json +dmypy.json +.idea .ipynb_checkpoints - -# pyenv +.ruff_cache +.cache +profile_default/ +ipython_config.py +__pypackages__/ .python-version +qodana.yaml -# celery beat schedule file -celerybeat-schedule - -# dotenv -.env - -# virtualenv -venv/ -ENV/ - -# Spyder project settings -.spyderproject +**/*.log +**/*tmp* +**/*secret* +**/*.pkl -# Rope project settings -.ropeproject -doc/examples/* -doc/modules/* -doc/out/ -.vscode \ No newline at end of file +generated/ +.mypy_cache \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..fe8aa0b1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,32 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-added-large-files + args: ["--maxkb=5000"] + - id: check-toml + - id: check-json + - id: check-symlinks + - id: debug-statements + - id: detect-private-key + - id: check-yaml + args: + - --unsafe + - id: trailing-whitespace + - repo: https://github.com/psf/black + rev: 23.9.1 + hooks: + - id: black + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.292 + hooks: + - id: ruff + args: ["--fix"] + - repo: local + hooks: + - id: tests + name: tests + language: system + types: [python] + pass_filenames: false + entry: poetry run pytest diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c760d38b..00000000 --- a/.travis.yml +++ /dev/null @@ -1,67 +0,0 @@ -language: python - -os: linux - -python: - - "3.8" - - "3.7" - -env: - global: - - GH_REF: github.com/samreay/ChainConsumer.git - -dist: xenial - -services: - - xvfb - -install: - - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh - - chmod +x miniconda.sh - - bash miniconda.sh -b -p $HOME/miniconda - - export PATH=$HOME/miniconda/bin:$PATH - - conda config --set always_yes yes --set changeps1 no --set anaconda_upload no - - conda update -q conda - - conda install python=$TRAVIS_PYTHON_VERSION pip setuptools - - sudo apt-get install texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended dvipng cm-super - - hash -r - - pip install wheel twine - - pip install . - - pip install -r requirements.txt - - ulimit -s unlimited - -script: - - py.test --cov=chainconsumer -vv - -after_success: - - codecov - - chmod u+x deploy.sh - - ./deploy.sh - - chmod +x conda/deploy_anaconda.sh - -deploy: - - provider: script - script: ./conda/deploy_anaconda.sh - skip_cleanup: true - on: - python: 3.8 - tags: true - condition: $TRAVIS_TAG =~ ^v[0-9,\.]*$ - - provider: pypi - user: "samreay" - password: - secure: "etRu952bkCozacIuC38kgsr5Ul05QG5lY1ESrC50El0fdQjimCeDqislB8buYyD+TeXfPY4jN10ApFfYzTJGfVFsM5NLE/T6+UBfCx3h07jN5ewA9jY5LDKaP5UJ+e33NrC+vkvsxoB5BJzwtbmrj6TDM9/s8RxsIZYFpZ+PoJxO/S7g2d6g1kCkbG2Q/2PpWa4VRacS29CAlBkeDY6z7ZCLEnpK0733ccQPxNfbdmAsXdHnk1cLWA5FNzbp5Vjti/tet6Wv8aVgw1RgSSN3HQ/5hl/uDgmGlloHvXDAK7xdtEo0R5y9mR4cfVJOxARweLBS84bPEysI+N7VoOAQkrBRxL85YHtuFBBiQdGMQazG8M/FhO0aeyMj1KJM1yaLj8lQJN+9qeIrtagV1LkAW9kzWvE4NOCPah4LGy2NvgJ0L7F8Kmgk2ReAZ3FfbfGyoISg/iqGBwDTjYH0GtXc3DYswpKylkpdpkcCh13usyVYxoHUJ+pT594vNJrNk6qQof6PVUiBldXkoWofoJGgASUh6r1gbcK/qlgD+sOCRyQ7zH5N8H/0ecnEC+io178r/5NioVfmsNBS4izJQPVzdUb3bB3jDjMwOU+yO/amD0V9Te9ctqDJL8cWmBwj0UsKunpZ2qbWfPUwtNS9/ehHH8RElJv8IKuj+j/md91KQy0=" - skip_cleanup: true - edge: - branch: v1.8.45 - on: - tags: true - condition: $TRAVIS_TAG =~ ^v[0-9,\.]*$ - python: 3.8 - - provider: releases - api_key: "${GITHUB_API_KEY2}" - skip_cleanup: true - on: - tags: true - condition: $TRAVIS_TAG =~ ^v[0-9,\.]*$ - python: 3.8 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..78a13c91 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,9 @@ +# Guide for contributing! + +1. Please read over the online documentation, or use GitHub discussions to chat if you're unsure about anything. +2. Fork the repo + clone it out +3. I highly recommend either a nix system or using the Windows Subsystem for Linux +4. Install a developer version by running (in the directory you just cloned into): `make install` + 1. This should install poetry, dependencies, and a pre-commit hook + 2. Verify it all looks good by running `make precommit` +5. Code! \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..d4a87c22 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +.PHONY: tests docs build +VERSION := $(shell git for-each-ref refs/tags --format='%(refname:short)' | grep -E "^v?[0-9]+\..*" | tail -n1) + +install: + pip install -U pip poetry -q + poetry install --with=dev,test --all-extras + poetry run pre-commit install + poetry run pre-commit autoupdate + +precommit: + poetry run pre-commit run --all-files + +test: + poetry run pytest + +serve: + rm -rf docs/generated/gallery; + poetry run mkdocs serve --clean + +docs: + poetry run poetry version $(VERSION) && poetry run mkdocs build + +pushdocs: + poetry run poetry version $(VERSION) && poetry run mkdocs gh-deploy --force + +build: + rm -rf dist; poetry version $(VERSION) && poetry publish --build --dry-run + +publish: + rm -rf dist; poetry config pypi-token.pypi $PYPI_TOKEN && poetry version $(VERSION) && poetry publish --build -y + +tests: test + +all: precommit tests \ No newline at end of file diff --git a/README.md b/README.md index 8a4a2de1..51532909 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,19 @@ and perform some model selection! ### Installation Install via `pip`: - + pip install chainconsumer ### Python Versions -Due to dependencies dropping support for Python 2.7, from 0.31.0 and onwards, only Python 3 will be supported. +Time has ticked on, and now only python 3.10 will be supported. This is because type hints are amazing. + +### Developing -Previous versions will still be installable for Python 2.7 environments, however I'd strong recommend upgrading. +1. Clone repo +2. Run `make install` +3. Ensure that you set your python interpreter to the `.venv/bin/python` +4. Code away. ### Contributors @@ -53,7 +58,7 @@ which have helped improve ChainConsumer: ### Common Issues -Users on some Linux platforms have reported issues rendering plots using ChainConsumer. +Users on some Linux platforms have reported issues rendering plots using ChainConsumer. The common error states that `dvipng: not found`, and as per [StackOverflow](http://stackoverflow.com/a/32915992/3339667) post, it can be solved by explicitly install the `matplotlib` dependency `dvipng` via `sudo apt-get install dvipng`. diff --git a/chainconsumer/__init__.py b/chainconsumer/__init__.py deleted file mode 100644 index 7faf22de..00000000 --- a/chainconsumer/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from .chainconsumer import ChainConsumer - -__all__ = ["ChainConsumer"] diff --git a/chainconsumer/analysis.py b/chainconsumer/analysis.py deleted file mode 100644 index d1f4d91b..00000000 --- a/chainconsumer/analysis.py +++ /dev/null @@ -1,550 +0,0 @@ -# -*- coding: utf-8 -*- -import logging -import numpy as np -from scipy.integrate import simps -from scipy.interpolate import interp1d -from scipy.ndimage.filters import gaussian_filter - -from .helpers import get_smoothed_bins, get_grid_bins, get_latex_table_frame -from .kde import MegKDE - - -class Analysis(object): - - summaries = ["max", "mean", "cumulative", "max_symmetric", "max_shortest", "max_central"] - - def __init__(self, parent): - self.parent = parent - self._logger = logging.getLogger("chainconsumer") - - self._summaries = { - "max": self.get_parameter_summary_max, - "mean": self.get_parameter_summary_mean, - "cumulative": self.get_parameter_summary_cumulative, - "max_symmetric": self.get_paramater_summary_max_symmetric, - "max_shortest": self.get_parameter_summary_max_shortest, - "max_central": self.get_parameter_summary_max_central, - } - - def get_latex_table( - self, parameters=None, transpose=False, caption=None, label="tab:model_params", hlines=True, blank_fill="--", filename=None - ): # pragma: no cover - """ Generates a LaTeX table from parameter summaries. - - Parameters - ---------- - parameters : list[str], int optional - A list of what parameters to include in the table. By default, includes all parameters - transpose : bool, optional - Defaults to False, which gives each column as a parameter, each chain (framework) - as a row. You can swap it so that you have a parameter each row and a framework - each column by setting this to True - caption : str, optional - If you want to generate a caption for the table through Python, use this. - Defaults to an empty string - label : str, optional - If you want to generate a label for the table through Python, use this. - Defaults to an empty string - hlines : bool, optional - Inserts ``\\hline`` before and after the header, and at the end of table. - blank_fill : str, optional - If a framework does not have a particular parameter, will fill that cell of - the table with this string. - filename : str, optional - The file to save the output string to - - Returns - ------- - str - the LaTeX table. - """ - if parameters is None: - parameters = self.parent._all_parameters - elif isinstance(parameters, int): - parameters = self.parent._all_parameters[:parameters] - for p in parameters: - assert isinstance(p, str), "Generating a LaTeX table requires all parameters have labels" - num_parameters = len(parameters) - chains = self.parent.get_mcmc_chains() - num_chains = len(chains) - fit_values = self.get_summary(squeeze=False, chains=chains) - if label is None: - label = "" - if caption is None: - caption = "" - - end_text = " \\\\ \n" - if transpose: - column_text = "c" * (num_chains + 1) - else: - column_text = "c" * (num_parameters + 1) - - center_text = "" - hline_text = "\\hline\n" - if hlines: - center_text += hline_text + "\t\t" - if transpose: - center_text += " & ".join(["Parameter"] + [c.name for c in chains]) + end_text - if hlines: - center_text += "\t\t" + hline_text - for p in parameters: - arr = ["\t\t" + p] - for chain_res in fit_values: - if p in chain_res: - arr.append(self.get_parameter_text(*chain_res[p], wrap=True)) - else: - arr.append(blank_fill) - center_text += " & ".join(arr) + end_text - else: - center_text += " & ".join(["Model"] + parameters) + end_text - if hlines: - center_text += "\t\t" + hline_text - for name, chain_res in zip([c.name for c in chains], fit_values): - arr = ["\t\t" + name] - for p in parameters: - if p in chain_res: - arr.append(self.get_parameter_text(*chain_res[p], wrap=True)) - else: - arr.append(blank_fill) - center_text += " & ".join(arr) + end_text - if hlines: - center_text += "\t\t" + hline_text - final_text = get_latex_table_frame(caption, label) % (column_text, center_text) - - if filename is not None: - with open(filename, "w") as f: - f.write(final_text) - - return final_text - - def get_summary(self, squeeze=True, parameters=None, chains=None): - """ Gets a summary of the marginalised parameter distributions. - - Parameters - ---------- - squeeze : bool, optional - Squeeze the summaries. If you only have one chain, squeeze will not return - a length one list, just the single summary. If this is false, you will - get a length one list. - parameters : list[str], optional - A list of parameters which to generate summaries for. - chains : list[int|str], optional - A list of the chains to get a summary of. - - Returns - ------- - list of dictionaries - One entry per chain, parameter bounds stored in dictionary with parameter as key - """ - results = [] - if chains is None: - chains = self.parent.get_mcmc_chains() - else: - if isinstance(chains, (int, str)): - chains = [chains] - if isinstance(chains[0], (int, str)): - chains = [self.parent.chains[i] for c in chains for i in self.parent._get_chain(c)] - - for chain in chains: - res = {} - params_to_find = parameters if parameters is not None else chain.parameters - for p in params_to_find: - if p not in chain.parameters: - continue - summary = self.get_parameter_summary(chain, p) - res[p] = summary - results.append(res) - if squeeze and len(results) == 1: - return results[0] - return results - - def get_max_posteriors(self, parameters=None, squeeze=True, chains=None): - """ Gets the maximum posterior point in parameter space from the passed parameters. - - Requires the chains to have set `posterior` values. - - Parameters - ---------- - parameters : str|list[str] - The parameters to find - squeeze : bool, optional - Squeeze the summaries. If you only have one chain, squeeze will not return - a length one list, just the single summary. If this is false, you will - get a length one list. - chains : list[int|str], optional - A list of the chains to get a summary of. - - Returns - ------- - list of two-tuples - One entry per chain, two-tuple represents the max-likelihood coordinate - """ - - results = [] - if chains is None: - chains = self.parent.chains - else: - if isinstance(chains, (int, str)): - chains = [chains] - chains = [self.parent.chains[i] for c in chains for i in self.parent._get_chain(c)] - - if isinstance(parameters, str): - parameters = [parameters] - - for chain in chains: - if chain.posterior_max_index is None: - results.append(None) - continue - res = {} - params_to_find = parameters if parameters is not None else chain.parameters - for p in params_to_find: - if p in chain.parameters: - res[p] = chain.posterior_max_params[p] - results.append(res) - - if squeeze and len(results) == 1: - return results[0] - return results - - def get_parameter_summary(self, chain, parameter): - # Ensure config has been called so we get the statistics set in config - if not self.parent._configured: - self.parent.configure() - callback = self._summaries[chain.config["statistics"]] - return chain.get_summary(parameter, callback) - - def get_correlations(self, chain=0, parameters=None): - """ - Takes a chain and returns the correlation between chain parameters. - - Parameters - ---------- - chain : int|str, optional - The chain index or name. Defaults to first chain. - parameters : list[str], optional - The list of parameters to compute correlations. Defaults to all parameters - for the given chain. - - Returns - ------- - tuple - The first index giving a list of parameter names, the second index being the - 2D correlation matrix. - """ - parameters, cov = self.get_covariance(chain=chain, parameters=parameters) - diag = np.sqrt(np.diag(cov)) - divisor = diag[None, :] * diag[:, None] - correlations = cov / divisor - return parameters, correlations - - def get_covariance(self, chain=0, parameters=None): - """ - Takes a chain and returns the covariance between chain parameters. - - Parameters - ---------- - chain : int|str, optional - The chain index or name. Defaults to first chain. - parameters : list[str], optional - The list of parameters to compute correlations. Defaults to all parameters - for the given chain. - - Returns - ------- - tuple - The first index giving a list of parameter names, the second index being the - 2D covariance matrix. - """ - index = self.parent._get_chain(chain) - assert len(index) == 1, "Please specify only one chain, have %d chains" % len(index) - chain = self.parent.chains[index[0]] - if parameters is None: - parameters = chain.parameters - - data = chain.get_data(parameters) - cov = np.atleast_2d(np.cov(data, aweights=chain.weights, rowvar=False)) - - return parameters, cov - - def get_correlation_table(self, chain=0, parameters=None, caption="Parameter Correlations", label="tab:parameter_correlations"): - """ - Gets a LaTeX table of parameter correlations. - - Parameters - ---------- - chain : int|str, optional - The chain index or name. Defaults to first chain. - parameters : list[str], optional - The list of parameters to compute correlations. Defaults to all parameters - for the given chain. - caption : str, optional - The LaTeX table caption. - label : str, optional - The LaTeX table label. - - Returns - ------- - str - The LaTeX table ready to go! - """ - parameters, cor = self.get_correlations(chain=chain, parameters=parameters) - return self._get_2d_latex_table(parameters, cor, caption, label) - - def get_covariance_table(self, chain=0, parameters=None, caption="Parameter Covariance", label="tab:parameter_covariance"): - """ - Gets a LaTeX table of parameter covariance. - - Parameters - ---------- - chain : int|str, optional - The chain index or name. Defaults to first chain. - parameters : list[str], optional - The list of parameters to compute correlations. Defaults to all parameters - for the given chain. - caption : str, optional - The LaTeX table caption. - label : str, optional - The LaTeX table label. - - Returns - ------- - str - The LaTeX table ready to go! - """ - parameters, cov = self.get_covariance(chain=chain, parameters=parameters) - return self._get_2d_latex_table(parameters, cov, caption, label) - - def _get_smoothed_histogram(self, chain, parameter, pad=False): - data = chain.get_data(parameter) - smooth = chain.config["smooth"] - if chain.grid: - bins = get_grid_bins(data) - else: - bins = chain.config["bins"] - bins, smooth = get_smoothed_bins(smooth, bins, data, chain.weights, pad=pad) - - hist, edges = np.histogram(data, bins=bins, density=True, weights=chain.weights) - if chain.power is not None: - hist = hist ** chain.power - edge_centers = 0.5 * (edges[1:] + edges[:-1]) - xs = np.linspace(edge_centers[0], edge_centers[-1], 10000) - - if smooth: - hist = gaussian_filter(hist, smooth, mode=self.parent._gauss_mode) - kde = chain.config["kde"] - if kde: - kde_xs = np.linspace(edge_centers[0], edge_centers[-1], max(200, int(bins.max()))) - ys = MegKDE(data, chain.weights, factor=kde).evaluate(kde_xs) - area = simps(ys, x=kde_xs) - ys = ys / area - ys = interp1d(kde_xs, ys, kind="linear")(xs) - else: - ys = interp1d(edge_centers, hist, kind="linear")(xs) - cs = ys.cumsum() - cs /= cs.max() - return xs, ys, cs - - def _get_2d_latex_table(self, parameters, matrix, caption, label): - latex_table = get_latex_table_frame(caption=caption, label=label) - column_def = "c|%s" % ("c" * len(parameters)) - hline_text = " \\hline\n" - - table = "" - table += " & ".join([""] + parameters) + "\\\\ \n" - table += hline_text - max_len = max([len(s) for s in parameters]) - format_string = " %%%ds" % max_len - for p, row in zip(parameters, matrix): - table += format_string % p - for r in row: - table += " & %5.2f" % r - table += " \\\\ \n" - table += hline_text - return latex_table % (column_def, table) - - def get_parameter_text(self, lower, maximum, upper, wrap=False): - """ Generates LaTeX appropriate text from marginalised parameter bounds. - - Parameters - ---------- - lower : float - The lower bound on the parameter - maximum : float - The value of the parameter with maximum probability - upper : float - The upper bound on the parameter - wrap : bool - Wrap output text in dollar signs for LaTeX - - Returns - ------- - str - The formatted text given the parameter bounds - """ - if lower is None or upper is None: - return "" - upper_error = upper - maximum - lower_error = maximum - lower - if upper_error != 0 and lower_error != 0: - resolution = min(np.floor(np.log10(np.abs(upper_error))), np.floor(np.log10(np.abs(lower_error)))) - elif upper_error == 0 and lower_error != 0: - resolution = np.floor(np.log10(np.abs(lower_error))) - elif upper_error != 0 and lower_error == 0: - resolution = np.floor(np.log10(np.abs(upper_error))) - else: - resolution = np.floor(np.log10(np.abs(maximum))) - factor = 0 - fmt = "%0.1f" - r = 1 - if np.abs(resolution) > 2: - factor = -resolution - if resolution == 2: - fmt = "%0.0f" - factor = -1 - r = 0 - if resolution == 1: - fmt = "%0.0f" - if resolution == -1: - fmt = "%0.2f" - r = 2 - elif resolution == -2: - fmt = "%0.3f" - r = 3 - upper_error *= 10 ** factor - lower_error *= 10 ** factor - maximum *= 10 ** factor - upper_error = round(upper_error, r) - lower_error = round(lower_error, r) - maximum = round(maximum, r) - if maximum == -0.0: - maximum = 0.0 - if resolution == 2: - upper_error *= 10 ** -factor - lower_error *= 10 ** -factor - maximum *= 10 ** -factor - factor = 0 - fmt = "%0.0f" - upper_error_text = fmt % upper_error - lower_error_text = fmt % lower_error - if upper_error_text == lower_error_text: - text = r"%s\pm %s" % (fmt, "%s") % (maximum, lower_error_text) - else: - text = r"%s^{+%s}_{-%s}" % (fmt, "%s", "%s") % (maximum, upper_error_text, lower_error_text) - if factor != 0: - text = r"\left( %s \right) \times 10^{%d}" % (text, -factor) - if wrap: - text = "$%s$" % text - return text - - def get_parameter_summary_mean(self, chain, parameter): - desired_area = chain.config["summary_area"] - xs, _, cs = self._get_smoothed_histogram(chain, parameter) - vals = [0.5 - desired_area / 2, 0.5, 0.5 + desired_area / 2] - bounds = interp1d(cs, xs)(vals) - bounds[1] = 0.5 * (bounds[0] + bounds[2]) - return bounds - - def get_parameter_summary_cumulative(self, chain, parameter): - xs, _, cs = self._get_smoothed_histogram(chain, parameter) - desired_area = chain.config["summary_area"] - vals = [0.5 - desired_area / 2, 0.5, 0.5 + desired_area / 2] - bounds = interp1d(cs, xs)(vals) - return bounds - - def get_parameter_summary_max(self, chain, parameter): - xs, ys, cs = self._get_smoothed_histogram(chain, parameter) - desired_area = chain.config["summary_area"] - n_pad = 1000 - x_start = xs[0] * np.ones(n_pad) - x_end = xs[-1] * np.ones(n_pad) - y_start = np.linspace(0, ys[0], n_pad) - y_end = np.linspace(ys[-1], 0, n_pad) - xs = np.concatenate((x_start, xs, x_end)) - ys = np.concatenate((y_start, ys, y_end)) - cs = ys.cumsum() - cs = cs / cs.max() - startIndex = ys.argmax() - maxVal = ys[startIndex] - minVal = 0 - threshold = 0.003 - x1 = None - x2 = None - count = 0 - while x1 is None: - mid = (maxVal + minVal) / 2.0 - count += 1 - try: - if count > 50: - raise ValueError("Failed to converge") - i1 = startIndex - np.where(ys[:startIndex][::-1] < mid)[0][0] - i2 = startIndex + np.where(ys[startIndex:] < mid)[0][0] - area = cs[i2] - cs[i1] - deviation = np.abs(area - desired_area) - if deviation < threshold: - x1 = xs[i1] - x2 = xs[i2] - elif area < desired_area: - maxVal = mid - elif area > desired_area: - minVal = mid - except ValueError: - self._logger.warning("Parameter %s in chain %s is not constrained" % (parameter, chain.name)) - return [None, xs[startIndex], None] - - return [x1, xs[startIndex], x2] - - def get_paramater_summary_max_symmetric(self, chain, parameter): - xs, ys, cs = self._get_smoothed_histogram(chain, parameter) - desired_area = chain.config["summary_area"] - - x_to_c = interp1d(xs, cs, bounds_error=False, fill_value=(0, 1)) - - # Get max likelihood x - max_index = ys.argmax() - x = xs[max_index] - - # Estimate width - h = 0.5 * (xs[-1] - xs[0]) - prev_h = 0 - - # Hone in on right answer - while True: - current_area = x_to_c(x + h) - x_to_c(x - h) - if np.abs(current_area - desired_area) < 0.0001: - return [x - h, x, x + h] - temp = h - h += 0.5 * np.abs(prev_h - h) * (1 if current_area < desired_area else -1) - prev_h = temp - - def get_parameter_summary_max_shortest(self, chain, parameter): - xs, ys, cs = self._get_smoothed_histogram(chain, parameter) - desired_area = chain.config["summary_area"] - - c_to_x = interp1d(cs, xs, bounds_error=False, fill_value=(-np.inf, np.inf)) - - # Get max likelihood x - max_index = ys.argmax() - x = xs[max_index] - - # Pair each lower bound with an upper to get the right area - x2 = c_to_x(cs + desired_area) - dists = x2 - xs - mask = (xs > x) | (x2 < x) # Ensure max point is inside the area - dists[mask] = np.inf - ind = dists.argmin() - return [xs[ind], x, x2[ind]] - - def get_parameter_summary_max_central(self, chain, parameter): - xs, ys, cs = self._get_smoothed_histogram(chain, parameter) - desired_area = chain.config["summary_area"] - - c_to_x = interp1d(cs, xs) - - # Get max likelihood x - max_index = ys.argmax() - x = xs[max_index] - - vals = [0.5 - 0.5 * desired_area, 0.5 + 0.5 * desired_area] - xvals = c_to_x(vals) - - return [xvals[0], x, xvals[1]] diff --git a/chainconsumer/chain.py b/chainconsumer/chain.py deleted file mode 100644 index 64744f69..00000000 --- a/chainconsumer/chain.py +++ /dev/null @@ -1,288 +0,0 @@ -# -*- coding: utf-8 -*- -import logging -import numpy as np - -from .colors import Colors -from .analysis import Analysis - - -class Chain(object): - - colors = Colors() # Static colors object to do color mapping - - def __init__( - self, - chain, - parameters, - name, - weights=None, - posterior=None, - walkers=None, - grid=False, - num_free_params=None, - num_eff_data_points=None, - power=None, - statistics="max", - color=None, - linestyle=None, - linewidth=None, - cloud=None, - shade=None, - shade_alpha=None, - shade_gradient=None, - bar_shade=None, - bins=None, - kde=None, - smooth=None, - color_params=None, - plot_color_params=None, - cmap=None, - num_cloud=None, - plot_contour=True, - plot_point=False, - show_as_1d_prior=False, - marker_style=None, - marker_size=None, - marker_alpha=None, - zorder=None, - shift_params=None, - ): - self.chain = chain - self.parameters = parameters - self.name = name - self.mcmc_chain = True - - self.posterior_max_index = None - self.posterior_max_params = {} - - if weights is None: - weights = np.ones(chain.shape[0]) - weights = weights.squeeze() - - if posterior is not None: - posterior = posterior.squeeze() - self.posterior_max_index = np.argmax(posterior) - for i, p in enumerate(parameters): - self.posterior_max_params[p] = chain[self.posterior_max_index, i] - - self.shift_params = shift_params - if shift_params is not None: - for key in shift_params.keys(): - try: - index = self.parameters.index(key) - avg = np.average(chain[:, index], weights=weights) - chain[:, index] += shift_params[key] - avg - except ValueError: - continue - self.weights = weights - self.posterior = posterior - self.walkers = walkers - self.grid = grid - self.num_free_params = num_free_params - self.num_eff_data_points = num_eff_data_points - self.power = power - - self._logger = logging.getLevelName(self.__class__.__name__) - - # Storing config overrides - self.color = color - self.linewidth = linewidth - self.linestyle = linestyle - self.kde = kde - self.shade_alpha = shade_alpha - - self.summaries = {} - self.config = {} - - self.configure( - statistics=statistics, - color=color, - linestyle=linestyle, - linewidth=linewidth, - cloud=cloud, - shade=shade, - shade_alpha=shade_alpha, - shade_gradient=shade_gradient, - bar_shade=bar_shade, - bins=bins, - kde=kde, - smooth=smooth, - color_params=color_params, - plot_color_params=plot_color_params, - cmap=cmap, - num_cloud=num_cloud, - plot_contour=plot_contour, - plot_point=plot_point, - show_as_1d_prior=show_as_1d_prior, - marker_style=marker_style, - marker_size=marker_size, - marker_alpha=marker_alpha, - zorder=zorder, - ) - self.validate_chain() - self.validated_params = set() - - def configure( - self, - statistics=None, - color=None, - linestyle=None, - linewidth=None, - cloud=None, - shade=None, - shade_alpha=None, - shade_gradient=None, - bar_shade=None, - bins=None, - kde=None, - smooth=None, - color_params=None, - plot_color_params=None, - cmap=None, - num_cloud=None, - marker_style=None, - marker_size=None, - marker_alpha=None, - plot_contour=True, - plot_point=False, - show_as_1d_prior=False, - zorder=None, - ): - - if statistics is not None: - assert isinstance(statistics, str), "statistics should be a string" - assert statistics in list(Analysis.summaries), "statistics %s not recognised. Should be in %s" % (statistics, Analysis.summaries) - self.config["statistics"] = statistics - - if color is not None: - color = self.colors.format(color) - self.config["color"] = color - - # See I wish I didnt have to do this, but I get too many issues raised when people - # pass in the weirdest stuff and expect it to work. - self._validate_config("linestyle", linestyle, str) - self._validate_config("linewidth", linewidth, int, float) - self._validate_config("cloud", cloud, bool) - self._validate_config("shade", shade, bool) - self._validate_config("shade_alpha", shade_alpha, int, float) - self._validate_config("shade_gradient", shade_gradient, int, float) - self._validate_config("bar_shade", bar_shade, bool) - self._validate_config("bins", bins, int, float) - self._validate_config("kde", kde, int, float, bool) - self._validate_config("smooth", smooth, int, float, bool) - self._validate_config("color_params", color_params, str) - self._validate_config("plot_color_params", plot_color_params, bool) - self._validate_config("cmap", cmap, str) - self._validate_config("num_cloud", num_cloud, int, float) - self._validate_config("marker_style", marker_style, str) - self._validate_config("marker_size", marker_size, int, float) - self._validate_config("marker_alpha", marker_alpha, int, float) - self._validate_config("plot_contour", plot_contour, bool) - self._validate_config("plot_point", plot_point, bool) - self._validate_config("show_as_1d_prior", show_as_1d_prior, bool) - self._validate_config("zorder", zorder, int) - - def update_unset_config(self, name, value, override=None): - if (override is not None and name in override) or self.config.get(name) is None: - self.config[name] = value - - def _validate_config(self, name, value, *types): - if value is not None: - assert isinstance(value, tuple(types)), "%s, which is %s, should be type of: %s" % (name, value, " or ".join([t.__name__ for t in types])) - self.config[name] = value - - def validate_chain(self): - # So many people request help when the pass in junk data without realising it. - # Let's try and flag this as quickly as we can. - # Defensive coding; engage! - - assert isinstance(self.name, str), "Chain name needs to be a string. It is %s" % type(self.name) - assert np.all(np.isfinite(self.weights)), "Chain %s has weights which are NaN or inf!" % self.name - assert len(self.weights.shape) == 1, "Weights should be a 1D array, have instead %s" % str(self.weights.shape) - assert self.weights.size == self.chain.shape[0], "Chain %s has %d steps but %d weights" % (self.name, self.weights.size, self.chain.shape[0]) - assert self.chain.shape[0] > 0, "Chain has shape %s, which means it has 0 steps!" % str(self.chain.shape) - assert np.sum(self.weights) > 0, "Chain weights sum to zero, this is not good" - if self.walkers is not None: - assert int(self.walkers) == self.walkers, "Walkers should be an integer!" - assert self.chain.shape[0] % self.walkers == 0, "Chain %s has %d walkers and %d steps... which aren't divisible. They need to be!" % ( - self.name, - self.walkers, - self.chain.shape[0], - ) - assert isinstance(self.grid, bool), "Chain %s has %s for grid, should be a bool" % (self.name, type(self.grid)) - assert self.parameters is not None, "Chain %s has parameter list of None. Please give names" % self.name - assert len(self.parameters) == self.chain.shape[1], "Chain %s has %d parameters but data has %d columns" % ( - self.name, - len(self.parameters), - self.chain.shape[1], - ) - for i, p in enumerate(self.parameters): - assert isinstance(p, str), "Param index %d, which is %s, needs to be a string!" % (i, p) - if self.posterior is not None: - assert len(self.posterior.shape) == 1, "posterior should be a 1D array, have instead %s" % str(self.posterior.shape) - assert self.posterior.size == self.chain.shape[0], "Chain %s has %d steps but %d log-posterior values" % ( - self.name, - self.chain.shape[0], - self.posterior.size, - ) - assert np.all(np.isfinite(self.posterior)), "Chain %s has NaN or inf in the log-posterior" % self.name - if self.num_free_params is not None: - assert isinstance(self.num_free_params, (int, float)), "Chain %s has num_free_params which is not an integer, its %s" % ( - self.name, - type(self.num_free_params), - ) - assert np.isfinite(self.num_free_params), "num_free_params is either infinite or NaN" - assert self.num_free_params > 0, "num_free_params must be positive" - if self.num_eff_data_points is not None: - assert isinstance(self.num_eff_data_points, (int, float)), "Chain %s has num_eff_data_points which is not an a number, its %s" % ( - self.name, - type(self.num_eff_data_points), - ) - assert np.isfinite(self.num_eff_data_points), "num_eff_data_points is either infinite or NaN" - assert self.num_eff_data_points > 0, "num_eff_data_points must be positive" - - # def reset_config(self): - # self.config = {} - # self.summaries = {} - # self.validated_params = set() - - def get_summary(self, param, callback): - stat = "%s %s" % (self.config["statistics"], self.config["summary_area"]) - if stat in self.summaries.keys() and param in self.summaries[stat]: - return self.summaries[stat][param] - result = callback(self, param) - if stat not in self.summaries.keys(): - self.summaries[stat] = {} - self.summaries[stat][param] = result - return result - - def get_color_data(self): - color_param = self.config.get("color_params") - color_data = None - if color_param in self.parameters: - color_data = self.get_data(color_param) - elif color_param == "weights": - color_data = self.weights - elif color_param == "log_weights": - color_data = np.log(self.weights) - elif color_param == "posterior": - color_data = self.posterior - return color_data - - def get_data(self, params): - if not isinstance(params, list): - params = [params] - - params = [self.parameters[param] if isinstance(param, int) else param for param in params] - for p in params: - self.validate_parameter(p) - indexes = [self.parameters.index(param) for param in params] - return np.squeeze(self.chain[:, indexes]) - - def validate_parameter(self, param): - if param not in self.validated_params: - index = self.parameters.index(param) - data = self.chain[:, index] - msg = "Data for chain %s, parameter %s is being used, but has either NaNs or infs in it!" - assert np.all(np.isfinite(data)), msg % (self.name, param) - self.validated_params.add(param) diff --git a/chainconsumer/chainconsumer.py b/chainconsumer/chainconsumer.py deleted file mode 100644 index 9f287300..00000000 --- a/chainconsumer/chainconsumer.py +++ /dev/null @@ -1,1119 +0,0 @@ -# -*- coding: utf-8 -*- -import numpy as np -import pandas as pd -import logging - -from .comparisons import Comparison -from .diagnostic import Diagnostic -from .plotter import Plotter -from .helpers import get_bins -from .analysis import Analysis -from .colors import Colors -from .chain import Chain - -__all__ = ["ChainConsumer"] - - -class ChainConsumer(object): - """ A class for consuming chains produced by an MCMC walk. Or grid searches. To make plots, - figures, tables, diagnostics, you name it. - - """ - - __version__ = "0.34.0" - - def __init__(self): - logging.basicConfig(level=logging.INFO) - self._logger = logging.getLogger("chainconsumer") - self.color_finder = Colors() - self._all_colours = self.color_finder.get_default() - self._cmaps = ["viridis", "inferno", "hot", "Blues", "Greens", "Greys"] - self._linestyles = ["-", "--", ":"] - self.chains = [] - self._all_parameters = [] - self._default_parameters = None - self._init_params() - self._gauss_mode = "reflect" - self._configured = False - self._num_configure_calls = 0 - - self.plotter = Plotter(self) - self.diagnostic = Diagnostic(self) - self.comparison = Comparison(self) - self.analysis = Analysis(self) - - def _init_params(self): - self.config = {} - self.config_truth = {} - self._configured = False - self._configured_truth = False - - def get_mcmc_chains(self): - return [c for c in self.chains if c.mcmc_chain] - - def add_chain( - self, - chain, - parameters=None, - name=None, - weights=None, - posterior=None, - walkers=None, - grid=False, - num_eff_data_points=None, - num_free_params=None, - color=None, - linewidth=None, - linestyle=None, - kde=None, - shade=None, - shade_alpha=None, - power=None, - marker_style=None, - marker_size=None, - marker_alpha=None, - plot_contour=None, - plot_point=None, - show_as_1d_prior=None, - statistics=None, - cloud=None, - shade_gradient=None, - bar_shade=None, - bins=None, - smooth=None, - color_params=None, - plot_color_params=None, - cmap=None, - num_cloud=None, - zorder=None, - shift_params=None, - ): - r""" Add a chain to the consumer. - - Parameters - ---------- - chain : str|ndarray|dict|pandas.DataFrame - The chain to load. Normally a ``numpy.ndarray``. If a string is found, it - interprets the string as a filename and attempts to load it in using pandas.read_csv. If a ``dict`` - is passed in, it assumes the dict has keys of parameter names and values of - an array of samples. Notice that using a dictionary puts the order of - parameters in the output under the control of the python ``dict.keys()`` function. - If you passed ``grid`` is set, you can pass in the parameter ranges in list form. If you pass - a DataFrame, I will look for a "weight" and "posterior" column by default. If they are - called something different, extract them and pass them directly into weights and posterior. - parameters : list[str], optional - A list of parameter names, one for each column (dimension) in the chain. This parameter - should remain ``None`` if a dictionary is given as ``chain``, as the parameter names - are taken from the dictionary keys. - name : str, optional - The name of the chain. Used when plotting multiple chains at once. - weights : ndarray, optional - If given, uses this array to weight the samples in chain - posterior : ndarray, optional - If given, records the log posterior for each sample in the chain - walkers : int, optional - How many walkers went into creating the chain. Each walker should - contribute the same number of steps, and should appear in contiguous - blocks in the final chain. - grid : boolean, optional - Whether the input is a flattened chain from a grid search instead of a Monte-Carlo - chains. Note that when this is set, `walkers` should not be set, and `weights` should - be set to the posterior evaluation for the grid point. **Be careful** when using - a coarse grid of setting a high smoothing value, as this may oversmooth the posterior - surface and give unreasonably large parameter bounds. - num_eff_data_points : int|float, optional - The number of effective (independent) data points used in the model fitting. Not required - for plotting, but required if loading in multiple chains to perform model comparison. - num_free_params : int, optional - The number of degrees of freedom in your model. Not required for plotting, but required if - loading in multiple chains to perform model comparison. - color : str(hex), optional - Provide a colour for the chain. Can be used instead of calling `configure` for convenience. - linewidth : float, optional - Provide a line width to plot the contours. Can be used instead of calling `configure` for convenience. - linestyle : str, optional - Provide a line style to plot the contour. Can be used instead of calling `configure` for convenience. - kde : bool|float, optional - Set the `kde` value for this specific chain. Can be used instead of calling `configure` for convenience. - shade : booloptional - If set, overrides the default behaviour and plots filled contours or not. If a list of - bools is passed, you can turn shading on or off for specific chains. - shade_alpha : float, optional - Filled contour alpha value. Can be used instead of calling `configure` for convenience. - power : float, optional - The power to raise the posterior surface to. Useful for inflating or deflating uncertainty for debugging. - marker_style : str|, optional - The marker style to use when plotting points. Defaults to `'.'` - marker_size : numeric|, optional - Size of markers, if plotted. Defaults to `20`. - marker_alpha : numeric, optional - The alpha values when plotting markers. - plot_contour : bool, optional - Whether to plot the whole contour (as opposed to a point). Defaults to true for less than - 25 concurrent chains. - plot_point : bool, optional - Whether to plot a maximum likelihood point. Defaults to true for more then 24 chains. - show_as_1d_prior : bool, optional - Showing as a 1D prior will show the 1D histograms, but won't plot the 2D contours. - statistics : string, optional - Which sort of statistics to use. Defaults to `"max"` for maximum likelihood - statistics. Other available options are `"mean"`, `"cumulative"`, `"max_symmetric"`, - `"max_closest"` and `"max_central"`. In the - very, very rare case you want to enable different statistics for different - chains, you can pass in a list of strings. - cloud : bool, optional - If set, overrides the default behaviour and plots the cloud or not shade_gradient : - bar_shade : bool, optional - If set to true, shades in confidence regions in under histogram. By default - this happens if you less than 3 chains, but is disabled if you are comparing - more chains. You can pass a list if you wish to shade some chains but not others. - bins : int|float, optional - The number of bins to use. By default uses :math:`\frac{\sqrt{n}}{10}`, where - :math:`n` are the number of data points. Giving an integer will set the number - of bins to the given value. Giving a float will scale the number of bins, such - that giving ``bins=1.5`` will result in using :math:`\frac{1.5\sqrt{n}}{10}` bins. - Note this parameter is most useful if `kde=False` is also passed, so you - can actually see the bins and not a KDE. smooth : - color_params : str, optional - The name of the parameter to use for the colour scatter. Defaults to none, for no colour. If set - to 'weights', 'log_weights', or 'posterior' (without the quotes), and that is not a parameter in the chain, - it will respectively use the weights, log weights, or posterior, to colour the points. - plot_color_params : bool, optional - Whether or not the colour parameter should also be plotted as a posterior surface. - cmaps : str, optional - The matplotlib colourmap to use in the `colour_param`. If you have multiple `color_param`s, you can - specific a different cmap for each variable. By default ChainConsumer will cycle between several - cmaps. - num_cloud : int, optional - The number of scatter points to show when enabling `cloud` or setting one of the parameters - to colour scatter. Defaults to 15k per chain. - zorder : int, optional - The zorder to pass to `matplotlib` when plotting to determine visual order in the plot. - shift_params : dict|list, optional - Shifts the parameters specify to the numeric values. Useful to shift contours to the same location to perform blinded - uncertainty comparisons. - Returns - ------- - ChainConsumer - Itself, to allow chaining calls. - """ - is_dict = False - assert chain is not None, "You cannot have a chain of None" - if isinstance(chain, str): - if chain.lower().endswith(".npy"): - chain = np.load(chain) - else: - chain = pd.read_csv(chain) - elif isinstance(chain, dict): - assert parameters is None, "You cannot pass a dictionary and specify parameter names" - is_dict = True - parameters = list(chain.keys()) - chain = np.array([chain[p] for p in parameters]).T - elif isinstance(chain, list): - chain = np.array(chain).T - - if isinstance(chain, pd.DataFrame): - assert parameters is None, "You cannot pass a DataFrame and use parameter names, we're using the columns names" - parameters = list(chain.columns) - if "weight" in parameters: - weights = chain["weight"] - if "posterior" in parameters: - posterior = chain["posterior"] - parameters = [p for p in parameters if p not in ["weight", "posterior"]] - chain = chain[parameters].to_numpy() - - if grid: - assert walkers is None, "If grid is set, walkers should not be" - assert weights is not None, "If grid is set, you need to supply weights" - if len(weights.shape) > 1: - assert not is_dict, ( - "We cannot construct a meshgrid from a dictionary, as the parameters" "are no longer ordered. Please pass in a flattened array instead." - ) - self._logger.info("Constructing meshgrid for grid results") - meshes = np.meshgrid(*[u for u in chain.T], indexing="ij") - chain = np.vstack([m.flatten() for m in meshes]).T - weights = weights.flatten() - assert weights.size == chain[:, 0].size, "Error, given weight array size disagrees with parameter sampling" - - if len(chain.shape) == 1: - chain = chain[None].T - - if name is None: - name = "Chain %d" % len(self.chains) - - if power is not None: - assert isinstance(power, int) or isinstance(power, float), "Power should be numeric, but is %s" % type(power) - - if self._default_parameters is None and parameters is not None: - self._default_parameters = parameters - - if parameters is None: - if self._default_parameters is not None: - assert chain.shape[1] == len(self._default_parameters), "Chain has %d dimensions, but default parameters have %d dimensions" % ( - chain.shape[1], - len(self._default_parameters), - ) - parameters = self._default_parameters - self._logger.debug("Adding chain using default parameters") - else: - self._logger.debug("Adding chain with no parameter names") - parameters = ["%d" % x for x in range(chain.shape[1])] - else: - self._logger.debug("Adding chain with defined parameters") - assert len(parameters) <= chain.shape[1], "Have only %d columns in chain, but have been given %d parameters names! " "Please double check this." % ( - chain.shape[1], - len(parameters), - ) - for p in parameters: - if p not in self._all_parameters: - self._all_parameters.append(p) - - if shift_params is not None: - if isinstance(shift_params, list): - shift_params = dict([(p, s) for p, s in zip(parameters, shift_params)]) - for key in shift_params.keys(): - if key not in parameters: - self._logger.warning("Warning, shift parameter %s is not in list of parameters %s" % (key, parameters)) - - # Sorry, no KDE for you on a grid. - if grid: - kde = None - if color is not None: - color = self.color_finder.get_formatted([color])[0] - - c = Chain( - chain, - parameters, - name, - weights=weights, - posterior=posterior, - walkers=walkers, - grid=grid, - num_free_params=num_free_params, - num_eff_data_points=num_eff_data_points, - color=color, - linewidth=linewidth, - linestyle=linestyle, - kde=kde, - shade_alpha=shade_alpha, - power=power, - marker_style=marker_style, - marker_size=marker_size, - marker_alpha=marker_alpha, - plot_contour=plot_contour, - plot_point=plot_point, - show_as_1d_prior=show_as_1d_prior, - statistics=statistics, - cloud=cloud, - shade=shade, - shade_gradient=shade_gradient, - bar_shade=bar_shade, - bins=bins, - smooth=smooth, - color_params=color_params, - plot_color_params=plot_color_params, - cmap=cmap, - num_cloud=num_cloud, - zorder=zorder, - shift_params=shift_params, - ) - self.chains.append(c) - self._init_params() - return self - - def add_covariance(self, mean, covariance, parameters=None, name=None, **kwargs): - r""" Generate samples as per mean and covariance supplied. Useful for Fisher matrix forecasts. - - Parameters - ---------- - mean : list|np.ndarray - The an array of mean values. - covariance : list|np.ndarray - The 2D array describing the covariance. Dimensions should agree with the `mean` input. - parameters : list[str], optional - A list of parameter names, one for each column (dimension) in the mean array. - name : str, optional - The name of the chain. Used when plotting multiple chains at once. - kwargs : - Extra arguments about formatting - identical to what you would find in `add_chain`. `linewidth`, `color`, - etc. - - Returns - ------- - ChainConsumer - Itself, to allow chaining calls. - """ - chain = np.random.multivariate_normal(mean, covariance, size=1000000) - self.add_chain(chain, parameters=parameters, name=name, **kwargs) - self.chains[-1].mcmc_chain = False # So we dont plot this when looking at walks, etc - return self - - def add_marker( - self, location, parameters=None, name=None, color=None, marker_size=None, marker_style=None, marker_alpha=None, - ): - r""" Add a marker to the plot at the given location. - - Parameters - ---------- - location : list|np.ndarray - The coordinates to place the marker - parameters : list[str], optional - A list of parameter names, one for each column (dimension) in the mean array. - name : str, optional - The name of the chain. Used when plotting multiple chains at once. - color : str(hex), optional - Provide a colour for the chain. Can be used instead of calling `configure` for convenience. - marker_style : str|, optional - The marker style to use when plotting points. Defaults to `'.'` - marker_size : numeric|, optional - Size of markers, if plotted. Defaults to `20`. - marker_alpha : numeric, optional - The alpha values when plotting markers. - - Returns - ------- - ChainConsumer - Itself, to allow chaining calls. - """ - chain = np.vstack((location, location)) - posterior = np.array([0, 1]) - self.add_chain( - chain, - parameters=parameters, - posterior=posterior, - name=name, - color=color, - marker_size=marker_size, - marker_style=marker_style, - marker_alpha=marker_alpha, - plot_point=True, - plot_contour=False, - ) - self.chains[-1].mcmc_chain = False # So we dont plot this when looking at walks, etc - return self - - def remove_chain(self, chain=-1): - r""" Removes a chain from ChainConsumer. - - Calling this will require any configurations set to be redone! - - Parameters - ---------- - chain : int|str, list[str|int] - The chain(s) to remove. You can pass in either the chain index, or the chain name, to remove it. - By default removes the last chain added. - - Returns - ------- - ChainConsumer - Itself, to allow chaining calls. - """ - if isinstance(chain, str) or isinstance(chain, int): - chain = [chain] - - chain = sorted([i for c in chain for i in self._get_chain(c)])[::-1] - assert len(chain) == len(list(set(chain))), "Error, you are trying to remove a chain more than once." - - for index in chain: - del self.chains[index] - - seen = set() - self._all_parameters = [p for c in self.chains for p in c.parameters if not (p in seen or seen.add(p))] - - # Need to reconfigure - self._init_params() - - return self - - def configure( - self, - statistics="max", - max_ticks=5, - plot_hists=True, - flip=True, - serif=False, - sigma2d=False, - sigmas=None, - summary=None, - bins=None, - cmap=None, - colors=None, - linestyles=None, - linewidths=None, - kde=False, - smooth=None, - cloud=None, - shade=None, - shade_alpha=None, - shade_gradient=None, - bar_shade=None, - num_cloud=None, - color_params=None, - plot_color_params=False, - cmaps=None, - plot_contour=None, - plot_point=None, - show_as_1d_prior=None, - global_point=True, - marker_style=None, - marker_size=None, - marker_alpha=None, - usetex=False, - diagonal_tick_labels=True, - label_font_size=12, - tick_font_size=10, - spacing=None, - contour_labels=None, - contour_label_font_size=10, - legend_kwargs=None, - legend_location=None, - legend_artists=None, - legend_color_text=True, - watermark_text_kwargs=None, - summary_area=0.6827, - zorder=None, - stack=False, - ): # pragma: no cover - r""" Configure the general plotting parameters common across the bar - and contour plots. - - If you do not call this explicitly, the :func:`plot` - method will invoke this method automatically. - - Please ensure that you call this method *after* adding all the relevant data to the - chain consumer, as the consume changes configuration values depending on - the supplied data. - - Parameters - ---------- - statistics : string|list[str], optional - Which sort of statistics to use. Defaults to `"max"` for maximum likelihood - statistics. Other available options are `"mean"`, `"cumulative"`, `"max_symmetric"`, - `"max_closest"` and `"max_central"`. In the - very, very rare case you want to enable different statistics for different - chains, you can pass in a list of strings. - max_ticks : int, optional - The maximum number of ticks to use on the plots - plot_hists : bool, optional - Whether to plot marginalised distributions or not - flip : bool, optional - Set to false if, when plotting only two parameters, you do not want it to - rotate the histogram so that it is horizontal. - sigma2d: bool, optional - Defaults to `False`. When `False`, uses :math:`\sigma` levels for 1D Gaussians - ie confidence - levels of 68% and 95%. When `True`, uses the confidence levels for 2D Gaussians, where 1 and 2 - :math:`\sigma` represents 39% and 86% confidence levels respectively. - sigmas : np.array, optional - The :math:`\sigma` contour levels to plot. Defaults to [0, 1, 2, 3] for a single chain - and [0, 1, 2] for multiple chains. - serif : bool, optional - Whether to display ticks and labels with serif font. - summary : bool, optional - If overridden, sets whether parameter summaries should be set as axis titles. - Will not work if you have multiple chains - bins : int|float,list[int|float], optional - The number of bins to use. By default uses :math:`\frac{\sqrt{n}}{10}`, where - :math:`n` are the number of data points. Giving an integer will set the number - of bins to the given value. Giving a float will scale the number of bins, such - that giving ``bins=1.5`` will result in using :math:`\frac{1.5\sqrt{n}}{10}` bins. - Note this parameter is most useful if `kde=False` is also passed, so you - can actually see the bins and not a KDE. - cmap : str, optional - Set to the matplotlib colour map you want to use to overwrite the default colours. - Note that this parameter overwrites colours. The `cmaps` parameters is different, - and used when you ask for an extra dimension to be used to colour scatter points. - See the online examples to see the difference. - colors : str(hex)|list[str(hex)], optional - Provide a list of colours to use for each chain. If you provide more chains - than colours, you *will* get the rainbow colour spectrum. If you only pass - one colour, all chains are set to this colour. This probably won't look good. - linestyles : str|list[str], optional - Provide a list of line styles to plot the contours and marginalised - distributions with. By default, this will become a list of solid lines. If a - string is passed instead of a list, this style is used for all chains. - linewidths : float|list[float], optional - Provide a list of line widths to plot the contours and marginalised - distributions with. By default, this is a width of 1. If a float - is passed instead of a list, this width is used for all chains. - kde : bool|float|list[bool|float], optional - Whether to use a Gaussian KDE to smooth marginalised posteriors. If false, uses - bins and linear interpolation, so ensure you have plenty of samples if your - distribution is highly non-gaussian. Due to the slowness of performing a - KDE on all data, it is often useful to disable this before producing final - plots. If float, scales the width of the KDE bandpass manually. - smooth : int|list[int], optional - Defaults to 3. How much to smooth the marginalised distributions using a gaussian filter. - If ``kde`` is set to true, this parameter is ignored. Setting it to either - ``0``, ``False`` disables smoothing. For grid data, smoothing - is set to 0 by default, not 3. - cloud : bool|list[bool], optional - If set, overrides the default behaviour and plots the cloud or not - shade : bool|list[bool] optional - If set, overrides the default behaviour and plots filled contours or not. If a list of - bools is passed, you can turn shading on or off for specific chains. - shade_alpha : float|list[float], optional - Filled contour alpha value override. Default is 1.0. If a list is passed, you can set the - shade opacity for specific chains. - shade_gradient : float|list[float], optional - How much to vary colours in different contour levels. - bar_shade : bool|list[bool], optional - If set to true, shades in confidence regions in under histogram. By default - this happens if you less than 3 chains, but is disabled if you are comparing - more chains. You can pass a list if you wish to shade some chains but not others. - num_cloud : int|list[int], optional - The number of scatter points to show when enabling `cloud` or setting one of the parameters - to colour scatter. Defaults to 15k per chain. - color_params : str|list[str], optional - The name of the parameter to use for the colour scatter. Defaults to none, for no colour. If set - to 'weights', 'log_weights', or 'posterior' (without the quotes), and that is not a parameter in the chain, - it will respectively use the weights, log weights, or posterior, to colour the points. - plot_color_params : bool|list[bool], optional - Whether or not the colour parameter should also be plotted as a posterior surface. - cmaps : str|list[str], optional - The matplotlib colourmap to use in the `colour_param`. If you have multiple `color_param`s, you can - specific a different cmap for each variable. By default ChainConsumer will cycle between several - cmaps. - plot_contour : bool|list[bool], optional - Whether to plot the whole contour (as opposed to a point). Defaults to true for less than - 25 concurrent chains. - plot_point : bool|list[bool], optional - Whether to plot a maximum likelihood point. Defaults to true for more then 24 chains. - show_as_1d_prior : bool|list[bool], optional - Showing as a 1D prior will show the 1D histograms, but won't plot the 2D contours. - global_point : bool, optional - Whether the point which gets plotted is the global posterior maximum, or the marginalised 2D - posterior maximum. Note that when you use marginalised 2D maximums for the points, you do not - get the 1D histograms. Defaults to `True`, for a global maximum value. - marker_style : str|list[str], optional - The marker style to use when plotting points. Defaults to `'.'` - marker_size : numeric|list[numeric], optional - Size of markers, if plotted. Defaults to `20`. - marker_alpha : numeric|list[numeric], optional - The alpha values when plotting markers. - usetex : bool, optional - Whether or not to parse text as LaTeX in plots. - diagonal_tick_labels : bool, optional - Whether to display tick labels on a 45 degree angle. - label_font_size : int|float, optional - The font size for plot axis labels and axis titles if summaries are configured to display. - tick_font_size : int|float, optional - The font size for the tick labels in the plots. - spacing : float, optional - The amount of spacing to add between plots. Defaults to `None`, which equates to 1.0 for less - than 6 dimensions and 0.0 for higher dimensions. - contour_labels : string, optional - If unset do not plot contour labels. If set to "confidence", label the using confidence - intervals. If set to "sigma", labels using sigma. - contour_label_font_size : int|float, optional - The font size for contour labels, if they are enabled. - legend_kwargs : dict, optional - Extra arguments to pass to the legend api. - legend_location : tuple(int,int), optional - Specifies the subplot in which to locate the legend. By default, this will be (0, -1), - corresponding to the top right subplot if there are more than two parameters, - and the bottom left plot for only two parameters with flip on. - For having the legend in the primary subplot - in the bottom left, set to (-1,0). - legend_artists : bool, optional - Whether to include hide artists in the legend. If all linestyles and line widths are identical, - this will default to false (as only the colours change). Otherwise it will be true. - legend_color_text : bool, optional - Whether to colour the legend text. - watermark_text_kwargs : dict, optional - Options to pass to the fontdict property when generating text for the watermark. - summary_area : float, optional - The confidence interval used when generating parameter summaries. Defaults to 1 sigma, aka 0.6827 - zorder : int, optional - The zorder to pass to `matplotlib` to determine visual ordering when plotting. - - Returns - ------- - ChainConsumer - Itself, to allow chaining calls. - """ - # Warn the user if configure has been invoked multiple times - self._num_configure_calls += 1 - if self._num_configure_calls > 1: - self._logger.warning("Configure has been called %d times - this is not good - it should be once!" % self._num_configure_calls) - self._logger.warning("To avoid this, load your chains in first, then call analysis/plotting methods") - - # Dirty way of ensuring overrides happen when requested - l = locals() - explicit = [] - for k in l.keys(): - if l[k] is not None: - explicit.append(k) - if k.endswith("s"): - explicit.append(k[:-1]) - self._init_params() - - num_chains = len(self.chains) - - assert cmap is None or colors is None, "You cannot both ask for cmap colours and then give explicit colours" - - # Determine statistics - assert statistics is not None, "statistics should be a string or list of strings!" - if isinstance(statistics, str): - assert statistics in list(Analysis.summaries), "statistics %s not recognised. Should be in %s" % (statistics, Analysis.summaries,) - statistics = [statistics.lower()] * len(self.chains) - elif isinstance(statistics, list): - for i, l in enumerate(statistics): - statistics[i] = l.lower() - else: - raise ValueError("statistics is not a string or a list!") - - # Determine KDEs - if isinstance(kde, bool) or isinstance(kde, float): - kde = [False if c.grid else kde for c in self.chains] - - kde_override = [c.kde for c in self.chains] - kde = [c2 if c2 is not None else c1 for c1, c2 in zip(kde, kde_override)] - - # Determine bins - if bins is None: - bins = get_bins(self.chains) - elif isinstance(bins, list): - bins = [b2 if isinstance(b2, int) else np.floor(b2 * b1) for b1, b2 in zip(get_bins(self.chains), bins)] - elif isinstance(bins, float): - bins = [np.floor(b * bins) for b in get_bins(self.chains)] - elif isinstance(bins, int): - bins = [bins] * len(self.chains) - else: - raise ValueError("bins value is not a recognised class (float or int)") - - # Determine smoothing - if smooth is None: - smooth = [0 if c.grid or k else 3 for c, k in zip(self.chains, kde)] - else: - if smooth is not None and not smooth: - smooth = 0 - if isinstance(smooth, list): - smooth = [0 if k else s for s, k in zip(smooth, kde)] - else: - smooth = [0 if k else smooth for k in kde] - - # Determine color parameters - if color_params is None: - color_params = [None] * num_chains - else: - if isinstance(color_params, str): - color_params = [color_params if color_params in cs.parameters + ["log_weights", "weights", "posterior"] else None for cs in self.chains] - color_params = [None if c == "posterior" and self.chains[i].posterior is None else c for i, c in enumerate(color_params)] - elif isinstance(color_params, list) or isinstance(color_params, tuple): - for c, chain in zip(color_params, self.chains): - p = chain.parameters - if c is not None: - assert c in p, "Color parameter %s not in parameters %s" % (c, p) - # Determine if we should plot color parameters - if isinstance(plot_color_params, bool): - plot_color_params = [plot_color_params] * len(color_params) - - # Determine cmaps - if cmaps is None: - param_cmaps = {} - cmaps = [] - i = 0 - for cp in color_params: - if cp is None: - cmaps.append(None) - elif cp in param_cmaps: - cmaps.append(param_cmaps[cp]) - else: - param_cmaps[cp] = self._cmaps[i] - cmaps.append(self._cmaps[i]) - i = (i + 1) % len(self._cmaps) - - # Determine colours - if colors is None: - if cmap: - colors = self.color_finder.get_colormap(num_chains, cmap) - else: - if num_chains > len(self._all_colours): - num_needed_colours = np.sum([c is None for c in color_params]) - colour_list = self.color_finder.get_colormap(num_needed_colours, "inferno") - else: - colour_list = self._all_colours - colors = [] - ci = 0 - for c in color_params: - if c: - colors.append("#000000") - else: - colors.append(colour_list[ci]) - ci += 1 - elif isinstance(colors, str): - colors = [colors] * len(self.chains) - colors = self.color_finder.get_formatted(colors) - - # Determine linestyles - if linestyles is None: - i = 0 - linestyles = [] - for c in color_params: - if c is None: - linestyles.append(self._linestyles[0]) - else: - linestyles.append(self._linestyles[i]) - i = (i + 1) % len(self._linestyles) - elif isinstance(linestyles, str): - linestyles = [linestyles] * len(self.chains) - - # Determine linewidths - if linewidths is None: - linewidths = [1.0] * len(self.chains) - elif isinstance(linewidths, float) or isinstance(linewidths, int): - linewidths = [linewidths] * len(self.chains) - - # Determine clouds - if cloud is None: - cloud = False - cloud = [cloud or c is not None for c in color_params] - - # Determine cloud points - if num_cloud is None: - num_cloud = 30000 - if isinstance(num_cloud, int) or isinstance(num_cloud, float): - num_cloud = [int(num_cloud)] * num_chains - - # Should we shade the contours - if shade is None: - if shade_alpha is None: - shade = num_chains <= 3 - else: - shade = True - if isinstance(shade, bool): - # If not overridden, do not shade chains with colour scatter points - shade = [shade and c is None for c in color_params] - - # Modify shade alpha based on how many chains we have - if shade_alpha is None: - if num_chains == 1: - if contour_labels is not None: - shade_alpha = 0.75 - else: - shade_alpha = 1.0 - else: - shade_alpha = 1.0 / np.sqrt(num_chains) - # Decrease the shading amount if there are colour scatter points - if isinstance(shade_alpha, float) or isinstance(shade_alpha, int): - shade_alpha = [shade_alpha if c is None else 0.25 * shade_alpha for c in color_params] - - if shade_gradient is None: - shade_gradient = 1.0 - if isinstance(shade_gradient, float): - shade_gradient = [shade_gradient] * num_chains - elif isinstance(shade_gradient, list): - assert len(shade_gradient) == num_chains, "Have %d shade_gradient but % chains" % (len(shade_gradient), num_chains,) - - contour_over_points = num_chains < 20 - - if plot_contour is None: - plot_contour = [contour_over_points if chain.posterior is not None else True for chain in self.chains] - elif isinstance(plot_contour, bool): - plot_contour = [plot_contour] * num_chains - - if plot_point is None: - plot_point = [not contour_over_points] * num_chains - elif isinstance(plot_point, bool): - plot_point = [plot_point] * num_chains - - if show_as_1d_prior is None: - show_as_1d_prior = [not contour_over_points] * num_chains - elif isinstance(show_as_1d_prior, bool): - show_as_1d_prior = [show_as_1d_prior] * num_chains - - if marker_style is None: - marker_style = ["."] * num_chains - elif isinstance(marker_style, str): - marker_style = [marker_style] * num_chains - - if marker_size is None: - marker_size = [20] * num_chains - elif isinstance(marker_style, (int, float)): - marker_size = [marker_size] * num_chains - - if marker_alpha is None: - marker_alpha = [1.0] * num_chains - elif isinstance(marker_alpha, (int, float)): - marker_alpha = [marker_alpha] * num_chains - - # Figure out if we should display parameter summaries - if summary is not None: - summary = summary and num_chains == 1 - - # Figure out bar shading - if bar_shade is None: - bar_shade = num_chains <= 3 - if isinstance(bar_shade, bool): - bar_shade = [bar_shade] * num_chains - - if zorder is None: - zorder = [1] * num_chains - - # Figure out how many sigmas to plot - if sigmas is None: - if num_chains == 1: - sigmas = np.array([0, 1, 2]) - else: - sigmas = np.array([0, 1, 2]) - if sigmas[0] != 0: - sigmas = np.concatenate(([0], sigmas)) - sigmas = np.sort(sigmas) - - if contour_labels is not None: - assert isinstance(contour_labels, str), "contour_labels parameter should be a string" - contour_labels = contour_labels.lower() - assert contour_labels in ["sigma", "confidence",], "contour_labels should be either sigma or confidence" - assert isinstance(contour_label_font_size, int) or isinstance(contour_label_font_size, float), "contour_label_font_size needs to be numeric" - - if legend_artists is None: - legend_artists = len(set(linestyles)) > 1 or len(set(linewidths)) > 1 - - if legend_kwargs is not None: - assert isinstance(legend_kwargs, dict), "legend_kwargs should be a dict" - else: - legend_kwargs = {} - - if num_chains < 3: - labelspacing = 0.5 - elif num_chains == 3: - labelspacing = 0.2 - else: - labelspacing = 0.15 - legend_kwargs_default = { - "labelspacing": labelspacing, - "loc": "upper right", - "frameon": False, - "fontsize": label_font_size, - "handlelength": 1, - "handletextpad": 0.2, - "borderaxespad": 0.0, - } - legend_kwargs_default.update(legend_kwargs) - - watermark_text_kwargs_default = { - "color": "#333333", - "alpha": 0.7, - "verticalalignment": "center", - "horizontalalignment": "center", - } - if watermark_text_kwargs is None: - watermark_text_kwargs = {} - watermark_text_kwargs_default.update(watermark_text_kwargs) - - assert isinstance(summary_area, float), "summary_area needs to be a float, not %s!" % type(summary_area) - assert summary_area > 0, "summary_area should be a positive number, instead is %s!" % summary_area - assert summary_area < 1, "summary_area must be less than unity, instead is %s!" % summary_area - assert isinstance(global_point, bool), "global_point should be a bool" - - # List options - for i, c in enumerate(self.chains): - try: - c.update_unset_config("statistics", statistics[i], override=explicit) - c.update_unset_config("color", colors[i], override=explicit) - c.update_unset_config("linestyle", linestyles[i], override=explicit) - c.update_unset_config("linewidth", linewidths[i], override=explicit) - c.update_unset_config("cloud", cloud[i], override=explicit) - c.update_unset_config("shade", shade[i], override=explicit) - c.update_unset_config("shade_alpha", shade_alpha[i], override=explicit) - c.update_unset_config("shade_gradient", shade_gradient[i], override=explicit) - c.update_unset_config("bar_shade", bar_shade[i], override=explicit) - c.update_unset_config("bins", bins[i], override=explicit) - c.update_unset_config("kde", kde[i], override=explicit) - c.update_unset_config("smooth", smooth[i], override=explicit) - c.update_unset_config("color_params", color_params[i], override=explicit) - c.update_unset_config("plot_color_params", plot_color_params[i], override=explicit) - c.update_unset_config("cmap", cmaps[i], override=explicit) - c.update_unset_config("num_cloud", num_cloud[i], override=explicit) - c.update_unset_config("marker_style", marker_style[i], override=explicit) - c.update_unset_config("marker_size", marker_size[i], override=explicit) - c.update_unset_config("marker_alpha", marker_alpha[i], override=explicit) - c.update_unset_config("plot_contour", plot_contour[i], override=explicit) - c.update_unset_config("plot_point", plot_point[i], override=explicit) - c.update_unset_config("show_as_1d_prior", show_as_1d_prior[i], override=explicit) - c.update_unset_config("zorder", zorder[i], override=explicit) - c.config["summary_area"] = summary_area - - except IndentationError as e: - print( - "Index error when assigning chain properties, make sure you " - "have enough properties set for the number of chains you have loaded! " - "See the stack trace for which config item has the wrong number of entries." - ) - raise e - - # Non list options - self.config["sigma2d"] = sigma2d - self.config["sigmas"] = sigmas - self.config["summary"] = summary - self.config["flip"] = flip - self.config["serif"] = serif - self.config["plot_hists"] = plot_hists - self.config["max_ticks"] = max_ticks - self.config["usetex"] = usetex - self.config["diagonal_tick_labels"] = diagonal_tick_labels - self.config["label_font_size"] = label_font_size - self.config["tick_font_size"] = tick_font_size - self.config["spacing"] = spacing - self.config["contour_labels"] = contour_labels - self.config["contour_label_font_size"] = contour_label_font_size - self.config["legend_location"] = legend_location - self.config["legend_kwargs"] = legend_kwargs_default - self.config["legend_artists"] = legend_artists - self.config["legend_color_text"] = legend_color_text - self.config["watermark_text_kwargs"] = watermark_text_kwargs_default - self.config["global_point"] = global_point - - self._configured = True - return self - - def configure_truth(self, **kwargs): # pragma: no cover - r""" Configure the arguments passed to the ``axvline`` and ``axhline`` - methods when plotting truth values. - - If you do not call this explicitly, the :func:`plot` method will - invoke this method automatically. - - Recommended to set the parameters ``linestyle``, ``color`` and/or ``alpha`` - if you want some basic control. - - Default is to use an opaque black dashed line. - - Parameters - ---------- - kwargs : dict - The keyword arguments to unwrap when calling ``axvline`` and ``axhline``. - - Returns - ------- - ChainConsumer - Itself, to allow chaining calls. - """ - if kwargs.get("ls") is None and kwargs.get("linestyle") is None: - kwargs["ls"] = "--" - # kwargs["dashes"] = (3, 3) - if kwargs.get("lw") is None and kwargs.get("linewidth") is None: - kwargs["linewidth"] = 1 - if kwargs.get("color") is None: - kwargs["color"] = "#000000" - if kwargs.get("zorder") is None: - kwargs["zorder"] = 100 - self.config_truth = kwargs - self._configured_truth = True - return self - - def divide_chain(self, chain=0): - r""" - Returns a ChainConsumer instance containing all the walks of a given chain - as individual chains themselves. - - This method might be useful if, for example, your chain was made using - MCMC with 4 walkers. To check the sampling of all 4 walkers agree, you could - call this to get a ChainConsumer instance with one chain for ech of the - four walks. If you then plot, hopefully all four contours - you would see agree. - - Parameters - ---------- - chain : int|str, optional - The index or name of the chain you want divided - - Returns - ------- - ChainConsumer - A new ChainConsumer instance with the same settings as the parent instance, containing - ``num_walker`` chains. - """ - indexes = self._get_chain(chain) - con = ChainConsumer() - - for index in indexes: - chain = self.chains[index] - assert chain.walkers is not None, "The chain you have selected was not added with any walkers!" - num_walkers = chain.walkers - data = np.split(chain.chain, num_walkers) - ws = np.split(chain.weights, num_walkers) - for j, (c, w) in enumerate(zip(data, ws)): - con.add_chain(c, weights=w, name="Chain %d" % j, parameters=chain.parameters) - return con - - def _get_chain(self, chain): - if isinstance(chain, Chain): - return [self.chains.index(chain)] - if isinstance(chain, str): - names = [c.name for c in self.chains] - assert chain in names, "Chain %s not found!" % chain - index = [i for i, n in enumerate(names) if chain == n] - elif isinstance(chain, int): - assert chain < len(self.chains), "Chain index %d not found!" % chain - index = [chain] - else: - raise ValueError("Type %s not recognised for chain" % type(chain)) - return index - - def _get_chain_name(self, index): - return self.chains[index].name - - def _all_names(self): - return [c.name for c in self.chains] - - # Deprecated methods - def plot(self, *args, **kwargs): # pragma: no cover - print("This method is deprecated. Please use chainConsumer.plotter.plot instead") - return self.plotter.plot(*args, **kwargs) - - def plot_walks(self, *args, **kwargs): # pragma: no cover - print("This method is deprecated. Please use chainConsumer.plotter.plot_walks instead") - return self.plotter.plot_walks(*args, **kwargs) - - def get_latex_table(self, *args, **kwargs): # pragma: no cover - print("This method is deprecated. Please use chainConsumer.analysis.get_latex_table instead") - return self.analysis.get_latex_table(*args, **kwargs) - - def get_parameter_text(self, *args, **kwargs): # pragma: no cover - print("This method is deprecated. Please use chainConsumer.analysis.get_parameter_text instead") - return self.analysis.get_parameter_text(*args, **kwargs) - - def get_summary(self, *args, **kwargs): # pragma: no cover - print("This method is deprecated. Please use chainConsumer.analysis.get_summary instead") - return self.analysis.get_summary(*args, **kwargs) - - def get_correlations(self, *args, **kwargs): # pragma: no cover - print("This method is deprecated. Please use chainConsumer.analysis.get_correlations instead") - return self.analysis.get_correlations(*args, **kwargs) - - def get_correlation_table(self, *args, **kwargs): # pragma: no cover - print("This method is deprecated. Please use chainConsumer.analysis.get_correlation_table instead") - return self.analysis.get_correlation_table(*args, **kwargs) - - def get_covariance(self, *args, **kwargs): # pragma: no cover - print("This method is deprecated. Please use chainConsumer.analysis.get_covariance instead") - return self.analysis.get_covariance(*args, **kwargs) - - def get_covariance_table(self, *args, **kwargs): # pragma: no cover - print("This method is deprecated. Please use chainConsumer.analysis.get_covariance_table instead") - return self.analysis.get_covariance_table(*args, **kwargs) - - def diagnostic_gelman_rubin(self, *args, **kwargs): # pragma: no cover - print("This method is deprecated. Please use chainConsumer.diagnostic.gelman_rubin instead") - return self.diagnostic.gelman_rubin(*args, **kwargs) - - def diagnostic_geweke(self, *args, **kwargs): # pragma: no cover - print("This method is deprecated. Please use chainConsumer.diagnostic.geweke instead") - return self.diagnostic.geweke(*args, **kwargs) - - def comparison_aic(self): # pragma: no cover - print("This method is deprecated. Please use chainConsumer.comparison.aic instead") - return self.comparison.aic() - - def comparison_bic(self): # pragma: no cover - print("This method is deprecated. Please use chainConsumer.comparison.bic instead") - return self.comparison.bic() - - def comparison_dic(self): # pragma: no cover - print("This method is deprecated. Please use chainConsumer.comparison.dic instead") - return self.comparison.dic() - - def comparison_table(self, *args, **kwargs): # pragma: no cover - print("This method is deprecated. Please use chainConsumer.comparison.comparison_table instead") - return self.comparison.comparison_table(*args, **kwargs) diff --git a/chainconsumer/colors.py b/chainconsumer/colors.py deleted file mode 100644 index 7b22bf27..00000000 --- a/chainconsumer/colors.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- -from matplotlib.colors import rgb2hex -import matplotlib.pyplot as plt -import numpy as np - -# Colours drawn from material designs colour pallet at https://material.io/guidelines/style/color.html - - -class Colors(object): - def __init__(self): - self.color_map = { - "blue": "#1976D2", - "lblue": "#4FC3F7", - "red": "#E53935", - "green": "#43A047", - "lgreen": "#8BC34A", - "purple": "#673AB7", - "cyan": "#4DD0E1", - "magenta": "#E91E63", - "yellow": "#F2D026", - "black": "#333333", - "grey": "#9E9E9E", - "orange": "#FB8C00", - "amber": "#FFB300", - "brown": "#795548", - } - self.aliases = { - "b": "blue", - "r": "red", - "g": "green", - "k": "black", - "m": "magenta", - "c": "cyan", - "o": "orange", - "y": "yellow", - "a": "amber", - "p": "purple", - "e": "grey", - "lg": "lgreen", - "lb": "lblue", - } - self.default_colors = ["blue", "lgreen", "red", "purple", "yellow", "grey", "lblue", "magenta", "green", "brown", "black", "orange"] - - def format(self, color): - if isinstance(color, np.ndarray): - color = rgb2hex(color) - if color[0] == "#": - return color - elif color in self.color_map: - return self.color_map[color] - elif color in self.aliases: - alias = self.aliases[color] - return self.color_map[alias] - else: - raise ValueError("Color %s is not mapped. Please give a hex code" % color) - - def get_formatted(self, list_colors): - return [self.format(c) for c in list_colors] - - def get_default(self): - return self.get_formatted(self.default_colors) - - def get_colormap(self, num, cmap_name, scale=0.7): # pragma: no cover - color_list = self.get_formatted(plt.get_cmap(cmap_name)(np.linspace(0.05, 0.9, num))) - scales = scale + (1 - scale) * np.abs(1 - np.linspace(0, 2, num)) - scaled = [self.scale_colour(c, s) for c, s in zip(color_list, scales)] - return scaled - - def scale_colour(self, colour, scalefactor): # pragma: no cover - if isinstance(colour, np.ndarray): - r, g, b = colour[:3] * 255.0 - else: - hexx = colour.strip("#") - if scalefactor < 0 or len(hexx) != 6: - return hexx - r, g, b = int(hexx[:2], 16), int(hexx[2:4], 16), int(hexx[4:], 16) - r = self._clamp(int(r * scalefactor)) - g = self._clamp(int(g * scalefactor)) - b = self._clamp(int(b * scalefactor)) - return "#%02x%02x%02x" % (r, g, b) - - def _clamp(self, val, minimum=0, maximum=255): - if val < minimum: - return minimum - if val > maximum: - return maximum - return val diff --git a/chainconsumer/comparisons.py b/chainconsumer/comparisons.py deleted file mode 100644 index d2322933..00000000 --- a/chainconsumer/comparisons.py +++ /dev/null @@ -1,269 +0,0 @@ -# -*- coding: utf-8 -*- -from scipy.interpolate import griddata -import numpy as np -import logging - -from .helpers import get_latex_table_frame - - -class Comparison(object): - def __init__(self, parent): - self.parent = parent - self._logger = logging.getLogger("chainconsumer") - - def dic(self): - r""" Returns the corrected Deviance Information Criterion (DIC) for all chains loaded into ChainConsumer. - - If a chain does not have a posterior, this method will return `None` for that chain. **Note that - the DIC metric is only valid on posterior surfaces which closely resemble multivariate normals!** - Formally, we follow Liddle (2007) and first define *Bayesian complexity* as - - .. math:: - p_D = \bar{D}(\theta) - D(\bar{\theta}), - - where :math:`D(\theta) = -2\ln(P(\theta)) + C` is the deviance, where :math:`P` is the posterior - and :math:`C` a constant. From here the DIC is defined as - - .. math:: - DIC \equiv D(\bar{\theta}) + 2p_D = \bar{D}(\theta) + p_D. - - Returns - ------- - list[float] - A list of all the DIC values - one per chain, in the order in which the chains were added. - - References - ---------- - [1] Andrew R. Liddle, "Information criteria for astrophysical model selection", MNRAS (2007) - """ - dics = [] - dics_bool = [] - for i, chain in enumerate(self.parent.chains): - p = chain.posterior - if p is None: - dics_bool.append(False) - self._logger.warning("You need to set the posterior for chain %s to get the DIC" % chain.name) - else: - dics_bool.append(True) - num_params = chain.chain.shape[1] - means = np.array([np.average(chain.chain[:, ii], weights=chain.weights) for ii in range(num_params)]) - d = -2 * p - d_of_mean = griddata(chain.chain, d, means, method="nearest")[0] - mean_d = np.average(d, weights=chain.weights) - p_d = mean_d - d_of_mean - dic = mean_d + p_d - dics.append(dic) - if len(dics) > 0: - dics -= np.min(dics) - dics_fin = [] - i = 0 - for b in dics_bool: - if not b: - dics_fin.append(None) - else: - dics_fin.append(dics[i]) - i += 1 - return dics_fin - - def bic(self): - r""" Returns the corrected Bayesian Information Criterion (BIC) for all chains loaded into ChainConsumer. - - If a chain does not have a posterior, number of data points, and number of free parameters - loaded, this method will return `None` for that chain. Formally, the BIC is defined as - - .. math:: - BIC \equiv -2\ln(P) + k \ln(N), - - where :math:`P` represents the posterior, :math:`k` the number of model parameters and :math:`N` - the number of independent data points used in the model fitting. - - Returns - ------- - list[float] - A list of all the BIC values - one per chain, in the order in which the chains were added. - """ - bics = [] - bics_bool = [] - for i, chain in enumerate(self.parent.chains): - p, n_data, n_free = chain.posterior, chain.num_eff_data_points, chain.num_free_params - if p is None or n_data is None or n_free is None: - bics_bool.append(False) - missing = "" - if p is None: - missing += "posterior, " - if n_data is None: - missing += "num_eff_data_points, " - if n_free is None: - missing += "num_free_params, " - - self._logger.warning("You need to set %s for chain %s to get the BIC" % (missing[:-2], chain.name)) - else: - bics_bool.append(True) - bics.append(n_free * np.log(n_data) - 2 * np.max(p)) - if len(bics) > 0: - bics -= np.min(bics) - bics_fin = [] - i = 0 - for b in bics_bool: - if not b: - bics_fin.append(None) - else: - bics_fin.append(bics[i]) - i += 1 - return bics_fin - - def aic(self): - r""" Returns the corrected Akaike Information Criterion (AICc) for all chains loaded into ChainConsumer. - - If a chain does not have a posterior, number of data points, and number of free parameters - loaded, this method will return `None` for that chain. Formally, the AIC is defined as - - .. math:: - AIC \equiv -2\ln(P) + 2k, - - where :math:`P` represents the posterior, and :math:`k` the number of model parameters. The AICc - is then defined as - - .. math:: - AIC_c \equiv AIC + \frac{2k(k+1)}{N-k-1}, - - where :math:`N` represents the number of independent data points used in the model fitting. - The AICc is a correction for the AIC to take into account finite chain sizes. - - Returns - ------- - list[float] - A list of all the AICc values - one per chain, in the order in which the chains were added. - """ - aics = [] - aics_bool = [] - for i, chain in enumerate(self.parent.chains): - p, n_data, n_free = chain.posterior, chain.num_eff_data_points, chain.num_free_params - if p is None or n_data is None or n_free is None: - aics_bool.append(False) - missing = "" - if p is None: - missing += "posterior, " - if n_data is None: - missing += "num_eff_data_points, " - if n_free is None: - missing += "num_free_params, " - - self._logger.warning("You need to set %s for chain %s to get the AIC" % (missing[:-2], chain.name)) - else: - aics_bool.append(True) - c_cor = 1.0 * n_free * (n_free + 1) / (n_data - n_free - 1) - aics.append(2.0 * (n_free + c_cor - np.max(p))) - if len(aics) > 0: - aics -= np.min(aics) - aics_fin = [] - i = 0 - for b in aics_bool: - if not b: - aics_fin.append(None) - else: - aics_fin.append(aics[i]) - i += 1 - return aics_fin - - def comparison_table( - self, caption=None, label="tab:model_comp", hlines=True, aic=True, bic=True, dic=True, sort="bic", descending=True - ): # pragma: no cover - """ - Return a LaTeX ready table of model comparisons. - - Parameters - ---------- - caption : str, optional - The table caption to insert. - label : str, optional - The table label to insert. - hlines : bool, optional - Whether to insert hlines in the table or not. - aic : bool, optional - Whether to include a column for AICc or not. - bic : bool, optional - Whether to include a column for BIC or not. - dic : bool, optional - Whether to include a column for DIC or not. - sort : str, optional - How to sort the models. Should be one of "bic", "aic" or "dic". - descending : bool, optional - The sort order. - - Returns - ------- - str - A LaTeX table to be copied into your document. - """ - - if sort == "bic": - assert bic, "You cannot sort by BIC if you turn it off" - if sort == "aic": - assert aic, "You cannot sort by AIC if you turn it off" - if sort == "dic": - assert dic, "You cannot sort by DIC if you turn it off" - - if caption is None: - caption = "" - if label is None: - label = "" - - base_string = get_latex_table_frame(caption, label) - end_text = " \\\\ \n" - num_cols = 1 + (1 if aic else 0) + (1 if bic else 0) - column_text = "c" * (num_cols + 1) - center_text = "" - hline_text = "\\hline\n" - if hlines: - center_text += hline_text - center_text += "\tModel" + (" & AIC" if aic else "") + (" & BIC " if bic else "") + (" & DIC " if dic else "") + end_text - if hlines: - center_text += "\t" + hline_text - if aic: - aics = self.aic() - else: - aics = np.zeros(len(self.parent.chains)) - if bic: - bics = self.bic() - else: - bics = np.zeros(len(self.parent.chains)) - if dic: - dics = self.dic() - else: - dics = np.zeros(len(self.parent.chains)) - - if sort == "bic": - to_sort = bics - elif sort == "aic": - to_sort = aics - elif sort == "dic": - to_sort = dics - else: - raise ValueError("sort %s not recognised, must be dic, aic or dic" % sort) - - good = [i for i, t in enumerate(to_sort) if t is not None] - names = [self.parent.chains[g].name for g in good] - aics = [aics[g] for g in good] - bics = [bics[g] for g in good] - to_sort = bics if sort == "bic" else aics - - indexes = np.argsort(to_sort) - - if descending: - indexes = indexes[::-1] - - for i in indexes: - line = "\t" + names[i] - if aic: - line += " & %5.1f " % aics[i] - if bic: - line += " & %5.1f " % bics[i] - if dic: - line += " & %5.1f " % dics[i] - line += end_text - center_text += line - if hlines: - center_text += "\t" + hline_text - - return base_string % (column_text, center_text) diff --git a/chainconsumer/diagnostic.py b/chainconsumer/diagnostic.py deleted file mode 100644 index 0316f6a1..00000000 --- a/chainconsumer/diagnostic.py +++ /dev/null @@ -1,132 +0,0 @@ -# -*- coding: utf-8 -*- -import numpy as np -import logging -from scipy.stats import normaltest - - -class Diagnostic(object): - def __init__(self, parent): - self.parent = parent - self._logger = logging.getLogger("chainconsumer") - - def gelman_rubin(self, chain=None, threshold=0.05): - r""" Runs the Gelman Rubin diagnostic on the supplied chains. - - Parameters - ---------- - chain : int|str, optional - Which chain to run the diagnostic on. By default, this is `None`, - which will run the diagnostic on all chains. You can also - supply and integer (the chain index) or a string, for the chain - name (if you set one). - threshold : float, optional - The maximum deviation permitted from 1 for the final value - :math:`\hat{R}` - - Returns - ------- - float - whether or not the chains pass the test - - Notes - ----- - - I follow PyMC in calculating the Gelman-Rubin statistic, where, - having :math:`m` chains of length :math:`n`, we compute - - .. math:: - - B = \frac{n}{m-1} \sum_{j=1}^{m} \left(\bar{\theta}_{.j} - \bar{\theta}_{..}\right)^2 - - W = \frac{1}{m} \sum_{j=1}^{m} \left[ \frac{1}{n-1} \sum_{i=1}^{n} \left( \theta_{ij} - \bar{\theta_{.j}}\right)^2 \right] - - where :math:`\theta` represents each model parameter. We then compute - :math:`\hat{V} = \frac{n_1}{n}W + \frac{1}{n}B`, and have our convergence ratio - :math:`\hat{R} = \sqrt{\frac{\hat{V}}{W}}`. We check that for all parameters, - this ratio deviates from unity by less than the supplied threshold. - """ - if chain is None: - return np.all([self.gelman_rubin(k, threshold=threshold) for k in range(len(self.parent.chains))]) - - index = self.parent._get_chain(chain) - assert len(index) == 1, "Please specify only one chain, have %d chains" % len(index) - chain = self.parent.chains[index[0]] - - num_walkers = chain.walkers - parameters = chain.parameters - name = chain.name - data = chain.chain - chains = np.split(data, num_walkers) - assert num_walkers > 1, "Cannot run Gelman-Rubin statistic with only one walker" - m = 1.0 * len(chains) - n = 1.0 * chains[0].shape[0] - all_mean = np.mean(data, axis=0) - chain_means = np.array([np.mean(c, axis=0) for c in chains]) - chain_var = np.array([np.var(c, axis=0, ddof=1) for c in chains]) - b = n / (m - 1) * ((chain_means - all_mean) ** 2).sum(axis=0) - w = (1 / m) * chain_var.sum(axis=0) - var = (n - 1) * w / n + b / n - v = var + b / (n * m) - R = np.sqrt(v / w) - - passed = np.abs(R - 1) < threshold - print("Gelman-Rubin Statistic values for chain %s" % name) - for p, v, pas in zip(parameters, R, passed): - param = "Param %d" % p if isinstance(p, int) else p - print("%s: %7.5f (%s)" % (param, v, "Passed" if pas else "Failed")) - return np.all(passed) - - def geweke(self, chain=None, first=0.1, last=0.5, threshold=0.05): - """ Runs the Geweke diagnostic on the supplied chains. - - Parameters - ---------- - chain : int|str, optional - Which chain to run the diagnostic on. By default, this is `None`, - which will run the diagnostic on all chains. You can also - supply and integer (the chain index) or a string, for the chain - name (if you set one). - first : float, optional - The amount of the start of the chain to use - last : float, optional - The end amount of the chain to use - threshold : float, optional - The p-value to use when testing for normality. - - Returns - ------- - float - whether or not the chains pass the test - - """ - if chain is None: - return np.all([self.geweke(k, threshold=threshold) for k in range(len(self.parent.chains))]) - - index = self.parent._get_chain(chain) - assert len(index) == 1, "Please specify only one chain, have %d chains" % len(index) - chain = self.parent.chains[index[0]] - - num_walkers = chain.walkers - assert num_walkers is not None and num_walkers > 0, "You need to specify the number of walkers to use the Geweke diagnostic." - name = chain.name - data = chain.chain - chains = np.split(data, num_walkers) - n = 1.0 * chains[0].shape[0] - n_start = int(np.floor(first * n)) - n_end = int(np.floor((1 - last) * n)) - mean_start = np.array([np.mean(c[:n_start, i]) for c in chains for i in range(c.shape[1])]) - var_start = np.array([self._spec(c[:n_start, i]) / c[:n_start, i].size for c in chains for i in range(c.shape[1])]) - mean_end = np.array([np.mean(c[n_end:, i]) for c in chains for i in range(c.shape[1])]) - var_end = np.array([self._spec(c[n_end:, i]) / c[n_end:, i].size for c in chains for i in range(c.shape[1])]) - zs = (mean_start - mean_end) / (np.sqrt(var_start + var_end)) - _, pvalue = normaltest(zs) - print("Gweke Statistic for chain %s has p-value %e" % (name, pvalue)) - return pvalue > threshold - - # Method of estimating spectral density following PyMC. - # See https://github.com/pymc-devs/pymc/blob/master/pymc/diagnostics.py - def _spec(self, x, order=2): - from statsmodels.regression.linear_model import yule_walker - - beta, sigma = yule_walker(x, order) - return sigma ** 2 / (1.0 - np.sum(beta)) ** 2 diff --git a/chainconsumer/helpers.py b/chainconsumer/helpers.py deleted file mode 100644 index 8f515f01..00000000 --- a/chainconsumer/helpers.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -import numpy as np - - -def get_extents(data, weight, plot=False, wide_extents=True, tiny=False, pad=False): - hist, be = np.histogram(data, weights=weight, bins=2000) - bc = 0.5 * (be[1:] + be[:-1]) - cdf = hist.cumsum() - cdf = cdf / cdf.max() - icdf = (1 - cdf)[::-1] - icdf = icdf / icdf.max() - cdf = 1 - icdf[::-1] - threshold = 1e-4 if plot else 1e-5 - if plot and not wide_extents: - threshold = 0.05 - if tiny: - threshold = 0.3 - i1 = np.where(cdf > threshold)[0][0] - i2 = np.where(icdf > threshold)[0][0] - lower = bc[i1] - upper = bc[-i2] - if pad: - width = upper - lower - lower -= 0.2 * width - upper += 0.2 * width - return lower, upper - - -def get_bins(chains): - proposal = [max(35, np.floor(1.0 * np.power(chain.chain.shape[0] / chain.chain.shape[1], 0.25))) for chain in chains] - return proposal - - -def get_smoothed_bins(smooth, bins, data, weight, marginalised=True, plot=False, pad=False): - minv, maxv = get_extents(data, weight, plot=plot, pad=pad) - if smooth is None or not smooth or smooth == 0: - return np.linspace(minv, maxv, int(bins)), 0 - else: - return np.linspace(minv, maxv, int((2 if marginalised else 2) * smooth * bins)), smooth - - -def get_grid_bins(data): - bin_c = np.sort(np.unique(data)) - delta = 0.5 * (bin_c[1] - bin_c[0]) - bins = np.concatenate((bin_c - delta, [bin_c[-1] + delta])) - return bins - - -def get_latex_table_frame(caption, label): # pragma: no cover - base_string = r"""\begin{table} - \centering - \caption{%s} - \label{%s} - \begin{tabular}{%s} - %s \end{tabular} -\end{table}""" - return base_string % (caption, label, "%s", "%s") diff --git a/chainconsumer/plotter.py b/chainconsumer/plotter.py deleted file mode 100644 index 5be11cdb..00000000 --- a/chainconsumer/plotter.py +++ /dev/null @@ -1,1375 +0,0 @@ -# -*- coding: utf-8 -*- -import logging -import numpy as np -import matplotlib.pyplot as plt -import matplotlib -from matplotlib.font_manager import FontProperties -from matplotlib.ticker import MaxNLocator, ScalarFormatter, LogLocator -from matplotlib.textpath import TextPath -from numpy import meshgrid -from scipy.interpolate import interp1d -from scipy.ndimage import gaussian_filter -from scipy.stats import norm - -from .helpers import get_extents, get_smoothed_bins, get_grid_bins -from .kde import MegKDE - - -class Plotter(object): - def __init__(self, parent): - self.parent = parent - self._logger = logging.getLogger("chainconsumer") - - self.usetex_old = matplotlib.rcParams["text.usetex"] - self.serif_old = matplotlib.rcParams["font.family"] - - def plot( - self, - figsize="GROW", - parameters=None, - chains=None, - extents=None, - filename=None, - display=False, - truth=None, - legend=None, - blind=None, - watermark=None, - log_scales=None, - ): # pragma: no cover - """ Plot the chain! - - Parameters - ---------- - figsize : str|tuple(float)|float, optional - The figure size to generate. Accepts a regular two tuple of size in inches, - or one of several key words. The default value of ``COLUMN`` creates a figure - of appropriate size of insertion into an A4 LaTeX document in two-column mode. - ``PAGE`` creates a full page width figure. ``GROW`` creates an image that - scales with parameters (1.5 inches per parameter). String arguments are not - case sensitive. If you pass a float, it will scale the default ``GROW`` by - that amount, so ``2.0`` would result in a plot 3 inches per parameter. - parameters : list[str]|int, optional - If set, only creates a plot for those specific parameters (if list). If an - integer is given, only plots the fist so many parameters. - chains : int|str, list[str|int], optional - Used to specify which chain to show if more than one chain is loaded in. - Can be an integer, specifying the - chain index, or a str, specifying the chain name. - extents : list[tuple[float]] or dict[str], optional - Extents are given as two-tuples. You can pass in a list the same size as - parameters (or default parameters if you don't specify parameters), - or as a dictionary. - filename : str, optional - If set, saves the figure to this location - display : bool, optional - If True, shows the figure using ``plt.show()``. - truth : list[float] or dict[str], optional - A list of truth values corresponding to parameters, or a dictionary of - truth values indexed by key - legend : bool, optional - If true, creates a legend in your plot using the chain names. - blind : bool|string|list[string], optional - Whether to blind axes values. Can be set to `True` to blind all parameters, - or can pass in a string (or list of strings) which specify the parameters to blind. - watermark : str, optional - A watermark to add to the figure - log_scales : bool, list[bool] or dict[bool], optional - Whether or not to use a log scale on any given axis. Can be a list of True/False, a list of param - names to set to true, a dictionary of param names with true/false - or just a bool (just `True` would set everything to log scales). - - Returns - ------- - figure - the matplotlib figure - - """ - - chains, parameters, truth, extents, blind, log_scales = self._sanitise( - chains, parameters, truth, extents, color_p=True, blind=blind, log_scales=log_scales - ) - names = [chain.name for chain in chains] - - if legend is None: - legend = len(chains) > 1 - - # If no chains have names, don't plot the legend - legend = legend and len([n for n in names if n]) > 0 - - # Calculate cmap extents - unique_color_params = list(set([c.config["color_params"] for c in chains if c.config["color_params"] is not None])) - num_cax = len(unique_color_params) - color_param_extents = {} - for u in unique_color_params: - umin, umax = np.inf, -np.inf - for chain in chains: - if chain.config["color_params"] == u: - data = chain.get_color_data() - if data is not None: - umin = min(umin, data.min()) - umax = max(umax, data.max()) - color_param_extents[u] = (umin, umax) - - grow_size = 1.5 - if isinstance(figsize, float): - grow_size *= figsize - figsize = "GROW" - - if isinstance(figsize, str): - if figsize.upper() == "COLUMN": - figsize = (5 + (1 if num_cax > 0 else 0), 5) - elif figsize.upper() == "PAGE": - figsize = (10, 10) - elif figsize.upper() == "GROW": - figsize = (grow_size * len(parameters) + num_cax * 1.0, grow_size * len(parameters)) - else: - raise ValueError("Unknown figure size %s" % figsize) - elif isinstance(figsize, float): - figsize = (figsize * grow_size * len(parameters), figsize * grow_size * len(parameters)) - - plot_hists = self.parent.config["plot_hists"] - flip = len(parameters) == 2 and plot_hists and self.parent.config["flip"] - - fig, axes, params1, params2, extents = self._get_figure( - parameters, chains=chains, figsize=figsize, flip=flip, external_extents=extents, blind=blind, log_scales=log_scales - ) - label_font_size = self.parent.config["label_font_size"] - - axl = axes.ravel().tolist() - summary = self.parent.config["summary"] - - if summary is None: - summary = len(parameters) < 5 and len(self.parent.chains) == 1 - if len(chains) == 1: - self._logger.debug("Plotting surfaces for chain of dimension %s" % (chains[0].chain.shape,)) - else: - self._logger.debug("Plotting surfaces for %d chains" % len(chains)) - cbar_done = [] - - chain_points = [c for c in chains if c.config["plot_point"]] - num_chain_points = len(chain_points) - if num_chain_points: - subgroup_names = list(set([c.name for c in chain_points])) - subgroups = [[c for c in chain_points if c.name == n] for n in subgroup_names] - markers = [group[0].config["marker_style"] for group in subgroups] # Only one marker per group - marker_sizes = [[g.config["marker_size"] for g in group] for group in subgroups] # But size can diff - marker_alphas = [group[0].config["marker_alpha"] for group in subgroups] # Only one marker per group - for i, p1 in enumerate(params1): - for j, p2 in enumerate(params2): - if i < j: - continue - ax = axes[i, j] - do_flip = flip and i == len(params1) - 1 - - # Plot the histograms - if plot_hists and i == j: - if do_flip: - self._add_truth(ax, truth, p1) - else: - self._add_truth(ax, truth, None, py=p2) - max_val = None - - # Plot each chain - for chain in chains: - if p1 not in chain.parameters: - continue - if not chain.config["plot_contour"]: - continue - - param_summary = summary and p1 not in blind - m = self._plot_bars(ax, p1, chain, flip=do_flip, summary=param_summary) - - if max_val is None or m > max_val: - max_val = m - - if num_chain_points and self.parent.config["global_point"]: - m = self._plot_point_histogram(ax, subgroups, p1, flip=do_flip) - if max_val is None or m > max_val: - max_val = m - - if max_val is not None: - if do_flip: - ax.set_xlim(0, 1.1 * max_val) - else: - ax.set_ylim(0, 1.1 * max_val) - - else: - for chain in chains: - if p1 not in chain.parameters or p2 not in chain.parameters: - continue - if not chain.config["plot_contour"] or chain.config["show_as_1d_prior"]: - continue - h = None - if p1 in chain.parameters and p2 in chain.parameters: - h = self._plot_contour(ax, chain, p1, p2, color_extents=color_param_extents) - cp = chain.config["color_params"] - if h is not None and cp is not None and cp not in cbar_done: - cbar_done.append(cp) - aspect = figsize[1] / 0.15 - fraction = 0.85 / figsize[0] - cbar = fig.colorbar(h, ax=axl, aspect=aspect, pad=0.03, fraction=fraction, drawedges=False) - label = cp - if label == "weights": - label = "Weights" - elif label == "log_weights": - label = "log(Weights)" - elif label == "posterior": - label = "log(Posterior)" - cbar.set_label(label, fontsize=label_font_size) - cbar.solids.set(alpha=1) - - if num_chain_points: - self._plot_points(ax, subgroups, markers, marker_sizes, marker_alphas, p1, p2) - - self._add_truth(ax, truth, p1, py=p2) - - colors = [c.config["color"] for c in chains] - plot_points = [c.config["plot_point"] for c in chains] - plot_contours = [c.config["plot_contour"] for c in chains] - linestyles = [c.config["linestyle"] for c in chains] - linewidths = [c.config["linewidth"] for c in chains] - marker_styles = [c.config["marker_style"] for c in chains] - marker_sizes = [c.config["marker_size"] for c in chains] - legend_kwargs = self.parent.config["legend_kwargs"] - legend_artists = self.parent.config["legend_artists"] - legend_color_text = self.parent.config["legend_color_text"] - legend_location = self.parent.config["legend_location"] - - if legend_location is None: - if not flip or len(parameters) > 2: - legend_location = (0, -1) - else: - legend_location = (-1, 0) - outside = legend_location[0] >= legend_location[1] - if names is not None and legend: - ax = axes[legend_location[0], legend_location[1]] - if "markerfirst" not in legend_kwargs: - # If we have legend inside a used subplot, switch marker order - legend_kwargs["markerfirst"] = outside or not legend_artists - linewidths2 = linewidths if legend_artists else [0] * len(linewidths) - linestyles2 = linestyles if legend_artists else ["-"] * len(linestyles) - marker_sizes2 = marker_sizes if legend_artists else [0] * len(linestyles) - - artists = [] - done_names = [] - final_colors = [] - for i, (n, c, ls, lw, marker, size, pp, pc) in enumerate( - zip(names, colors, linestyles2, linewidths2, marker_styles, marker_sizes2, plot_points, plot_contours) - ): - if n is None or n in done_names: - continue - done_names.append(n) - final_colors.append(c) - size = np.sqrt(size) # plot vs scatter use size differently, hence the sqrt - if pc and not pp: - artists.append(plt.Line2D((0, 1), (0, 0), color=c, ls=ls, lw=lw)) - elif not pc and pp: - artists.append(plt.Line2D((0, 1), (0, 0), color=c, ls=ls, lw=0, marker=marker, markersize=size)) - else: - artists.append(plt.Line2D((0, 1), (0, 0), color=c, ls=ls, lw=lw, marker=marker, markersize=size)) - - leg = ax.legend(artists, done_names, **legend_kwargs) - if legend_color_text: - for text, c in zip(leg.get_texts(), final_colors): - text.set_weight("medium") - text.set_color(c) - if not outside: - loc = legend_kwargs.get("loc") or "" - if isinstance(loc, str) and "right" in loc.lower(): - vp = leg._legend_box._children[-1]._children[0] - vp.align = "right" - - fig.canvas.draw() - for ax in axes[-1, :]: - offset = ax.get_xaxis().get_offset_text() - ax.set_xlabel("{0} {1}".format(ax.get_xlabel(), "[{0}]".format(offset.get_text()) if offset.get_text() else "")) - offset.set_visible(False) - for ax in axes[:, 0]: - offset = ax.get_yaxis().get_offset_text() - ax.set_ylabel("{0} {1}".format(ax.get_ylabel(), "[{0}]".format(offset.get_text()) if offset.get_text() else "")) - offset.set_visible(False) - - dpi = 300 - if watermark: - if flip and len(parameters) == 2: - ax = axes[-1, 0] - else: - ax = None - self._add_watermark(fig, ax, figsize, watermark, dpi=dpi) - - if filename is not None: - if isinstance(filename, str): - filename = [filename] - for f in filename: - self._save_fig(fig, f, dpi) - if display: - plt.show() - - return fig - - def _save_fig(self, fig, filename, dpi): # pragma: no cover - fig.savefig(filename, bbox_inches="tight", dpi=dpi, transparent=True, pad_inches=0.05) - - def _add_watermark(self, fig, axes, figsize, text, dpi=300, size_scale=1.0): # pragma: no cover - # Code based off github repository https://github.com/cpadavis/preliminize - dx, dy = figsize - dy, dx = dy * dpi, dx * dpi - rotation = 180 / np.pi * np.arctan2(-dy, dx) - property_dict = self.parent.config["watermark_text_kwargs"] - - keys_in_font_dict = ["family", "style", "variant", "weight", "stretch", "size"] - fontdict = {k: property_dict[k] for k in keys_in_font_dict if k in property_dict} - font_prop = FontProperties(**fontdict) - usetex = property_dict.get("usetex", self.parent.config["usetex"]) - if usetex: - px, py, scale = 0.5, 0.5, 1.0 - else: - px, py, scale = 0.5, 0.5, 0.8 - - bb0 = TextPath((0, 0), text, size=50, prop=font_prop, usetex=usetex).get_extents() - bb1 = TextPath((0, 0), text, size=51, prop=font_prop, usetex=usetex).get_extents() - dw = (bb1.width - bb0.width) * (dpi / 100) - dh = (bb1.height - bb0.height) * (dpi / 100) - size = np.sqrt(dy ** 2 + dx ** 2) / (dh * abs(dy / dx) + dw) * 0.6 * scale * size_scale - if axes is not None: - if usetex: - size *= 0.7 - else: - size *= 0.8 - size = int(size) - print(f"Font size is {size}") - if axes is None: - fig.text(px, py, text, fontdict=property_dict, rotation=rotation, fontsize=size) - else: - axes.text(px, py, text, transform=axes.transAxes, fontdict=property_dict, rotation=rotation, fontsize=size) - - def plot_walks( - self, - parameters=None, - truth=None, - extents=None, - display=False, - filename=None, - chains=None, - convolve=None, - figsize=None, - plot_weights=True, - plot_posterior=True, - log_weight=None, - log_scales=None, - ): # pragma: no cover - """ Plots the chain walk; the parameter values as a function of step index. - - This plot is more for a sanity or consistency check than for use with final results. - Plotting this before plotting with :func:`plot` allows you to quickly see if the - chains are well behaved, or if certain parameters are suspect - or require a greater burn in period. - - The desired outcome is to see an unchanging distribution along the x-axis of the plot. - If there are obvious tails or features in the parameters, you probably want - to investigate. - - Parameters - ---------- - parameters : list[str]|int, optional - Specify a subset of parameters to plot. If not set, all parameters are plotted. - If an integer is given, only the first so many parameters are plotted. - truth : list[float]|dict[str], optional - A list of truth values corresponding to parameters, or a dictionary of - truth values keyed by the parameter. - extents : list[tuple]|dict[str], optional - A list of two-tuples for plot extents per parameter, or a dictionary of - extents keyed by the parameter. - display : bool, optional - If set, shows the plot using ``plt.show()`` - filename : str, optional - If set, saves the figure to the filename - chains : int|str, list[str|int], optional - Used to specify which chain to show if more than one chain is loaded in. - Can be an integer, specifying the - chain index, or a str, specifying the chain name. - convolve : int, optional - If set, overplots a smoothed version of the steps using ``convolve`` as - the width of the smoothing filter. - figsize : tuple, optional - If set, sets the created figure size. - plot_weights : bool, optional - If true, plots the weight if they are available - plot_posterior : bool, optional - If true, plots the log posterior if they are available - log_weight : bool, optional - Whether to display weights in log space or not. If None, the value is - inferred by the mean weights of the plotted chains. - log_scales : bool, list[bool] or dict[bool], optional - Whether or not to use a log scale on any given axis. Can be a list of True/False, a list of param - names to set to true, a dictionary of param names with true/false - or just a bool (just `True` would set everything to log scales). - - Returns - ------- - figure - the matplotlib figure created - - """ - - chains, parameters, truth, extents, _, log_scales = self._sanitise(chains, parameters, truth, extents, log_scales=log_scales) - - chains = [c for c in chains if c.mcmc_chain] - n = len(parameters) - extra = 0 - if plot_weights: - plot_weights = plot_weights and np.any([np.any(c.weights != 1.0) for c in chains]) - - plot_posterior = plot_posterior and np.any([c.posterior is not None for c in chains]) - - if plot_weights: - extra += 1 - if plot_posterior: - extra += 1 - - if figsize is None: - figsize = (8, 0.75 + (n + extra)) - - fig, axes = plt.subplots(figsize=figsize, nrows=n + extra, squeeze=False, sharex=True) - - for i, axes_row in enumerate(axes): - ax = axes_row[0] - if i >= extra: - p = parameters[i - n] - for chain in chains: - if p in chain.parameters: - chain_row = chain.get_data(p) - log = log_scales.get(p, False) - self._plot_walk(ax, p, chain_row, extents=extents.get(p), convolve=convolve, color=chain.config["color"], log_scale=log) - if truth.get(p) is not None: - self._plot_walk_truth(ax, truth.get(p)) - else: - if i == 0 and plot_posterior: - for chain in chains: - if chain.posterior is not None: - self._plot_walk(ax, r"$\log(P)$", chain.posterior - chain.posterior.max(), convolve=convolve, color=chain.config["color"]) - else: - if log_weight is None: - log_weight = np.any([chain.weights.mean() < 0.1 for chain in chains]) - if log_weight: - for chain in chains: - self._plot_walk(ax, r"$\log_{10}(w)$", np.log10(chain.weights), convolve=convolve, color=chain.config["color"]) - else: - for chain in chains: - self._plot_walk(ax, "$w$", chain.weights, convolve=convolve, color=chain.config["color"]) - - if filename is not None: - if isinstance(filename, str): - filename = [filename] - for f in filename: - self._save_fig(fig, f, 300) - if display: - plt.show() - - return fig - - def plot_distributions( - self, parameters=None, truth=None, extents=None, display=False, filename=None, chains=None, col_wrap=4, figsize=None, blind=None, log_scales=None - ): # pragma: no cover - """ Plots the 1D parameter distributions for verification purposes. - - This plot is more for a sanity or consistency check than for use with final results. - Plotting this before plotting with :func:`plot` allows you to quickly see if the - chains give well behaved distributions, or if certain parameters are suspect - or require a greater burn in period. - - - Parameters - ---------- - parameters : list[str]|int, optional - Specify a subset of parameters to plot. If not set, all parameters are plotted. - If an integer is given, only the first so many parameters are plotted. - truth : list[float]|dict[str], optional - A list of truth values corresponding to parameters, or a dictionary of - truth values keyed by the parameter. - extents : list[tuple]|dict[str], optional - A list of two-tuples for plot extents per parameter, or a dictionary of - extents keyed by the parameter. - display : bool, optional - If set, shows the plot using ``plt.show()`` - filename : str, optional - If set, saves the figure to the filename - chains : int|str, list[str|int], optional - Used to specify which chain to show if more than one chain is loaded in. - Can be an integer, specifying the - chain index, or a str, specifying the chain name. - col_wrap : int, optional - How many columns to plot before wrapping. - figsize : tuple(float)|float, optional - Either a tuple specifying the figure size or a float scaling factor. - blind : bool|string|list[string], optional - Whether to blind axes values. Can be set to `True` to blind all parameters, - or can pass in a string (or list of strings) which specify the parameters to blind. - log_scales : bool, list[bool] or dict[bool], optional - Whether or not to use a log scale on any given axis. Can be a list of True/False, a list of param - names to set to true, a dictionary of param names with true/false - or just a bool (just `True` would set everything to log scales). - - Returns - ------- - figure - the matplotlib figure created - - """ - chains, parameters, truth, extents, blind, log_scales = self._sanitise(chains, parameters, truth, extents, blind=blind, log_scales=log_scales) - - n = len(parameters) - num_cols = min(n, col_wrap) - num_rows = int(np.ceil(1.0 * n / col_wrap)) - - if figsize is None: - figsize = 1.0 - if isinstance(figsize, float): - figsize_float = figsize - figsize = (num_cols * 2 * figsize, num_rows * 2 * figsize) - else: - figsize_float = 1.0 - - summary = self.parent.config["summary"] - label_font_size = self.parent.config["label_font_size"] - tick_font_size = self.parent.config["tick_font_size"] - max_ticks = self.parent.config["max_ticks"] - diagonal_tick_labels = self.parent.config["diagonal_tick_labels"] - - if summary is None: - summary = len(self.parent.chains) == 1 - - hspace = (0.8 if summary else 0.5) / figsize_float - fig, axes = plt.subplots(nrows=num_rows, ncols=num_cols, figsize=figsize, squeeze=False) - fig.subplots_adjust(left=0.1, right=0.95, top=0.95, bottom=0.1, wspace=0.05, hspace=hspace) - - formatter = ScalarFormatter(useOffset=False) - formatter.set_powerlimits((-3, 4)) - - for i, ax in enumerate(axes.flatten()): - if i >= len(parameters): - ax.set_axis_off() - continue - p = parameters[i] - - ax.set_yticks([]) - if log_scales.get(p, False): - ax.set_xscale("log") - if p in blind: - ax.set_xticks([]) - else: - if diagonal_tick_labels: - _ = [l.set_rotation(45) for l in ax.get_xticklabels()] - _ = [l.set_fontsize(tick_font_size) for l in ax.get_xticklabels()] - - if log_scales.get(p, False): - ax.xaxis.set_major_locator(LogLocator(numticks=max_ticks)) - else: - ax.xaxis.set_major_locator(MaxNLocator(max_ticks, prune="lower")) - ax.xaxis.set_major_formatter(formatter) - ax.set_xlim(extents.get(p) or self._get_parameter_extents(p, chains)) - - max_val = None - for chain in chains: - if not chain.config["plot_contour"]: - continue - if p in chain.parameters: - param_summary = summary and p not in blind - m = self._plot_bars(ax, p, chain, summary=param_summary) - if max_val is None or m > max_val: - max_val = m - - self._add_truth(ax, truth, None, py=p) - ax.set_ylim(0, 1.1 * max_val) - ax.set_xlabel(p, fontsize=label_font_size) - - if filename is not None: - if isinstance(filename, str): - filename = [filename] - for f in filename: - self._save_fig(fig, f, 300) - if display: - plt.show() - - return fig - - def plot_summary( - self, - parameters=None, - truth=None, - extents=None, - display=False, - filename=None, - chains=None, - figsize=1.0, - errorbar=False, - include_truth_chain=True, - blind=None, - watermark=None, - extra_parameter_spacing=0.5, - vertical_spacing_ratio=1.0, - show_names=True, - log_scales=None, - ): # pragma: no cover - """ Plots parameter summaries - - This plot is more for a sanity or consistency check than for use with final results. - Plotting this before plotting with :func:`plot` allows you to quickly see if the - chains give well behaved distributions, or if certain parameters are suspect - or require a greater burn in period. - - - Parameters - ---------- - parameters : list[str]|int, optional - Specify a subset of parameters to plot. If not set, all parameters are plotted. - If an integer is given, only the first so many parameters are plotted. - truth : list[float]|list|list[float]|dict[str]|str, optional - A list of truth values corresponding to parameters, or a dictionary of - truth values keyed by the parameter. Each "truth value" can be either a float (will - draw a vertical line), two floats (a shaded interval) or three floats (min, mean, max), - which renders as a shaded interval with a line for the mean. Or, supply a string - which matches a chain name, and the results for that chain will be used as the 'truth' - extents : list[tuple]|dict[str], optional - A list of two-tuples for plot extents per parameter, or a dictionary of - extents keyed by the parameter. - display : bool, optional - If set, shows the plot using ``plt.show()`` - filename : str, optional - If set, saves the figure to the filename - chains : int|str, list[str|int], optional - Used to specify which chain to show if more than one chain is loaded in. - Can be an integer, specifying the - chain index, or a str, specifying the chain name. - figsize : float, optional - Scale horizontal and vertical figure size. - errorbar : bool, optional - Whether to onle plot an error bar, instead of the marginalised distribution. - include_truth_chain : bool, optional - If you specify another chain as the truth chain, determine if it should still - be plotted. - blind : bool|string|list[string], optional - Whether to blind axes values. Can be set to `True` to blind all parameters, - or can pass in a string (or list of strings) which specify the parameters to blind. - watermark : str, optional - A watermark to add to the figure - extra_parameter_spacing : float, optional - Increase horizontal space for parameter values - vertical_spacing_ratio : float, optional - Increase vertical space for each model - show_names : bool, optional - Whether to show chain names or not. Defaults to `True`. - log_scales : bool, list[bool] or dict[bool], optional - Whether or not to use a log scale on any given axis. Can be a list of True/False, a list of param - names to set to true, a dictionary of param names with true/false - or just a bool (just `True` would set everything to log scales). - - Returns - ------- - figure - the matplotlib figure created - - """ - wide_extents = not errorbar - chains, parameters, truth, extents, blind, log_scales = self._sanitise( - chains, parameters, truth, extents, blind=blind, wide_extents=wide_extents, log_scales=log_scales - ) - - all_names = [c.name for c in self.parent.chains] - - # Check if we're using a chain for truth values - if isinstance(truth, str): - assert truth in all_names, "Truth chain %s is not in the list of added chains %s" % (truth, all_names) - if not include_truth_chain: - chains = [c for c in chains if c.name != truth] - truth = self.parent.analysis.get_summary(chains=truth, parameters=parameters) - - max_param = self._get_size_of_texts(parameters) - fid_dpi = 65 # Seriously I have no idea what value this should be - param_width = extra_parameter_spacing + max(0.5, max_param / fid_dpi) - - if show_names: - max_model_name = self._get_size_of_texts([chain.name for chain in chains]) - model_width = 0.25 + (max_model_name / fid_dpi) - gridspec_kw = {"width_ratios": [model_width] + [param_width] * len(parameters), "height_ratios": [1] * len(chains)} - ncols = 1 + len(parameters) - else: - model_width = 0 - gridspec_kw = {"width_ratios": [param_width] * len(parameters), "height_ratios": [1] * len(chains)} - ncols = len(parameters) - - top_spacing = 0.3 - bottom_spacing = 0.2 - row_height = (0.5 if not errorbar else 0.3) * vertical_spacing_ratio - width = param_width * len(parameters) + model_width - height = top_spacing + bottom_spacing + row_height * len(chains) - top_ratio = 1 - (top_spacing / height) - bottom_ratio = bottom_spacing / height - - figsize = (width * figsize, height * figsize) - fig, axes = plt.subplots(nrows=len(chains), ncols=ncols, figsize=figsize, squeeze=False, gridspec_kw=gridspec_kw) - fig.subplots_adjust(left=0.05, right=0.95, top=top_ratio, bottom=bottom_ratio, wspace=0.0, hspace=0.0) - label_font_size = self.parent.config["label_font_size"] - legend_color_text = self.parent.config["legend_color_text"] - - max_vals = {} - for i, row in enumerate(axes): - chain = chains[i] - - cs, ws, ps, = chain.chain, chain.weights, chain.parameters - gs, ns = chain.grid, chain.name - - colour = chain.config["color"] - - # First one put name of model - if show_names: - ax_first = row[0] - ax_first.set_axis_off() - text_colour = "k" if not legend_color_text else colour - ax_first.text( - 0, 0.5, ns, transform=ax_first.transAxes, fontsize=label_font_size, verticalalignment="center", color=text_colour, weight="medium" - ) - cols = row[1:] - else: - cols = row - - for ax, p in zip(cols, parameters): - # Set up the frames - if i > 0: - ax.spines["top"].set_visible(False) - if i < (len(chains) - 1): - ax.spines["bottom"].set_visible(False) - if i < (len(chains) - 1) or p in blind: - ax.set_xticks([]) - ax.set_yticks([]) - ax.set_xlim(extents[p]) - if log_scales.get(p): - ax.set_xscale("log") - - # Put title in - if i == 0: - ax.set_title(r"$%s$" % p, fontsize=label_font_size) - - # Add truth values - truth_value = truth.get(p) - if truth_value is not None: - if isinstance(truth_value, float) or isinstance(truth_value, int): - truth_mean = truth_value - truth_min, truth_max = None, None - else: - if len(truth_value) == 1: - truth_mean = truth_value - truth_min, truth_max = None, None - elif len(truth_value) == 2: - truth_min, truth_max = truth_value - truth_mean = None - else: - truth_min, truth_mean, truth_max = truth_value - if truth_mean is not None: - ax.axvline(truth_mean, **self.parent.config_truth) - if truth_min is not None and truth_max is not None: - ax.axvspan(truth_min, truth_max, color=self.parent.config_truth["color"], alpha=0.15, lw=0) - # Skip if this chain doesnt have the parameter - if p not in ps: - continue - - # Plot the good stuff - if errorbar: - fv = self.parent.analysis.get_parameter_summary(chain, p) - if fv[0] is not None and fv[2] is not None: - diff = np.abs(np.diff(fv)) - ax.errorbar([fv[1]], 0, xerr=[[diff[0]], [diff[1]]], fmt="o", color=colour) - else: - m = self._plot_bars(ax, p, chain) - if max_vals.get(p) is None or m > max_vals.get(p): - max_vals[p] = m - - for i, row in enumerate(axes): - index = 1 if show_names else 0 - for ax, p in zip(row[index:], parameters): - if not errorbar: - ax.set_ylim(0, 1.1 * max_vals[p]) - - dpi = 300 - if watermark: - ax = None - self._add_watermark(fig, ax, figsize, watermark, dpi=dpi, size_scale=0.8) - - if filename is not None: - if isinstance(filename, str): - filename = [filename] - for f in filename: - self._save_fig(fig, f, dpi) - if display: - plt.show() - - return fig - - def _get_size_of_texts(self, texts): # pragma: no cover - usetex = self.parent.config["usetex"] - size = self.parent.config["label_font_size"] - widths = [TextPath((0, 0), text, usetex=usetex, size=size).get_extents().width for text in texts] - return max(widths) - - def _sanitise(self, chains, parameters, truth, extents, color_p=False, blind=None, wide_extents=True, log_scales=None): # pragma: no cover - - chains = self._sanitise_chains(chains) - - if color_p: - # Get all parameters to plot, taking into account some of them - # might be excluded colour parameters - all_parameters = [] - for chain in chains: - pc = chain.config["plot_color_params"] - cp = chain.config["color_params"] - ps = chain.parameters - for p in ps: - if (p != cp or pc) and p not in all_parameters: - all_parameters.append(p) - else: - all_parameters = [] - for chain in chains: - for p in chain.parameters: - if p not in all_parameters: - all_parameters.append(p) - - if parameters is None: - parameters = all_parameters - elif isinstance(parameters, int): - parameters = self.parent._all_parameters[:parameters] - - if truth is not None and isinstance(truth, np.ndarray): - truth = truth.tolist() - if truth is None: - truth = {} - else: - if isinstance(truth, np.ndarray): - truth = truth.tolist() - if isinstance(truth, list): - truth = dict((p, t) for p, t in zip(parameters, truth)) - - if extents is None: - extents = {} - elif isinstance(extents, list): - extents = dict((p, e) for p, e in zip(parameters, extents)) - - extents = self._get_custom_extents(parameters, chains, extents, wide_extents=wide_extents) - - if log_scales is None: - log_scales = {} - elif isinstance(log_scales, str): - log_scales = {log_scales: True} - elif isinstance(log_scales, list): - old = log_scales - log_scales = {} - for i, item in enumerate(old): - if isinstance(item, bool): - log_scales[parameters[i]] = item - elif isinstance(item, int): - log_scales[parameters[item]] = True - elif isinstance(item, str): - log_scales[item] = True - - elif isinstance(log_scales, bool): - log_scales = dict([(p, log_scales) for p in parameters]) - - if blind is None: - blind = [] - elif isinstance(blind, str): - blind = [blind] - elif isinstance(blind, bool) and blind: - blind = parameters - - self.set_rc_params() - - return chains, parameters, truth, extents, blind, log_scales - - def set_rc_params(self): - if self.parent.config["usetex"]: - plt.rc("text", usetex=True) - else: - plt.rc("text", usetex=False) - if self.parent.config["serif"]: - plt.rc("font", family="serif") - else: - plt.rc("font", family="sans-serif") - - def restore_rc_params(self): - """ Restores the matplotlib rc parameters modified by usetex and serif. - - Unfortunately this cannot be automated because you cannot invoke it whilst you have - an active figure object or matplotlib will destroy you. So do all your plotting, close - the plots, and then you can call this. - """ - plt.rc("text", usetex=self.usetex_old) - plt.rc("font", family=self.serif_old) - - def _get_custom_extents(self, parameters, chains, external_extents, wide_extents=True): # pragma: no cover - extents = {} - for p in parameters: - if external_extents is not None and p in external_extents: - extents[p] = external_extents[p] - else: - extents[p] = self._get_parameter_extents(p, chains, wide_extents=wide_extents) - return extents - - def _get_figure(self, all_parameters, flip, figsize=(5, 5), external_extents=None, chains=None, blind=None, log_scales=None): # pragma: no cover - n = len(all_parameters) - max_ticks = self.parent.config["max_ticks"] - spacing = self.parent.config["spacing"] - plot_hists = self.parent.config["plot_hists"] - label_font_size = self.parent.config["label_font_size"] - tick_font_size = self.parent.config["tick_font_size"] - diagonal_tick_labels = self.parent.config["diagonal_tick_labels"] - if blind is None: - blind = [] - - if chains is None: - chains = self.parent.chains - - if not plot_hists: - n -= 1 - - if spacing is None: - spacing = 1.0 if n < 6 else 0.0 - - if n == 2 and plot_hists and flip: - gridspec_kw = {"width_ratios": [3, 1], "height_ratios": [1, 3]} - else: - gridspec_kw = {} - - fig, axes = plt.subplots(n, n, figsize=figsize, squeeze=False, gridspec_kw=gridspec_kw) - fig.subplots_adjust(left=0.1, right=0.95, top=0.95, bottom=0.1, wspace=0.05 * spacing, hspace=0.05 * spacing) - - extents = self._get_custom_extents(all_parameters, chains, external_extents) - - if plot_hists: - params1 = all_parameters - params2 = all_parameters - else: - params1 = all_parameters[1:] - params2 = all_parameters[:-1] - for i, p1 in enumerate(params1): - for j, p2 in enumerate(params2): - ax = axes[i, j] - formatter_x = ScalarFormatter(useOffset=True) - formatter_x.set_powerlimits((-3, 4)) - formatter_y = ScalarFormatter(useOffset=True) - formatter_y.set_powerlimits((-3, 4)) - - display_x_ticks = False - display_y_ticks = False - if i < j: - ax.set_frame_on(False) - ax.set_xticks([]) - ax.set_yticks([]) - else: - logx = False - logy = False - if p1 == p2: - if log_scales.get(p1): - if flip and j == n - 1: - ax.set_yscale("log") - logy = True - else: - ax.set_xscale("log") - logx = True - else: - if log_scales.get(p1): - ax.set_yscale("log") - logy = True - if log_scales.get(p2): - ax.set_xscale("log") - logx = True - if i != n - 1 or (flip and j == n - 1): - ax.set_xticks([]) - else: - if p2 in blind: - ax.set_xticks([]) - else: - display_x_ticks = True - if isinstance(p2, str): - ax.set_xlabel(p2, fontsize=label_font_size) - if j != 0 or (plot_hists and i == 0): - ax.set_yticks([]) - else: - if p1 in blind: - ax.set_yticks([]) - else: - display_y_ticks = True - if isinstance(p1, str): - ax.set_ylabel(p1, fontsize=label_font_size) - if display_x_ticks: - if diagonal_tick_labels: - _ = [l.set_rotation(45) for l in ax.get_xticklabels()] - _ = [l.set_fontsize(tick_font_size) for l in ax.get_xticklabels()] - if not logx: - ax.xaxis.set_major_locator(MaxNLocator(max_ticks, prune="lower")) - ax.xaxis.set_major_formatter(formatter_x) - else: - ax.xaxis.set_major_locator(LogLocator(numticks=max_ticks)) - else: - ax.set_xticks([]) - if display_y_ticks: - if diagonal_tick_labels: - _ = [l.set_rotation(45) for l in ax.get_yticklabels()] - _ = [l.set_fontsize(tick_font_size) for l in ax.get_yticklabels()] - if not logy: - ax.yaxis.set_major_locator(MaxNLocator(max_ticks, prune="lower")) - ax.yaxis.set_major_formatter(formatter_y) - else: - ax.yaxis.set_major_locator(LogLocator(numticks=max_ticks)) - else: - ax.set_yticks([]) - if i != j or not plot_hists: - ax.set_ylim(extents[p1]) - elif flip and i == 1: - ax.set_ylim(extents[p1]) - ax.set_xlim(extents[p2]) - - return fig, axes, params1, params2, extents - - def _get_parameter_extents(self, parameter, chains, wide_extents=True): - min_val, max_val = None, None - for chain in chains: - if parameter not in chain.parameters: - continue # pragma: no cover - if not chain.config["plot_contour"]: - data = chain.get_data(parameter) - if data.size < 10: - min_prop, max_prop = np.min(data), np.max(data) - else: - if self.parent.config["global_point"]: - min_prop = chain.posterior_max_params.get(parameter) - max_prop = min_prop - else: - data = chain.get_data(parameter) - min_prop, max_prop = get_extents(data, chain.weights, tiny=True) - else: - data = chain.get_data(parameter) - if chain.grid: - min_prop = data.min() - max_prop = data.max() - else: - min_prop, max_prop = get_extents(data, chain.weights, plot=True, wide_extents=wide_extents) - if min_val is None or min_prop < min_val: - min_val = min_prop - if max_val is None or max_prop > max_val: - max_val = max_prop - return min_val, max_val - - def _get_levels(self): - sigma2d = self.parent.config["sigma2d"] - if sigma2d: - levels = 1.0 - np.exp(-0.5 * self.parent.config["sigmas"] ** 2) - else: - levels = 2 * norm.cdf(self.parent.config["sigmas"]) - 1.0 - return levels - - def _plot_points(self, ax, chains_groups, markers, sizes, alphas, py, px): # pragma: no cover - global_point = self.parent.config["global_point"] - for marker, chains, size, alpha in zip(markers, chains_groups, sizes, alphas): - if global_point: - res = self.parent.analysis.get_max_posteriors(parameters=[px, py], chains=chains, squeeze=False) - xs = [r[px] for r in res if r is not None] - ys = [r[py] for r in res if r is not None] - else: - xs, ys, res = [], [], [] - for chain in chains: - if px in chain.parameters and py in chain.parameters: - x = chain.get_data(px) - y = chain.get_data(py) - if x.size <= 2: # Marker only - xs.append(x[0]) - ys.append(y[0]) - res.append({"px": x[0], "py": y[0]}) - else: - hist, x_centers, y_centers = self._get_smoothed_histogram2d(chain, py, px) - index = np.unravel_index(hist.argmax(), hist.shape) - ys.append(x_centers[index[0]]) - xs.append(y_centers[index[1]]) - res.append({"px": xs[-1], "py": ys[-1]}) - else: - res.append(None) - cs = [c.config["color"] for c, r in zip(chains, res) if r is not None] - h = ax.scatter(xs, ys, marker=marker, c=cs, s=size, linewidth=0.7, alpha=alpha) - return h - - def _sanitise_chains(self, chains): - - if not self.parent._configured: - self.parent.configure() - if not self.parent._configured_truth: - self.parent.configure_truth() - - if chains is None: - chains = list(range(len(self.parent.chains))) - else: - if isinstance(chains, str) or isinstance(chains, int): - chains = [chains] - chains = [i for c in chains for i in self.parent._get_chain(c)] - - chains = [self.parent.chains[i] for i in chains] - return chains - - def plot_contour(self, ax, parameter_x, parameter_y, chains=None): - """ A lightweight method to plot contours in an external axis given two specified parameters - - Parameters - ========== - ax : matplotlib axis - The axis to plot on - parameter_x : str - The name of the parameter to plot for the x axis. Must be the string label of the parameter. - parameter_y : str - The name of the parameter to plot for the y axis. Must be the string label of the parameter. - chains : int|str, list[str|int], optional - Used to specify which chain to show if more than one chain is loaded in. - Can be an integer, specifying the chain index, or a str, specifying the chain name, or a list of either. - """ - chains = self._sanitise_chains(chains) - for chain in chains: - self._plot_contour(ax, chain, parameter_y, parameter_x) - - def _plot_contour(self, ax, chain, px, py, color_extents=None): # pragma: no cover - - levels = self._get_levels() - cloud = chain.config["cloud"] - colour = chain.config["color"] - shade = chain.config["shade"] - shade_alpha = chain.config["shade_alpha"] - shade_gradient = chain.config["shade_gradient"] - linestyle = chain.config["linestyle"] - linewidth = chain.config["linewidth"] - zorder = chain.config["zorder"] - cmap = chain.config["cmap"] - contour_labels = self.parent.config["contour_labels"] - if color_extents is None: - color_extents = {} - h = None - color_data = chain.get_color_data() - x = chain.get_data(py) - y = chain.get_data(px) - color_extent = color_extents.get(chain.config["color_params"]) - - cf = self.parent.color_finder - colours = self._scale_colours(colour, len(levels), shade_gradient) - sub = max(0.1, 1 - 0.2 * shade_gradient) - if shade: - sub *= 0.9 - colours2 = [cf.scale_colour(colours[0], sub)] + [cf.scale_colour(c, sub) for c in colours[:-1]] - - hist, x_centers, y_centers = self._get_smoothed_histogram2d(chain, py, px) - - hist[hist == 0] = 1e-16 - vals = self._convert_to_stdev(hist.T) - if cloud: - n = chain.config["num_cloud"] - skip = max(1, int(x.size / n)) - kwargs = {"c": colours[1], "alpha": 0.3} - if color_data is not None: - kwargs["c"] = color_data[::skip] - kwargs["cmap"] = cmap - if color_extent is not None: - kwargs["vmin"] = color_extent[0] - kwargs["vmax"] = color_extent[1] - if color_extent[0] == color_extent[1]: - kwargs["vmax"] = kwargs["vmin"] + 1.0 - - h = ax.scatter(x[::skip], y[::skip], s=10, marker=".", edgecolors="none", **kwargs) - if color_data is None: - h = None - - if shade and shade_alpha > 0: - ax.contourf(x_centers, y_centers, vals, levels=levels, colors=colours, alpha=shade_alpha, zorder=zorder) - con = ax.contour(x_centers, y_centers, vals, levels=levels, colors=colours2, linestyles=linestyle, linewidths=linewidth, zorder=zorder) - - if contour_labels is not None: - lvls = [l for l in con.levels if l != 0.0] - if contour_labels == "sigma": - sigmas = self.parent.config["sigmas"] - fmt = dict([(l, ("$%.1f \\sigma$" % s).replace(".0", "")) for l, s in zip(lvls, sigmas[1:])]) - else: - fmt = dict([(l, "%d\\%%" % (100 * l)) for l in lvls]) - ax.clabel(con, lvls, inline=True, fmt=fmt, fontsize=self.parent.config["contour_label_font_size"]) - return h - - def _add_truth(self, ax, truth, px, py=None): # pragma: no cover - if truth is not None: - if px is not None: - truth_value = truth.get(px) - if truth_value is not None: - ax.axhline(truth_value, **self.parent.config_truth) - if py is not None: - truth_value = truth.get(py) - if truth_value is not None: - ax.axvline(truth_value, **self.parent.config_truth) - - def _plot_bars(self, ax, parameter, chain, flip=False, summary=False): # pragma: no cover - - # Get values from config - colour = chain.config["color"] - linestyle = chain.config["linestyle"] - bar_shade = chain.config["bar_shade"] - linewidth = chain.config["linewidth"] - bins = chain.config["bins"] - smooth = chain.config["smooth"] - kde = chain.config["kde"] - zorder = chain.config["zorder"] - title_size = self.parent.config["label_font_size"] - chain_row = chain.get_data(parameter) - weights = chain.weights - if smooth or kde: - xs, ys, _ = self.parent.analysis._get_smoothed_histogram(chain, parameter, pad=True) - if flip: - ax.plot(ys, xs, color=colour, ls=linestyle, lw=linewidth, zorder=zorder) - else: - ax.plot(xs, ys, color=colour, ls=linestyle, lw=linewidth, zorder=zorder) - else: - if flip: - orientation = "horizontal" - else: - orientation = "vertical" - if chain.grid: - bins = get_grid_bins(chain_row) - else: - bins, smooth = get_smoothed_bins(smooth, bins, chain_row, weights) - hist, edges = np.histogram(chain_row, bins=bins, density=True, weights=weights) - if chain.power is not None: - hist = hist ** chain.power - edge_center = 0.5 * (edges[:-1] + edges[1:]) - xs, ys = edge_center, hist - ax.hist(xs, weights=ys, bins=bins, histtype="step", color=colour, orientation=orientation, ls=linestyle, lw=linewidth, zorder=zorder) - interp_type = "linear" if smooth else "nearest" - interpolator = interp1d(xs, ys, kind=interp_type) - - if bar_shade: - fit_values = self.parent.analysis.get_parameter_summary(chain, parameter) - if fit_values is not None: - lower = fit_values[0] - upper = fit_values[2] - if lower is not None and upper is not None: - if lower < xs.min(): - lower = xs.min() - if upper > xs.max(): - upper = xs.max() - x = np.linspace(lower, upper, 1000) - if flip: - ax.fill_betweenx(x, np.zeros(x.shape), interpolator(x), color=colour, alpha=0.2, zorder=zorder) - else: - ax.fill_between(x, np.zeros(x.shape), interpolator(x), color=colour, alpha=0.2, zorder=zorder) - if summary: - t = self.parent.analysis.get_parameter_text(*fit_values) - if isinstance(parameter, str): - ax.set_title(r"$%s = %s$" % (parameter.strip("$"), t), fontsize=title_size) - else: - ax.set_title(r"$%s$" % t, fontsize=title_size) - return ys.max() - - def _plot_point_histogram(self, ax, chains_groups, parameter, flip=False): # pragma: no cover - max_val = 0 - for chains in chains_groups: - if len(chains) < 10: # You probably dont want a contour if you only have a small group - continue # And even if you do, it'll be so inaccurate... - res = self.parent.analysis.get_max_posteriors(parameters=parameter, chains=chains, squeeze=False) - xs = [r[parameter] for r in res if r is not None] - colour = chains[0].config["color"] - num_bins = int(max(5, np.power(len(xs), 0.4))) - hist, bin_edges = np.histogram(xs, bins=num_bins, density=True) - if hist.max() > max_val: - max_val = hist.max() - if flip: - orientation = "horizontal" - else: - orientation = "vertical" - - bin_center = 0.5 * (bin_edges[:-1] + bin_edges[1:]) - xs, ys = bin_center, hist - ax.hist(xs, weights=ys, bins=bin_edges, histtype="step", color=colour, orientation=orientation) - return max_val - - def _plot_walk(self, ax, parameter, data, truth=None, extents=None, convolve=None, color=None, log_scale=False): # pragma: no cover - if extents is not None: - ax.set_ylim(extents) - assert convolve is None or isinstance(convolve, int), "Convolve must be an integer pixel window width" - x = np.arange(data.size) - ax.set_xlim(0, x[-1]) - ax.set_ylabel(parameter) - if color is None: - color = "#0345A1" - ax.scatter(x, data, c=color, s=2, marker=".", edgecolors="none", alpha=0.5) - max_ticks = self.parent.config["max_ticks"] - if log_scale: - ax.set_yscale("log") - ax.yaxis.set_major_locator(LogLocator(numticks=max_ticks)) - else: - ax.yaxis.set_major_locator(MaxNLocator(max_ticks, prune="lower")) - - if convolve is not None: - color2 = self.parent.color_finder.scale_colour(color, 0.5) - filt = np.ones(convolve) / convolve - filtered = np.convolve(data, filt, mode="same") - ax.plot(x[:-1], filtered[:-1], ls=":", color=color2, alpha=1) - - def _plot_walk_truth(self, ax, truth): - ax.axhline(truth, **self.parent.config_truth) - - def _convert_to_stdev(self, sigma): # pragma: no cover - # From astroML - shape = sigma.shape - sigma = sigma.ravel() - i_sort = np.argsort(sigma)[::-1] - i_unsort = np.argsort(i_sort) - - sigma_cumsum = 1.0 * sigma[i_sort].cumsum() - sigma_cumsum /= sigma_cumsum[-1] - - return sigma_cumsum[i_unsort].reshape(shape) - - def _scale_colours(self, colour, num, shade_gradient): # pragma: no cover - # http://thadeusb.com/weblog/2010/10/10/python_scale_hex_color - minv, maxv = 1 - 0.1 * shade_gradient, 1 + 0.5 * shade_gradient - scales = np.logspace(np.log(minv), np.log(maxv), num) - colours = [self.parent.color_finder.scale_colour(colour, scale) for scale in scales] - return colours - - def _get_smoothed_histogram2d(self, chain, param1, param2): # pragma: no cover - # No test coverage here because - smooth = chain.config["smooth"] - bins = chain.config["bins"] - kde = chain.config["kde"] - - x = chain.get_data(param1) - y = chain.get_data(param2) - w = chain.weights - - if chain.grid: - binsx = get_grid_bins(x) - binsy = get_grid_bins(y) - hist, x_bins, y_bins = np.histogram2d(x, y, bins=[binsx, binsy], weights=w) - else: - binsx, smooth = get_smoothed_bins(smooth, bins, x, w, marginalised=False) - binsy, _ = get_smoothed_bins(smooth, bins, y, w, marginalised=False) - hist, x_bins, y_bins = np.histogram2d(x, y, bins=[binsx, binsy], weights=w) - - if chain.power is not None: - hist = hist ** chain.power - - x_centers = 0.5 * (x_bins[:-1] + x_bins[1:]) - y_centers = 0.5 * (y_bins[:-1] + y_bins[1:]) - - if kde: - nn = x_centers.size * 2 # Double samples for KDE because smooth - x_centers = np.linspace(x_bins.min(), x_bins.max(), nn) - y_centers = np.linspace(y_bins.min(), y_bins.max(), nn) - xx, yy = meshgrid(x_centers, y_centers, indexing="ij") - coords = np.vstack((xx.flatten(), yy.flatten())).T - data = np.vstack((x, y)).T - hist = MegKDE(data, w, kde).evaluate(coords).reshape((nn, nn)) - if chain.power is not None: - hist = hist ** chain.power - elif smooth: - hist = gaussian_filter(hist, smooth, mode=self.parent._gauss_mode) - - return hist, x_centers, y_centers diff --git a/conda/deploy_anaconda.sh b/conda/deploy_anaconda.sh deleted file mode 100644 index 0c82dd77..00000000 --- a/conda/deploy_anaconda.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash -cd "${0%/*}" - -PKG_NAME=chainconsumer -USER=samreay - -# Removing this after testing. But now dont have to play with tags -TRAVIS_TAG=${TRAVIS_TAG:-v0.25.2} - -echo "Current tag is $TRAVIS_TAG" - -export CONDA_BLD_PATH=~/conda-bld -export VERSION=${TRAVIS_TAG#?} - -echo "Version building is $VERSION" - -mkdir ${CONDA_BLD_PATH} -conda config --set anaconda_upload no - -conda build --python 2.7 . -conda build --python 3.3 . -conda build --python 3.4 . -conda build --python 3.5 . -conda build --python 3.6 . -conda build --python 3.7 . -conda build --python 3.8 . -conda convert --platform all $CONDA_BLD_PATH/linux-64/$PKG_NAME-*.tar.bz2 -o $CONDA_BLD_PATH -anaconda -t $CONDA_UPLOAD_TOKEN upload -u $USER $CONDA_BLD_PATH/**/$PKG_NAME-*.tar.bz2 --force \ No newline at end of file diff --git a/conda/meta.yaml b/conda/meta.yaml deleted file mode 100644 index f93e3797..00000000 --- a/conda/meta.yaml +++ /dev/null @@ -1,41 +0,0 @@ - -package: - name: 'chainconsumer' - version: "{{ environ['VERSION'] }}" - -source: - git_url: https://github.com/samreay/chainconsumer.git - -build: - number: 0 - script: python setup.py install --single-version-externally-managed --record=record.txt - script_env: - - VERSION - - CONDA_BLD_PATH - -requirements: - host: - - python - - setuptools - - numpy - - scipy - - matplotlib >1.6.0,!=2.1.*,!=2.2.* - - statsmodels >=0.7.0 - run: - - python - - numpy - - scipy - - matplotlib >1.6.0,!=2.1.*,!=2.2.* - - statsmodels >=0.7.0 - - -about: - home: http://github.com/samreay/ChainConsumer - license: MIT License - license_family: MIT - license_file: '' - summary: Consume chains and produce plots and tables - description: 'Package documentation: http://samreay.github.io/ChainConsumer' - doc_url: 'https://samreay.github.io/ChainConsumer/' - dev_url: 'http://github.com/samreay/ChainConsumer' - diff --git a/deploy.sh b/deploy.sh deleted file mode 100644 index 13ffd6af..00000000 --- a/deploy.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash -if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "master" ]; then - echo "Not on master branch, or pull request. Not building doco" - echo "$TRAVIS_PULL_REQUEST" - echo "$TRAVIS_BRANCH" - exit 0; -fi -if [ -n "$GITHUB_API_KEY2" ]; then - echo "Github key found. Building documentation." - cd "$TRAVIS_BUILD_DIR"/doc - make clean - make html - make html - if [ "$TRAVIS_PYTHON_VERSION" == "3.7" ]; then - cd "$TRAVIS_BUILD_DIR" - rm -rf .git/ - cd doc/out/html - git config --global user.email "travis" - git config --global user.name "travis" - touch .nojekyll - git init - git add . - echo "Committing" - git commit -m init - # Make sure to make the output quiet, or else the API token will leak! - # This works because the API key can replace your password. - echo "Pushing" - git push -f -q "https://${GITHUB_API_KEY2}@${GH_REF}" master:gh-pages > /dev/null 2>&1 && echo "Pushed" - fi -fi -echo "Deploy script ending" \ No newline at end of file diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index f96a19f5..00000000 --- a/doc/Makefile +++ /dev/null @@ -1,178 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = out - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - rm -rf api/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/sep.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/sep.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/sep" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/sep" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." \ No newline at end of file diff --git a/doc/_static/theme_override.css b/doc/_static/theme_override.css deleted file mode 100644 index 822d11cc..00000000 --- a/doc/_static/theme_override.css +++ /dev/null @@ -1,67 +0,0 @@ -.wy-nav-top { - background-color: #208ae0 !important; - background: linear-gradient(to bottom, #1a7ccc 0%,#4ba3ea 100%); -} -.wy-side-nav-search { - background-color: #208ae0 !important; - background: linear-gradient(to bottom, #1a7ccc 0%,#4ba3ea 100%); -} - -.wy-menu-vertical a { - line-height: 1.7; - font-size: 100%; -} - -li>strong { - font-weight: bold; - color: #208ae0; - font-size: 1.1em; - padding-right: 0.5em; -} - -.field-body ul li { - padding-bottom: 0.5em; -} - -.wy-nav-side { - box-shadow: 0px 0px 15px 0px #444444; -} - -table.borderless { - border: none !important; -} - -table.borderless td { - background-color: transparent !important; - border: none !important; - border-bottom: none !important; - border-top: none !important; - border-right: none !important; -} - -.bounding img { - padding: 5px; -} - -table.borderless p { - text-align: center; -} - -.wy-table-responsive { - overflow: auto !important; -} - -.wy-nav-content { - max-width: 1000px !important; -} - -.rst-content dl.class > dt { - font-size: 1.2em; - margin-top: 30px !important; - margin-bottom: 30px; - width: 100%; -} - -.rst-content dl.method > dt { - width: 100%; -} \ No newline at end of file diff --git a/doc/build.bat b/doc/build.bat deleted file mode 100644 index d67126a9..00000000 --- a/doc/build.bat +++ /dev/null @@ -1,2 +0,0 @@ -pip uninstall -y chainconsumer -cd .. && python setup.py install && cd doc && make clean && make rst && make htmlfull \ No newline at end of file diff --git a/doc/chain_api.rst b/doc/chain_api.rst deleted file mode 100644 index 9dd26ecd..00000000 --- a/doc/chain_api.rst +++ /dev/null @@ -1,115 +0,0 @@ - -.. _chain_api: - -================== -Chain Consumer API -================== - -ChainConsumer has a number of different methods that can be access. In the latest version -of ChainConsumer, the increasing number of methods has had them put into smaller classes within -ChainConsumer. - -Basic Methods -------------- - -The methods found in the ChainConsumer class itself all relate to add, manipulating, and configuring -the chains fed in. - -* :func:`chainconsumer.ChainConsumer.add_chain` - Add a chain! -* :func:`chainconsumer.ChainConsumer.add_marker` - Add a marker! -* :func:`chainconsumer.ChainConsumer.add_covariance` - Add a Gaussian to the mix. -* :func:`chainconsumer.ChainConsumer.divide_chain` - Split a chain into multiple chains to inspect each walk. -* :func:`chainconsumer.ChainConsumer.remove_chain` - Remove a chain. -* :func:`chainconsumer.ChainConsumer.configure` - Configure ChainConsumer. -* :func:`chainconsumer.ChainConsumer.configure_truth` - Configure how truth values are plotted. - -Plotter Class -------------- - -The plotter class, accessible via `chainConsumer.plotter` contains the methods -used for generating plots. - - -* :func:`chainconsumer.plotter.Plotter.plot` - Plot the posterior surfaces -* :func:`chainconsumer.plotter.Plotter.plot_walks` - Plot the walks to visually inspect convergence. -* :func:`chainconsumer.plotter.Plotter.plot_distributions` - Plot the marginalised distributions only. -* :func:`chainconsumer.plotter.Plotter.plot_summary` - Plot the marginalised distributions only. -* :func:`chainconsumer.plotter.Plotter.plot_contour` - Pass in an axis for a contour plot on an external figure. - -Analysis Class --------------- - -The plotter class, accessible via `chainConsumer.analysis` contains the methods -used for getting data or LaTeX analysis of the chains fed in. - -* :func:`chainconsumer.analysis.Analysis.get_latex_table` - Return a LaTeX table of the parameter summaries. -* :func:`chainconsumer.analysis.Analysis.get_parameter_text` - Return LaTeX text for specified parameter bounds. -* :func:`chainconsumer.analysis.Analysis.get_summary` - Get the parameter bounds for your chains. -* :func:`chainconsumer.analysis.Analysis.get_max_posteriors` - Get the parameters for the point with greatest posterior. -* :func:`chainconsumer.analysis.Analysis.get_correlations` - Get the parameters and correlation matrix for a chain. -* :func:`chainconsumer.analysis.Analysis.get_correlation_table` - Get a chain's correlation matrix as a LaTeX table. -* :func:`chainconsumer.analysis.Analysis.get_covariance` - Get the parameters and covariance matrix for a chain. -* :func:`chainconsumer.analysis.Analysis.get_covariance_table` - Get a chain's covariance matrix as a LaTeX table. - - - -Diagnostic Class ----------------- - -The plotter class, accessible via `chainConsumer.diagnostic` contains the methods -used for checking chain convergence - -* :func:`chainconsumer.diagnostic.Diagnostic.gelman_rubin` - Run the Gelman-Rubin statistic on your chains. -* :func:`chainconsumer.diagnostic.Diagnostic.geweke` - Run the Geweke statistic on your chains. - -Model Comparison Class ----------------------- - - -The plotter class, accessible via `chainConsumer.comparison` contains the methods -used for comparing the chains from various models. - -* :func:`chainconsumer.comparisons.Comparison.comparison.aic` - Return the AICc values for all chains. -* :func:`chainconsumer.comparisons.Comparison.comparison.bic` - Return the BIC values for all chains. -* :func:`chainconsumer.comparisons.Comparison.comparison.dic` - Return the DIC values for all chains. -* :func:`chainconsumer.comparisons.Comparison.comparison.comparison_table` - Return a LaTeX table comparing models as per the above methods. - - -The full documentation can be found below. - -Full Documentation ------------------- - -.. autoclass:: chainconsumer.ChainConsumer - :members: - - ------- - - -.. autoclass:: chainconsumer.analysis.Analysis - :members: - - ------- - - -.. autoclass:: chainconsumer.comparisons.Comparison - :members: - - ------- - - -.. autoclass:: chainconsumer.diagnostic.Diagnostic - :members: - - ------- - - -.. autoclass:: chainconsumer.plotter.Plotter - :members: - - ------- diff --git a/doc/conf.py b/doc/conf.py deleted file mode 100644 index 2e99c899..00000000 --- a/doc/conf.py +++ /dev/null @@ -1,290 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -import os -import re -import sphinx_rtd_theme - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) -#sys.path.append(os.path.abspath('ext')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# intersphinx_mapping = { -# 'python': ('http://docs.python.org/', None), -# 'numpy': ('http://docs.scipy.org/doc/numpy/', None), -# 'scipy': ('http://docs.scipy.org/doc/scipy/reference/', None)} - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.intersphinx', - 'sphinx.ext.mathjax', - "sphinx.ext.napoleon", - # 'numpydoc', - 'sphinx_gallery.gen_gallery' -] -numpydoc_show_class_members = False -autosummary_generate = True -autoclass_content = "class" -autodoc_default_flags = ["members", "no-special-members"] -sphinx_gallery_conf = { - 'filename_pattern': 'plot_', - 'examples_dirs': '../examples', # path to examples scripts - 'gallery_dirs': 'examples', # path to gallery generated examples -} -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -version = re.findall(r"__version__ = \"(.*?)\"", open("../chainconsumer/chainconsumer.py").read())[0] - -project = u'ChainConsumer' -copyright = u'2016-2017, Samuel Hinton and contributors' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '.'.join(version.split('.')[0:2]) - -# The full version, including alpha/beta/rc tags. -release = version - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -default_role = 'obj' - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -html_theme = "sphinx_rtd_theme" -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - - -def setup(app): - app.add_stylesheet('theme_override.css') - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -#html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'ChainConsumerDoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'chainConsumer.tex', u'ChainConsumer Documentation', - u'Samuel Hinton', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'ChainConsumer', u'ChainConsumer Documentation', - [u'Samuel Hinton'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'ChainConsumer', u'ChainConsumer Documentation', - u'Samuel Hinton', 'ChainConsumer', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False \ No newline at end of file diff --git a/doc/index.rst b/doc/index.rst deleted file mode 100644 index c84b8e99..00000000 --- a/doc/index.rst +++ /dev/null @@ -1,107 +0,0 @@ -============= -ChainConsumer -============= - -ChainConsumer is a python package designed to do one thing - consume the chains -output from Monte Carlo processes like MCMC. ChainConsumer can utilise these chains -to produce plots of the posterior surface inferred from the chain distributions, -to plot the chains as walks (to check for mixing and convergence), and to output -parameter summaries in the form of LaTeX tables. On top of all of this, -if you have multiple models (chains), you can load them all in and perform some -model comparison using AIC, BIC or DIC metrics. - -To get things started, here is a basic example: - -.. code-block:: python - - import numpy as np - from chainconsumer import ChainConsumer - - mean = [0.0, 4.0] - data = np.random.multivariate_normal(mean, [[1.0, 0.7], [0.7, 1.5]], size=100000) - - c = ChainConsumer() - c.add_chain(data, parameters=["$x_1$", "$x_2$"]) - c.plotter.plot(filename="example.png", figsize="column", truth=mean) - - -The output figure is displayed below. - -.. figure:: ../paper/example.png - :align: center - :width: 80% - -Or you can add more models and look at the summaries between them. Or a ton more, check the examples! - -.. figure:: ../examples/resources/summary.png - :align: center - :width: 60% - -Check out the API and far more :ref:`examples-index` below: - -Contents --------- - -.. toctree:: - :maxdepth: 2 - - usage - examples/index - chain_api - -Installation ------------- - -ChainConsumer requires the following dependencies, along with a LaTeX installation and `dvipng` (a maptlotlib dependency):: - - numpy - scipy - matplotlib - statsmodels - -ChainConsumer can be installed as follows:: - - pip install chainconsumer - -Common Issues -------------- - -Users on some Linux platforms have reported issues rendering plots using ChainConsumer. -The common error states that `dvipng: not found`, and as per `StackOverflow `_ -post, it can be solved by explicitly install the `matplotlib` dependency `dvipng` via `sudo apt-get install dvipng`. - -If you are running on HPC or clusters where you can't install things yourself, -users may run into issues where LaTeX or other optional dependencies aren't installed. -In this case, ensure `usetex=False` in `configure` to request matplotlib not try to use TeX. -If this does not work, also set `serif=False`, which has helped some uses. - - - -Citing ------- - -You can cite ChainConsumer using the following BibTeX:: - - @ARTICLE{Hinton2016, - author = {{Hinton}, S.~R.}, - title = "{ChainConsumer}", - journal = {The Journal of Open Source Software}, - year = 2016, - month = aug, - volume = 1, - eid = {00045}, - pages = {00045}, - doi = {10.21105/joss.00045}, - adsurl = {http://adsabs.harvard.edu/abs/2016JOSS....1...45H}, - } - -Contributing ------------- - -Users that wish to contribute to this project may do so in a number of ways. -Firstly, for any feature requests, bugs or general ideas, please raise an issue -via `Github `_. - -If you wish to contribute code to the project, please simple fork the project on -Github and then raise a pull request. Pull requests will be reviewed to determine -whether the changes are major or minor in nature, and to ensure all changes are tested. \ No newline at end of file diff --git a/doc/make.bat b/doc/make.bat deleted file mode 100644 index a6a1eadc..00000000 --- a/doc/make.bat +++ /dev/null @@ -1,285 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=out -set BUILDDIR2=examples -set BUILDDIR3=modules -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - for /d %%i in (%BUILDDIR2%\*) do rmdir /q /s %%i - for /d %%i in (%BUILDDIR3%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - del /q /s %BUILDDIR2%\* - del /q /s %BUILDDIR3%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 1>NUL 2>NUL -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "htmlfull" ( - %SPHINXBUILD% -E -a -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "rst" ( - sphinx-apidoc -fM -o . ../chainconsumer - if errorlevel 1 exit /b 1 - echo. - echo.Made rst - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\dessn.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\dessn.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/doc/usage.rst b/doc/usage.rst deleted file mode 100644 index bcc3d852..00000000 --- a/doc/usage.rst +++ /dev/null @@ -1,63 +0,0 @@ -===== -Usage -===== - -I recommend going straight to the :ref:`chain_api` and -the :ref:`examples-index` page for details on how to use ChainConsumer. - -The Process ------------ - -The process of using ChainConsumer should be straightforward: - -1. Create an instance of ChainConsumer. -2. Add your chains to this instance. -3. Run convergence diagnostics, if desired. -4. Update the configurations if needed (make sure you do this *after* loading in the data). -5. Plot. - -The main page and the examples page has code demonstrating these, -so I won't repeat it here. - - - - -Statistics ----------- - -An area of some difference in analyses is how to generate summary statistics -from marginalised posterior distributions. ChainConsumer comes equipped -with the several different methods that can be configured with the -`configure` method. The three methods are: - -Maximum Likelihood Statistics - The default statistic used by ChainConsumer, maximum likelihood statistics - report asymmetric uncertainty, from the point of maximum likelihood to the - iso-likelihood levels above and below the maximal point. -Cumulative Statistics - For cumulative statistics , the lower :math:`1\sigma` confidence bound, mean value, - and upper bound are respectively given by the value of the cumulative function - at points :math:`C(x) = 0.15865`, :math:`0.5`, and :math:`0.84135`. -Mean Statistics - Mean statistics report the same upper and lower confidence bound as cumulative - statistics, however report symmetric error bars by having the primary statistic - reported as the mean of the lower and upper bound. -Max Symmetric - See `Figure 6(1) of Andrae (2010) `_. Maximum - likelihood with error with symmetric errors to get the desired confidence interval. -Max Shortest - See `Figure 6(2) of Andrae (2010) `_. Maximum - likelihood with uncertainty bounds that minimise the distance between bounds. -Max Central - See `Figure 6(3) of Andrae (2010) `_. Maximum - likelihood with uncertainty bounds from the CDF (i.e. same as cumulative stats - but the central point is the maximum likelihood point and not the :math:`x` such that - :math:`C(x)=0.5`. - - -All three methods are illustrated below. - -.. figure:: ../examples/resources/stats.png - :align: center - :width: 80% - diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 00000000..993fe281 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,14 @@ + +The ChainConsumer acts as manager and state holder, to which you supply configured pydantic objects to dictate the behaviour of your plots and analyses. + +***** + +::: chainconsumer.chainconsumer.ChainConsumer + +::: chainconsumer.chain.Chain + +::: chainconsumer.chain.ChainConfig + +::: chainconsumer.truth.Truth + + diff --git a/docs/examples/README.md b/docs/examples/README.md new file mode 100644 index 00000000..cf457a20 --- /dev/null +++ b/docs/examples/README.md @@ -0,0 +1,3 @@ +# Examples + +All the basics laid out in a few short examples. \ No newline at end of file diff --git a/docs/examples/advanced_examples/README.md b/docs/examples/advanced_examples/README.md new file mode 100644 index 00000000..ec68baf6 --- /dev/null +++ b/docs/examples/advanced_examples/README.md @@ -0,0 +1 @@ +# Specific Examples \ No newline at end of file diff --git a/docs/examples/advanced_examples/plot_0_grid.py b/docs/examples/advanced_examples/plot_0_grid.py new file mode 100644 index 00000000..a58697b8 --- /dev/null +++ b/docs/examples/advanced_examples/plot_0_grid.py @@ -0,0 +1,30 @@ +""" +# Plotting Grid Data + + +If you don't have Monte Carlo chains, and have grid evaluations instead, that's fine too! + +Just flatten your grid, set the weights to the grid evaluation, and set the grid flag. Here is +a nice diamond that you get from modifying a simple multivariate normal distribution. + +Note that by default, grid data is not smoothed, though you can explicitly set the smooth +parameter in ``configure_general`` if you do want smoothing. + +Note that you *cannot* use dictionary input with the grid method and not specify the full +flattened array. This is because we cannot construct the meshgrid from a dictionary, as +the order of the parameters is not preserved in the dictionary. + +""" +import numpy as np +import pandas as pd + +from chainconsumer import Chain, ChainConsumer + +x, y = np.linspace(-3, 3, 50), np.linspace(-7, 7, 100) +xx, yy = np.meshgrid(x, y, indexing="ij") +likelihood = np.exp(-0.5 * (xx * xx + yy * yy / 4 + np.abs(xx * yy))) +df = pd.DataFrame({"x": xx.flatten(), "y": yy.flatten(), "weight": likelihood.flatten()}) + +c = ChainConsumer() +c.add_chain(Chain(samples=df, grid=True, name="Grid Data")) +fig = c.plotter.plot() diff --git a/docs/examples/advanced_examples/plot_1_blinding.py b/docs/examples/advanced_examples/plot_1_blinding.py new file mode 100644 index 00000000..b03cbdef --- /dev/null +++ b/docs/examples/advanced_examples/plot_1_blinding.py @@ -0,0 +1,31 @@ +""" +# Blinding Plots + + +You can blind parameters and not show axis labels very easily! + +Just give ChainConsumer the `blind` parameter when plotting. You can specify `True` to blind all parameters, +or give it a string (or list of strings) detailing the specific parameters you want blinded! + +""" +from chainconsumer import Chain, ChainConsumer, PlotConfig, make_sample + +df = make_sample(num_dimensions=4, seed=1) +c = ChainConsumer() +c.add_chain(Chain(samples=df, name="Blind Me!")) +c.set_plot_config(PlotConfig(blind=["A", "B"])) +fig = c.plotter.plot() + +# %% +# Notice the blinding applies to all plots +fig = c.plotter.plot_summary() + +# %% +fig = c.plotter.plot_walks() + +# %% + +fig = c.plotter.plot_distributions() +# %% +# And the LaTeX output +print(c.analysis.get_latex_table()) diff --git a/docs/examples/advanced_examples/plot_2_kde.py b/docs/examples/advanced_examples/plot_2_kde.py new file mode 100644 index 00000000..6e427122 --- /dev/null +++ b/docs/examples/advanced_examples/plot_2_kde.py @@ -0,0 +1,26 @@ +""" +# KDE + + +I don't recommend using KDEs in general, as its very easy to have +them inflate your contours if the bandpass isn't tuned well, and +its hard to see when it's too large. + +Always run more samples if you can, instead of covering up +a lack of data with some extra smoothing. + +But, if there's no other way, here's how you can do it. + +Notice how the KDE, unless its perfectly matched to your distribution, +increses the width of the marginal distributions. + +""" +from chainconsumer import Chain, ChainConsumer, PlotConfig, make_sample + +df = make_sample(num_dimensions=2, seed=3, num_points=1000) +c = ChainConsumer() +c.add_chain(Chain(samples=df, name="No KDE")) +c.add_chain(Chain(samples=df + 1, name="KDE", kde=1.0)) +c.add_chain(Chain(samples=df + 2, name="KDE that's too large", kde=2.0)) +c.set_plot_config(PlotConfig(flip=True)) +fig = c.plotter.plot() diff --git a/docs/examples/advanced_examples/plot_3_divide_chains.py b/docs/examples/advanced_examples/plot_3_divide_chains.py new file mode 100644 index 00000000..c4d9b4d8 --- /dev/null +++ b/docs/examples/advanced_examples/plot_3_divide_chains.py @@ -0,0 +1,24 @@ +""" +# Dividing Chains + +It's common with algorithms like MH and other random walkers to have +multiple walkers each providing their own chains. Typically, you +want to verify each walker is burnt in, and then you put all of their +samples into one chain. + +But, if your final samples are made up for four walkers each contributing +10k samples, you may want to inspect each walker's surface individually. + +In this toy example, all the chains are from the same random generator, +so they're on top of each other. Except MCMC chains to not be as perfect. +""" +from chainconsumer import Chain, ChainConsumer, PlotConfig, make_sample + +df = make_sample(num_dimensions=2, seed=3, num_points=40000) + +c = ChainConsumer() +combined = Chain(samples=df, name="Model", walkers=4) +for chain in combined.divide(): + c.add_chain(chain) +c.set_plot_config(PlotConfig(flip=True)) +fig = c.plotter.plot() diff --git a/docs/examples/advanced_examples/plot_4_misc_chain_visuals.py b/docs/examples/advanced_examples/plot_4_misc_chain_visuals.py new file mode 100644 index 00000000..bdf859bf --- /dev/null +++ b/docs/examples/advanced_examples/plot_4_misc_chain_visuals.py @@ -0,0 +1,96 @@ +""" +# Miscellanous Visual Options + +Rather than having one example for each option, let's condense things. +""" +# %% +# # Shade Gradient +# +# Pretty simple - it controls how much visual difference there is in your contours. +import numpy as np + +from chainconsumer import Chain, ChainConfig, ChainConsumer, PlotConfig, make_sample + +df1 = make_sample(num_dimensions=2, seed=3) - 1 +df2 = make_sample(num_dimensions=2, seed=1) + 2 + +c = ChainConsumer() +c.add_chain(Chain(samples=df1, name="High Contrast", color="emerald", shade_gradient=2.0)) +c.add_chain(Chain(samples=df2, name="Low Contrast", color="sky", shade_gradient=0.2)) + +c.set_plot_config(PlotConfig(flip=True)) +fig = c.plotter.plot() + +# %% +# # Shade Alpha +# +# Controls how opaque the contours are. Like everything else, you +# can specify this when making the chain, or apply a single override +# to everything like so. +c.set_override(ChainConfig(shade_alpha=0.1)) +fig = c.plotter.plot() + +# %% +# # Contour Labels +# +# Add labels to contours. I used to have this configurable to be either +# sigma levels or percentages, but there was confusion over the 1D vs 2D sigma levels, +# in that $1\sigma$ in a 2D gaussian is *not* 68% of the volume. So now we just +# do percentages. +c.set_override(ChainConfig(show_contour_labels=True)) +fig = c.plotter.plot() + +# %% +# # Linestyles and widths +# +# Fairly simple to do. To show different ones, I'll remake the chains, +# rather than having a single override. Note you *could* try something +# like `chain.line_width = 5`, but this is a sneaky override, and it +# won't be registered in the internal "You set this attribute and didn't +# use the default when you made the chain, so don't screw with it." +# +# Nothing *does* screw with line width, or similar, but it's a good habit. +c2 = ChainConsumer() +c2.add_chain(Chain(samples=df1, name="Thin dots", color="emerald", linestyle=":", linewidth=0.5)) +c2.add_chain(Chain(samples=df2, name="Thick dashes", color="sky", linestyle="--", linewidth=2.0)) +fig = c2.plotter.plot() + +# %% +# # Marker styles and sizes +# +# Provided you have a posterior column, you can plot the maximum probability point. + +c.set_override(ChainConfig(plot_point=True, marker_style="P", marker_size=100)) +fig = c.plotter.plot() + +# %% +# # Cloud and Sigma Levels +# +# Choose custom sigma levels and display point cloud. +c.set_override( + ChainConfig( + shade_alpha=1.0, + sigmas=np.linspace(0, 1, 10).tolist(), + shade_gradient=1.0, + plot_cloud=True, + ) +) +fig = c.plotter.plot() + +# %% +# # Smoothing (or not) +# +# The histograms behind the scene in ChainConsumer are smoothed. But you can turn this off. +# The higher the smoothing vaule, the more subidivisions of your bins there will be. +c.set_override(ChainConfig(smooth=0)) +fig = c.plotter.plot() + +# %% +# But changing the smoothing doesn't change the number of bins. That's separate. +c.set_override(ChainConfig(smooth=0, bins=10)) +fig = c.plotter.plot() + +# %% +# It's beautiful. And it's hard to find a nice balance. +c.set_override(ChainConfig(smooth=0, bins=100)) +fig = c.plotter.plot() diff --git a/docs/examples/plot_0_contours.py b/docs/examples/plot_0_contours.py new file mode 100644 index 00000000..118aa6bf --- /dev/null +++ b/docs/examples/plot_0_contours.py @@ -0,0 +1,111 @@ +""" +# Introduction to Contours + +At the most basic, we take a contour as a pandas DataFrame and let ChainConsumer +handle the defaults and display. + +""" +from chainconsumer import Chain, ChainConfig, ChainConsumer, PlotConfig, Truth, make_sample + +# Here's what you might start with +df = make_sample(num_dimensions=2, seed=1) +print(df.head()) + +# %% New cell + +# And now we give this to chainconsumer +c = ChainConsumer() +c.add_chain(Chain(samples=df, name="An Example Contour")) +fig = c.plotter.plot() + +# %% Second cell +# +# If we wanted to customise the chain, that's easy enough to do. + +# Here's a convenience function for you +chain2 = Chain.from_covariance( + [3.0, 1.0], + [[1.0, -0.7], [-0.7, 1.5]], + columns=["A", "B"], + name="Another contour!", + color="#065f46", + linestyle=":", +) +c.add_chain(chain2) +fig = c.plotter.plot() + +# %% Third cell +# # Customising Chains +# +# There's a lot you can configure using chains, and to make it easy, Chains are defined as pydantic +# base models so you can easily see the default and values you can pass in. Don't worry, there will be +# plenty of very specific examples in a sub gallery you can check out, but as a final one for here, +# let's add markers and truth values. + +c.add_marker(location={"A": 0, "B": 2}, name="A point", color="orange", marker_style="P", marker_size=50) +c.add_truth(Truth(location={"A": 0, "B": 1})) +fig = c.plotter.plot() + + +# %% Fourth cell +# # Weights and Posteriors +# +# If you provide the log posteriors in the chain, you can ask for the maximum probability point +# to be plotted as well. Similarly, if you have samples with non-uniform weights, you can +# specify the weights column as well. +# +# To keep this clean, let's remake everything. I'm going to add an extra few columns into our +# dataframe. You'll see what they do + +df2 = df.assign(C=lambda x: x["A"] + x["B"]) + +c = ChainConsumer() +# Customise the chain when you add it +chain = Chain( + samples=df2, + name="Example", + color="k", + plot_point=True, + plot_cloud=True, + marker_style="*", + marker_size=100, + num_cloud=30000, + shade=False, + linewidth=2.0, + cmap="magma", + show_contour_labels=True, + color_param="C", +) +c.add_chain(chain) +# You can also override *all* chains at once like so +# Notice that Chain is a child of ChainConfig +# So you could override base properties like line weights... but not samples +c.set_override(ChainConfig(sigmas=[0, 1, 2, 3])) +c.add_truth(Truth(location={"A": 0, "B": 1}, color="#500724")) + +# And if we want to change the plot itself in some way, we can do that via +c.set_plot_config( + PlotConfig( + flip=True, + labels={"A": "$A$", "B": "$B$", "C": r"$\alpha^2$"}, + contour_label_font_size=12, + ) +) +fig = c.plotter.plot() + +# %% Fifth cell +# Here the maximum posterior value is used to plot the star-shaped +# point. A truth line is added in dark red, and instead of plotting +# $x_3$ as another contour, we use it to provide coloured scatter plots. +# This is useful if the parameter isn't constrained and has some dependency. +# +# I have used this in the past to show how adding different priors to Hubble's +# constant (as the color parameter) would pull our fits in different directions. +# +# Now, I am expecting a few comments like: "But Sam, it was easier to do everything +# in one line before, instead of all these objects." +# +# Having everything as kwargs being passed through made the code a nightmare, +# type hints impossible, and extensibility poor. By trying to separate +# things out into appropriate dataclasses, I am hoping it is a lot easier +# for people to contribute to this code base in the future. diff --git a/docs/examples/plot_1_summary.py b/docs/examples/plot_1_summary.py new file mode 100644 index 00000000..35e84b87 --- /dev/null +++ b/docs/examples/plot_1_summary.py @@ -0,0 +1,47 @@ +""" +# Introduction to Summaries + +When you have a few chains and want to contrast them all with +each other, you probably want a summary plot. + +To show you how they work, let's make some sample data that all +has the same average. +""" +from chainconsumer import Chain, ChainConfig, ChainConsumer, PlotConfig, Truth, make_sample + +# Here's what you might start with +df_1 = make_sample(num_dimensions=4, seed=1) +df_2 = make_sample(num_dimensions=5, seed=2) +print(df_1.head()) + +# %% New cell +## Using distributions + + +# And now we give this to chainconsumer +c = ChainConsumer() +c.add_chain(Chain(samples=df_1, name="An Example Contour")) +c.add_chain(Chain(samples=df_2, name="A Different Contour")) +fig = c.plotter.plot_summary() + +# %% New cell +# ## Using Errorbars +# +# Note that because the errorbar kwarg is specific to this function +# it is not part of the `PlotConfig` class. + +fig = c.plotter.plot_summary(errorbar=True) + +# %% New cell +# The other features of ChainConsumer should work with summaries too. +# +# For example, truth values should work just fine. + +c.add_truth(Truth(location={"A": 0, "B": 1}, line_style=":", color="red")) +fig = c.plotter.plot_summary(errorbar=True, vertical_spacing_ratio=2.0) + +# %% New cell +# And similarly, our overrides are generic and so effect this method too. +c.set_override(ChainConfig(bar_shade=False)) +c.set_plot_config(PlotConfig(watermark="Preliminary", blind=["E"])) +fig = c.plotter.plot_summary() diff --git a/docs/examples/plot_2_textual_output.py b/docs/examples/plot_2_textual_output.py new file mode 100644 index 00000000..8606c70e --- /dev/null +++ b/docs/examples/plot_2_textual_output.py @@ -0,0 +1,59 @@ +""" +# Introduction to LaTeX Tables + +Because typing those things out is a **massive pain in the ass.** + +""" +from chainconsumer import Chain, ChainConsumer, Truth, make_sample + +# Here's a sample dataset +n_1, n_2 = 100000, 200000 +df_1 = make_sample(num_dimensions=2, seed=0, num_points=n_1) +df_2 = make_sample(num_dimensions=2, seed=1, num_points=n_2) + + +# Here's what the plot looks like: +c = ChainConsumer() +c.add_chain(Chain(samples=df_1, name="Model A", num_free_params=1, num_eff_data_points=n_1)) +c.add_chain(Chain(samples=df_2, name="Model B", num_free_params=2, num_eff_data_points=n_2)) +c.add_truth(Truth(location={"A": 0, "B": 1})) +fig = c.plotter.plot() + +# %% Second cell +# # Comparing Models +# +# Provided you have the log posteriors, comparing models is easy. + +latex_table = c.comparison.comparison_table() +print(latex_table) + +# %% +# Granted, it's hard to read a LaTeX table. It'll come out something +# like this, though I took a screenshot a while ago and the data has changed. +# You get the idea though... +# +# ![](../../resources/comparison_table.png) +# +# # Summarising Parameters +# +# Alright, so what if you've compared models and you're happy and want +# to publish that paper! +# +# You can get a LaTeX table of the summary statistics as well. + +print(c.analysis.get_latex_table()) +# %% +# Which would look like this (though I saved this screenshot out a while ago too) +# +# ![](../../resources/summaries.png) +# +# And sometimes you might want this table transposed if you have a lot of +# parameters and not many models to compare. +# +print(c.analysis.get_latex_table(transpose=True, caption="The best table")) + +# %% +# +# There are other things you can do if you dig around in the API, like +# correlations and covariance. +print(c.analysis.get_covariance_table("Model A")) diff --git a/docs/examples/plot_3_distributions.py b/docs/examples/plot_3_distributions.py new file mode 100644 index 00000000..36ac4875 --- /dev/null +++ b/docs/examples/plot_3_distributions.py @@ -0,0 +1,35 @@ +""" +# Introduction to Distributions + +When you have a few chains and want to contrast them all with +each other, you probably want a summary plot. + +To show you how they work, let's make some sample data that all +has the same average. +""" +from chainconsumer import Chain, ChainConsumer, Truth, make_sample + +# Here's what you might start with +df_1 = make_sample(num_dimensions=4, seed=1, randomise_mean=True) +df_2 = make_sample(num_dimensions=5, seed=2, randomise_mean=True) +print(df_1.head()) + +# %% New cell +## Using distributions + + +# And now we give this to chainconsumer +c = ChainConsumer() +c.add_chain(Chain(samples=df_1, name="An Example Contour")) +fig = c.plotter.plot_distributions() + +# %% Second cell +# If you want the summary stats you'll need to keep it just one +# chain. And if you don't want them, you can pass `summarise=False` +# to the `PlotConfig`. +# +# When you add a second chain, you'll see the summaries disappear. + +c.add_chain(Chain(samples=df_2, name="Another contour!")) +c.add_truth(Truth(location={"A": 0, "B": 0})) +fig = c.plotter.plot_distributions(col_wrap=3, columns=["A", "B"]) diff --git a/docs/examples/plot_4_walks.py b/docs/examples/plot_4_walks.py new file mode 100644 index 00000000..d4d4bf75 --- /dev/null +++ b/docs/examples/plot_4_walks.py @@ -0,0 +1,23 @@ +""" +# Introduction to Walks + +Want to see if your chain is behaving nicely? Use a walk! +""" +from chainconsumer import Chain, ChainConsumer, Truth, make_sample + +# Here's a sample dataset +df_1 = make_sample(num_dimensions=4, seed=1, randomise_mean=True, num_points=10000) + +# And now we give this to chainconsumer +c = ChainConsumer() +c.add_chain(Chain(samples=df_1, name="An Example Contour")) +fig = c.plotter.plot_walks() + +# %% Second cell +# You can add other chains in if you want, though it can get messy to see things. +# +# To reduce the mess, try turning on convolve, which will +# get you a smoothed out version of the walk. And truth lines are always nice. + +c.add_truth(Truth(location={"A": 0, "B": 0})) +fig = c.plotter.plot_walks(convolve=100) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..3c66916e --- /dev/null +++ b/docs/index.md @@ -0,0 +1,66 @@ +# ChainConsumer + + +ChainConsumer is a python package designed to do one thing - consume the chains output from Monte Carlo processes like MCMC. ChainConsumer can utilise these chains to produce plots of the posterior surface inferred from the chain distributions, to plot the chains as walks (to check for mixing and convergence), and to output parameter summaries in the form of LaTeX tables. On top of all of this, if you have multiple models (chains), you can load them all in and perform some model comparison using AIC, BIC or DIC metrics. + +## Installation + +The latest version of ChainConsumer requires at least Python 3.10. If you have a version of `ChainConsumer` that is older (v0.34.0 or below) +you will find this documentation not very useful. + +`pip install chainconsumer` + +## Basic Example + +If you have some samples, analysing them should be straightforward: + +```python +from chainconsumer import Chain, ChainConsumer, make_sample + + +df = make_sample() +c = ChainConsumer() +c.add_chain(Chain(samples=df, name="An Example Contour")) +fig = c.plotter.plot() +``` + +![](resources/example.png) + + +## Common Issues + +Users on some Linux platforms have reported issues rendering plots using ChainConsumer. The common error states that `dvipng: not found`, and as per this [StackOverflow](http://stackoverflow.com/a/32915992/3339667) +post, it can be solved by explicitly installing the `matplotlib` dependency `dvipng` via `sudo apt-get install dvipng`. + +If you are running on HPC or clusters where you can't install things yourself, +users may run into issues where LaTeX or other optional dependencies aren't installed. In this case, ensure `usetex=False` in your `PlotConfig` (which is the default). If this does not work, also set `serif=False`, which has helped some uses. + +## Citing + + +You can cite ChainConsumer using the following BibTeX: + +```bash + @ARTICLE{Hinton2016, + author = {{Hinton}, S.~R.}, + title = "{ChainConsumer}", + journal = {The Journal of Open Source Software}, + year = 2016, + month = aug, + volume = 1, + eid = {00045}, + pages = {00045}, + doi = {10.21105/joss.00045}, + adsurl = {http://adsabs.harvard.edu/abs/2016JOSS....1...45H}, + } +``` + +## Contributing + + +Users that wish to contribute to this project may do so in a number of ways. +Firstly, for any feature requests, bugs or general ideas, please raise an issue via [Github](https://github.com/samreay/ChainConsumer/issues). + +If you wish to contribute code to the project, please simple fork the project on Github and then raise a pull request. Pull requests will be reviewed to determine whether the changes are major or minor in nature, and to ensure all changes are tested. + +After cloning down the project, run `make install` to install all dependencies and pre-commit hook. \ No newline at end of file diff --git a/docs/plugins/griffe_doclinks.py b/docs/plugins/griffe_doclinks.py new file mode 100644 index 00000000..b7bfc456 --- /dev/null +++ b/docs/plugins/griffe_doclinks.py @@ -0,0 +1,87 @@ +import ast +import re +from functools import partial +from pathlib import Path + +from griffe.dataclasses import Object as GriffeObject +from griffe.extensions import VisitorExtension +from pymdownx.slugs import slugify + +DOCS_PATH = Path(__file__).parent.parent +slugifier = slugify(case="lower") + + +def find_heading(content: str, slug: str, file_path: Path) -> tuple[str, int]: + for m in re.finditer("^#+ (.+)", content, flags=re.M): + heading = m.group(1) + h_slug = slugifier(heading, "-") + if h_slug == slug: + return heading, m.end() + raise ValueError(f"heading with slug {slug!r} not found in {file_path}") + + +def insert_at_top(path: str, api_link: str) -> str: + rel_file = path.rstrip("/") + ".md" + file_path = DOCS_PATH / rel_file + content = file_path.read_text() + second_heading = re.search("^#+ ", content, flags=re.M) + assert second_heading, "unable to find second heading in file" + first_section = content[: second_heading.start()] + + if f"[{api_link}]" not in first_section: + file_path.write_text('??? api "API Documentation"\n' f" [`{api_link}`][{api_link}]
\n\n" f"{content}") + + heading = file_path.stem.replace("_", " ").title() + return f'!!! abstract "Usage Documentation"\n [{heading}](../{rel_file})\n' + + +def replace_links(m: re.Match, *, api_link: str) -> str: + path_group = m.group(1) + if "#" not in path_group: + # no heading id, put the content at the top of the page + return insert_at_top(path_group, api_link) + + usage_path, slug = path_group.split("#", 1) + rel_file = usage_path.rstrip("/") + ".md" + file_path = DOCS_PATH / rel_file + content = file_path.read_text() + heading, heading_end = find_heading(content, slug, file_path) + + next_heading = re.search("^#+ ", content[heading_end:], flags=re.M) + if next_heading: # noqa: SIM108 + next_section = content[heading_end : heading_end + next_heading.start()] + else: + next_section = content[heading_end:] + + if f"[{api_link}]" not in next_section: + file_path.write_text( + f"{content[:heading_end]}\n\n" + '??? api "API Documentation"\n' + f" [`{api_link}`][{api_link}]
" + f"{content[heading_end:]}" + ) + + return f'!!! abstract "Usage Documentation"\n [{heading}](../{rel_file}#{slug})\n' + + +def update_docstring(obj: GriffeObject) -> str: + return re.sub( + r"usage[\- ]docs: ?https://docs\.pydantic\.dev/.+?/(\S+)", + partial(replace_links, api_link=obj.path), + obj.docstring.value, # type: ignore + flags=re.I, + ) + + +def update_docstrings_recursively(obj: GriffeObject) -> None: + if obj.docstring: + obj.docstring.value = update_docstring(obj) + for member in obj.members.values(): + if not member.is_alias: + update_docstrings_recursively(member) # type: ignore + + +class Extension(VisitorExtension): + def visit_module(self, node: ast.AST) -> None: + module = self.visitor.current.module + update_docstrings_recursively(module) diff --git a/docs/resources/comparison_table.png b/docs/resources/comparison_table.png new file mode 100644 index 00000000..7e78b028 Binary files /dev/null and b/docs/resources/comparison_table.png differ diff --git a/docs/resources/example.png b/docs/resources/example.png new file mode 100644 index 00000000..11eb1779 Binary files /dev/null and b/docs/resources/example.png differ diff --git a/examples/resources/stats.png b/docs/resources/stats.png similarity index 100% rename from examples/resources/stats.png rename to docs/resources/stats.png diff --git a/docs/resources/summaries.png b/docs/resources/summaries.png new file mode 100644 index 00000000..8fa6527c Binary files /dev/null and b/docs/resources/summaries.png differ diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 00000000..a8e9d27e --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,50 @@ +# Usage + +In general, this is the flow: + +1. Get your samples into a pandas dataframe. If you have another format and want to contribute a translation function, that would be amazing. +2. Turn those samples into a `Chain`. At this point, you can specify a ton of visual and statistical choices. +3. Give those chains to `ChainConsumer` +4. If you know the truth value of the chains, you can call `c.add_truth(Truth(...))` to add this to your plots. Note you can have as many truth lines as you want, not just one. +5. If you want to change plotting specific options (but not chain specific options), you can `set_plot_config(PlotConfig(...))` to control things like the number of ticks, font size, etc. +6. Optionally, use the diagnostics available to see if your chains are stable, or do this via using `plot_walks` +7. Make your contours via `plot`, or your summaries, or your LaTeX tables. + + + +## Statistics + +When summarising chains, ChainConsumer offers several different methods. The below image shows the upper and lower bounds and central points for the "MEAN", "CUMULATIVE", and "MAX" methods respectively. The "MAX_CENTRAL" method is the blue central value and the red bounds. + +![](resources/stats.png) + +::: chainconsumer.statistics.SummaryStatistic + +## Why all these classes and not just kwargs? + +Python type hinting for kwargs isn't quite there yet. `TypedDict` with Python 3.12 is a big step forward, +but I know it'll be a while before the scientific community is all on 3.12. The initial version of ChainConsumer, +which was Python 2.7 compatible, didn't have type hints at all. It just took tons of kwargs and passed them around, +which also caused a huge ton of duplicated docstring and functions. By encapsulating the options into a dataclass, +it becomes much easier for anyone, me or someone who wants to contribute to the repo, to simply add to this class. + +You don't need to remember to update five other functions, and their docstring. I like that. I *don't* like the extra +verbosity, but it's a price I'm willing to pay for more explicit code and better type hinting. + +## How to do overrides + +When you make a Chain, you specify its initial properties. + +```python +c = ChainConsumer() +chain = Chain(samples=df, name="Something", shade=True, plot_point=True, color="red") +c.add_chain(c) +``` + +If you then tell `ChainConsumer` to add an override, this will then replace your original value, for all chains. + +```python +c.add_override(ChainConfig(shade=False)) +``` + +I note that this override does not modify your original chain. When the plotting code requests final chains from `ChainConsumer`, the initial chains are copied and their attributes updated by the override. \ No newline at end of file diff --git a/examples/Basics/README.txt b/examples/Basics/README.txt deleted file mode 100644 index 5f3f78b0..00000000 --- a/examples/Basics/README.txt +++ /dev/null @@ -1,7 +0,0 @@ -.. _basic_examples: - -Basic Usages ------------- - -Things like how to load data and how to use the primary functions of the plotting library. - diff --git a/examples/Basics/plot_convergence.py b/examples/Basics/plot_convergence.py deleted file mode 100644 index 177dbe41..00000000 --- a/examples/Basics/plot_convergence.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -""" -======================= -Convergence Diagnostics -======================= - -How to use the built in convergence diagnostic tests! - -""" - - -############################################################################### -# Here we create some good and bad data, and then run convergence tests on it! - - -import numpy as np -from numpy.random import normal -from chainconsumer import ChainConsumer - -np.random.seed(0) -# Here we have some nice data, and then some bad data, -# where the last part of the chain has walked off, and the first part -# of the chain isn't agreeing with anything else! -data_good = normal(size=100000) -data_bad = data_good.copy() -data_bad += np.linspace(-0.5, 0.5, 100000) -data_bad[98000:] += 2 - -# Lets load it into ChainConsumer, and pretend 10 walks went into making the chain -c = ChainConsumer() -c.add_chain(data_good, walkers=10, name="good") -c.add_chain(data_bad, walkers=10, name="bad") - -# Now, lets check our convergence using the Gelman-Rubin statistic -gelman_rubin_converged = c.diagnostic.gelman_rubin() -# And also using the Geweke metric -geweke_converged = c.diagnostic.geweke() - -# Lets just output the results too -print(gelman_rubin_converged, geweke_converged) - -############################################################################### -# We can see that both the Gelman-Rubin and Geweke statistics failed. -# Note that by not specifying a chain when calling the diagnostics, -# they are invoked on *all* chains. For example, to invoke the statistic -# on only the second chain we can pass in either the chain index, or the chain -# name: - -print(c.diagnostic.geweke(chain="bad")) - -############################################################################### -# Finally, note that the statistics are set to fail easily. For example, -# if you have 10 chains and run `diagnostic_gelman_rubin` with the defaults, -# you will get false if *any* parameter of *any* chain has not converged. -# The printed output will let you know which chains and parameters are -# culpable. diff --git a/examples/Basics/plot_covariance_and_marker.py b/examples/Basics/plot_covariance_and_marker.py deleted file mode 100644 index 1fb9eaf9..00000000 --- a/examples/Basics/plot_covariance_and_marker.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -=============================== -Covariance, Fisher and Markers! -=============================== - -Sometimes you want to compare your data to a Fisher matrix projection, -or you just have some Gaussian you want also drawn. - -Or maybe its just a random point you want to put on the plot. - -It's all easy to do. - -""" -# -*- coding: utf-8 -*- -from chainconsumer import ChainConsumer - -mean = [1, 5] -cov = [[1, 1], [1, 3]] -parameters = ["a", "b"] - -c = ChainConsumer() -c.add_covariance(mean, cov, parameters=parameters, name="Cov") -c.add_marker(mean, parameters=parameters, name="Marker!", marker_style="*", marker_size=100, color="r") -c.configure(usetex=False, serif=False) -fig = c.plotter.plot() - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/Basics/plot_grid.py b/examples/Basics/plot_grid.py deleted file mode 100644 index 6316d440..00000000 --- a/examples/Basics/plot_grid.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -""" -========== -Grid Data! -========== - -If you don't have Monte Carlo chains, and have grid evaluations instead, that's fine too! - -Just flatten your grid, set the weights to the grid evaluation, and set the grid flag. Here is -a nice diamond that you get from modifying a simple multivariate normal distribution. - -Note that by default, grid data is not smoothed, though you can explicitly set the smooth -parameter in ``configure_general`` if you do want smoothing. - -Note that you *cannot* use dictionary input with the grid method and not specify the full -flattened array. This is because we cannot construct the meshgrid from a dictionary, as -the order of the parameters is not preserved in the dictionary. - -""" -import numpy as np -from chainconsumer import ChainConsumer -from scipy.stats import multivariate_normal - - -x, y = np.linspace(-3, 3, 50), np.linspace(-7, 7, 100) -xx, yy = np.meshgrid(x, y, indexing='ij') -pdf = np.exp(-0.5 * (xx * xx + yy * yy / 4 + np.abs(xx * yy))) - -c = ChainConsumer() -c.add_chain([x, y], parameters=["$x$", "$y$"], weights=pdf, grid=True) -fig = c.plotter.plot() -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# If you have the flattened array already, you can also pass this - -# Turning 2D data to flat data. -xs, ys = xx.flatten(), yy.flatten() -coords = np.vstack((xs, ys)).T -pdf_flat = multivariate_normal.pdf(coords, mean=[0.0, 0.0], cov=[[1.0, 0.7], [0.7, 3.5]]) -c = ChainConsumer() -c.add_chain([xs, ys], parameters=["$x$", "$y$"], weights=pdf_flat, grid=True) -c.configure(smooth=1) # Notice how smoothing changes the results! -fig = c.plotter.plot() - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/Basics/plot_hundreds_of_chains.py b/examples/Basics/plot_hundreds_of_chains.py deleted file mode 100644 index 4c1b169a..00000000 --- a/examples/Basics/plot_hundreds_of_chains.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- -""" -================== -Hundreds of Chains -================== - -Sometimes you have a lot of results and want to see the distribution of your results. - -When you have hundreds of chains, for example you've fit a model on 100 realsiations -of different data to validate your model, it's impractical to show hundreds of contours. - -In this case, it is often desired to show the distribution of maximum likelihood points, which -helps quantify whether your model is biased and the statistical uncertainty of your model. - -To do this, you will need to pass in the posterior values. By default, if you pass in enough chains (more than 20), -ChainConsumer will automatically only plot maximum posterior points for chains which have posteriors. You can -explicitly control this by setting `plot_point` and/or `plot_contour` when adding a chain. - -Importantly, if you have a set of chains that represent the same thing, you can group them together -by giving the chains the same name. It is also good practise to set the same colour for these chains. - -""" -# sphinx_gallery_thumbnail_number = 2 - -from scipy.stats import multivariate_normal -import numpy as np -from chainconsumer import ChainConsumer - - -c = ChainConsumer() -for i in range(1000): - # Generate some data centered at a random location with uncertainty - # equal to the scatter - mean = [3, 8] - cov = [[1.0, 0.5], [0.5, 2.0]] - mean_scattered = multivariate_normal.rvs(mean=mean, cov=cov) - data = multivariate_normal.rvs(mean=mean_scattered, cov=cov, size=1000) - posterior = multivariate_normal.logpdf(data, mean=mean_scattered, cov=cov) - c.add_chain(data, posterior=posterior, parameters=["$x$", "$y$"], color='r', name="Simulation validation") -fig = c.plotter.plot() -fig.set_size_inches(2.5 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# We can add multiple datasets, and even mix in plotting contours and points -# together. In this example, we generate two sets of data to plot two clusters -# of maximum posterior points. Additionally we show the contours of a -# 'representative' surface in amber. - -c = ChainConsumer() -p = ["$x$", "$y$", "$z$"] -for i in range(200): - # Generate some data centered at a random location with uncertainty - # equal to the scatter - mean = [3, 8, 4] - cov = [[1.0, 0.5, 0.5], [0.5, 2.0, 0.5], [0.5, 0.5, 1.4]] - mean_scattered = multivariate_normal.rvs(mean=mean, cov=cov) - data = multivariate_normal.rvs(mean=mean_scattered, cov=cov, size=5000) - data2 = data + multivariate_normal.rvs(mean=[8, -8, 7], cov=cov) - posterior = multivariate_normal.logpdf(data, mean=mean_scattered, cov=cov) - plot_contour = i == 0 - - c.add_chain(data, posterior=posterior, parameters=p, color='p', name="Sim1") - - c.add_chain(data2, posterior=posterior, parameters=p, color='k', - marker_style="+", marker_size=20, name="Sim2", marker_alpha=0.5) - -c.add_chain(data + np.array([4, -4, 3]), parameters=p, posterior=posterior, name="Contour Too", - plot_contour=True, plot_point=True, marker_style="*", marker_size=40, - color="a", shade=True, shade_alpha=0.3, kde=True, linestyle="--", bar_shade=True) - -c.configure(legend_artists=True) - -fig = c.plotter.plot() -fig.set_size_inches(2.5 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# If you've loaded a whole host of chains in, but only want to focus on one -# set, you can also pick out all chains with the same name when plotting. - -fig = c.plotter.plot(chains="Sim1") -fig.set_size_inches(2.5 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# Finally, we should clarify what exactly the points mean! If you don't specify -# anything, by defaults the points represent the coordinates of the -# maximum posterior value. However, in high dimensional surfaces, this maximum -# value across all dimensions can be different to the maximum posterior value -# of a 2D slice. If we want to plot, instead of the global maximum as defined -# by the posterior values, the maximum point of each 2D slice, we can specify -# to `configure` that `global_point=False`. - -c.configure(legend_artists=True, global_point=False) -fig = c.plotter.plot(chains="Sim1") -fig.set_size_inches(2.5 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# Note here that the histograms have disappeared. This is because the maximal -# point changes for each pair of parameters, and so none of the points can -# be used in a histogram. Whilst one could use the maximum point, marginalising -# across all parameters, this can be misleading if only two parameters -# are requested to be plotted. As such, we do not report histograms for -# the maximal 2D posterior points. diff --git a/examples/Basics/plot_loading_data.py b/examples/Basics/plot_loading_data.py deleted file mode 100644 index 2c09af5f..00000000 --- a/examples/Basics/plot_loading_data.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -============ -Loading Data -============ - -Demonstrates the different ways of loading data! - -If you want examples of loading grid data, see the grid data example! - -""" - -############################################################################### -# You can specify truth values using a list (in the same order as the -# declared parameters). - -import numpy as np -import pandas as pd -from numpy.random import multivariate_normal -import tempfile -import os -from chainconsumer import ChainConsumer - -# Lets create some data here to set things up -np.random.seed(4) -truth = [0, 5] -data = multivariate_normal(truth, np.eye(2), size=100000) -parameters = ["$x$", "$y$"] -df = pd.DataFrame(data, columns=parameters) - -directory = tempfile._get_default_tempdir() -filename = next(tempfile._get_candidate_names()) -filename1 = directory + os.sep + filename + ".csv" -filename2 = directory + os.sep + filename + ".npy" -df.to_csv(filename1, index=False) -np.save(filename2, data) - -# Now the normal way of giving data is passing a # -*- coding: utf-8 -*-numpy array and parameter separately -c = ChainConsumer().add_chain(data, parameters=parameters) -fig = c.plotter.plot(truth=truth) -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# You don't actually need to have them as a 2D array, if you have each parameter independently, just list em up! - -x, y = data[:, 0], data[:, 1] -c = ChainConsumer().add_chain([x, y], parameters=parameters) -fig = c.plotter.plot(truth=truth) -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# Of course, a true master uses pandas everywhere -c = ChainConsumer().add_chain(df) -fig = c.plotter.plot(truth=truth) -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - - -############################################################################### -# And yet we can do the same thing using a dictionary: - -dictionary = {"$x$": data[:, 0], "$y$": data[:, 1]} -c = ChainConsumer().add_chain(dictionary) -fig = c.plotter.plot(truth=truth) -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# Or we can pass a filename in containing a text dump of the chain - -c = ChainConsumer().add_chain(filename1) -fig = c.plotter.plot(truth=truth) -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# Or we can pass a filename for a file containing a binary numpy array - -c = ChainConsumer().add_chain(filename2, parameters=parameters) -fig = c.plotter.plot(truth=truth) -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/Basics/plot_statistics.py b/examples/Basics/plot_statistics.py deleted file mode 100644 index 6afe918c..00000000 --- a/examples/Basics/plot_statistics.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -""" -========== -Statistics -========== - -Demonstrates the different statistics you can use with ChainConsumer. - - -""" - -############################################################################### -# By default, ChainConsumer uses maximum likelihood statistics. Thus you do not -# need to explicitly enable maximum likelihood statistics. If you want to -# anyway, the keyword is `"max"`. - -import numpy as np -from scipy.stats import skewnorm -from chainconsumer import ChainConsumer - -# Lets create some data here to set things up -np.random.seed(0) -data = skewnorm.rvs(5, size=(1000000, 2)) -parameters = ["$x$", "$y$"] - - -# Now the normal way of giving data is passing a numpy array and parameter separately -c = ChainConsumer().add_chain(data, parameters=parameters).configure(statistics="max") -fig = c.plotter.plot() -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# Or we can enable cumulative statistics - -c = ChainConsumer().add_chain(data, parameters=parameters).configure(statistics="cumulative") -fig = c.plotter.plot() -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# Or we can enable mean statistics - -c = ChainConsumer().add_chain(data, parameters=parameters).configure(statistics="mean") -fig = c.plotter.plot() -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# Or we can enable maximum symmetric statistics - -c = ChainConsumer().add_chain(data, parameters=parameters).configure(statistics="max_symmetric") -fig = c.plotter.plot() -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# Or we can enable maximum closest statistics - -c = ChainConsumer().add_chain(data, parameters=parameters).configure(statistics="max_shortest") -fig = c.plotter.plot() -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# Or we can enable maximum central statistics - -c = ChainConsumer().add_chain(data, parameters=parameters).configure(statistics="max_central") -fig = c.plotter.plot() -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# We can also take advantage of the ability to pass lists to ChainConsumer's -# configuration to have report different statistics for different chains. -# Please note, I don't recommend you do this in practise, it is just begging -# for confusion. - -c = ChainConsumer() -stats = list(c.analysis._summaries.keys()) -for stat in stats: - c.add_chain(data, parameters=parameters, name=stat.replace("_", " ").title()) -c.configure(statistics=stats, bar_shade=True) -fig = c.plotter.plot() -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/Basics/plot_truth_values.py b/examples/Basics/plot_truth_values.py deleted file mode 100644 index 9ffde645..00000000 --- a/examples/Basics/plot_truth_values.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -""" -============ -Truth Values -============ - -Plot truth values on top of your contours. - -""" - -############################################################################### -# You can specify truth values using a list (in the same order as the -# declared parameters). - -import numpy as np -from numpy.random import normal, multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(2) -cov = 0.2 * normal(size=(3, 3)) + np.identity(3) -truth = normal(size=3) -data = multivariate_normal(truth, np.dot(cov, cov.T), size=100000) - -c = ChainConsumer().add_chain(data, parameters=["$x$", "$y$", r"$\beta$"]) -fig = c.plotter.plot(truth=truth) -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# Or you can specify truth values using a dictionary. This allows you to specify -# truth values for only some parameters. You can also customise the look -# of your truth lines. - - -c.configure_truth(color='w', ls=":", alpha=0.8) -fig2 = c.plotter.plot(truth={"$x$": truth[0], "$y$": truth[1]}) -fig2.set_size_inches(0 + fig2.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/Basics/plot_two_disjoint_chains.py b/examples/Basics/plot_two_disjoint_chains.py deleted file mode 100644 index 4aa31eed..00000000 --- a/examples/Basics/plot_two_disjoint_chains.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -""" -=================== -Two Disjoint Chains -=================== - -You can plot multiple chains. They can even have different parameters! - - -""" -import numpy as np -from numpy.random import normal, multivariate_normal -from chainconsumer import ChainConsumer - - -np.random.seed(0) -cov = normal(size=(3, 3)) -cov2 = normal(size=(4, 4)) -data = multivariate_normal(normal(size=3), np.dot(cov, cov.T), size=100000) -data2 = multivariate_normal(normal(size=4), np.dot(cov2, cov2.T), size=100000) - -c = ChainConsumer() -c.add_chain(data, parameters=["$x$", "$y$", r"$\alpha$"]) -c.add_chain(data2, parameters=["$x$", "$y$", r"$\alpha$", r"$\gamma$"]) -fig = c.plotter.plot() - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/README.txt b/examples/README.txt deleted file mode 100644 index 106114f3..00000000 --- a/examples/README.txt +++ /dev/null @@ -1,14 +0,0 @@ -.. _examples-index: - -======== -Examples -======== - -ChainConsumer can, at this point in time, do several distinct, useful tasks. -The primary one is to create the likelihood surfaces and marginalised -distributions that should be familiar. ChainConsumer can also plot -the chains as walks to check convergence and mixing. Additionally, ChainConsumer -can output LaTeX ready tables of parameter summaries that can be -copied and pasted directly into a TeX document, or similar LaTeX tables -with parameter correlation, covariance or inter-model comparisons using AIC, BIC and DIC. - diff --git a/examples/customisations/README.txt b/examples/customisations/README.txt deleted file mode 100644 index 9565248a..00000000 --- a/examples/customisations/README.txt +++ /dev/null @@ -1,8 +0,0 @@ -.. _customisations_examples: - -Customisations --------------- - -Here I have included some basic examples showing the different output you -can get with ChainConsumer. - diff --git a/examples/customisations/plot_as_prior.py b/examples/customisations/plot_as_prior.py deleted file mode 100644 index dd0add3d..00000000 --- a/examples/customisations/plot_as_prior.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -""" -================ -Plotting a prior -================ - -If you have 1D priors that you don't want to appear in the contour, thats possible too. - -""" -import numpy as np -from numpy.random import normal, random, multivariate_normal -from chainconsumer import ChainConsumer - - -if __name__ == "__main__": - np.random.seed(0) - cov = random(size=(2, 2)) + np.identity(2) - data = multivariate_normal(normal(size=2), np.dot(cov, cov.T), size=100000) - - prior = normal(0, 1, size=100000) - - fig = ChainConsumer()\ - .add_chain(data, parameters=["x", "y"], name="Normal")\ - .add_chain(prior, parameters=["y"], name="Prior", show_as_1d_prior=True)\ - .plotter.plot() - - fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_axis.py b/examples/customisations/plot_axis.py deleted file mode 100644 index 692c01a7..00000000 --- a/examples/customisations/plot_axis.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -""" -============= -External axes -============= - -To put contours in external figures. - -To help in inserting contours into other plots and figures, you can call the simplified -`plot_contour` method and pass in an axis. Note that this is a minimal routine, and will not -do auto-extents, labels, ticks, truth values, etc. But it will make contours for you. - -""" - -from chainconsumer import ChainConsumer -from scipy.stats import multivariate_normal as mv -import matplotlib.pyplot as plt - -data = mv.rvs(mean=[5, 6], cov=[[1, 0.9], [0.9, 1]], size=10000) - -fig, axes = plt.subplots(nrows=2, figsize=(4, 6), sharex=True) -axes[0].scatter(data[:, 0], data[:, 1], s=1, alpha=0.1) - -c = ChainConsumer() -c.add_chain(data, parameters=["a", "b"]) -c.plotter.plot_contour(axes[1], "a", "b") - -for ax in axes: - ax.axvline(5) - ax.axhline(6) diff --git a/examples/customisations/plot_blinding.py b/examples/customisations/plot_blinding.py deleted file mode 100644 index fa64c80d..00000000 --- a/examples/customisations/plot_blinding.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -""" -=================== -Blinding Parameters -=================== - -You can blind parameters and not show axis labels very easily! - -Just give ChainConsumer the `blind` parameter when plotting. You can specify `True` to blind all parameters, -or give it a string (or list of strings) detailing the specific parameters you want blinded! - -""" - -import numpy as np -from numpy.random import multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(0) -data = multivariate_normal([0, 0], [[1, 0], [0, 1]], size=1000000) - -c = ChainConsumer().add_chain(data, parameters=["$x$", "$y$"]) -c.configure(colors=["g"]) -fig = c.plotter.plot(blind="$y$") - -fig.set_size_inches(2.5 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_chain_override.py b/examples/customisations/plot_chain_override.py deleted file mode 100644 index 175744a0..00000000 --- a/examples/customisations/plot_chain_override.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -==================== -Overriding Configure -==================== - -You can specify display options when adding chains. - -This is useful for when you are playing around with code, adding and removing chains -as you tweak the plot. Normally, this would involve modifying the lists passed into `configure` -if you wanted to keep a specific chain with a specific style. To make it easier, -you can specify chain properties when addng them via `add_chain`. If set, these values override -anything specified in configure (and # -*- coding: utf-8 -*- -thus override the default configure behaviour). - -""" -import numpy as np -from numpy.random import multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(0) -data1 = multivariate_normal([-2, 0], [[1, 0], [0, 1]], size=100000) -data2 = multivariate_normal([4, -4], [[1, 0], [0, 1]], size=100000) -data3 = multivariate_normal([-2, -4], [[1, 0.7], [0.7, 1]], size=100000) - -c = ChainConsumer() -c.add_chain(data1, parameters=["x", "y"], color="red", linestyle=":", name="Red dots") -c.add_chain(data2, parameters=["x", "y"], color="#4286f4", shade_alpha=1.0, name="Blue solid") -c.add_chain(data3, parameters=["x", "y"], color="lg", kde=1.5, linewidth=2.0, name="Green smoothed") - -fig = c.plotter.plot() -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_cloud_sigma.py b/examples/customisations/plot_cloud_sigma.py deleted file mode 100644 index 54339a96..00000000 --- a/examples/customisations/plot_cloud_sigma.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -""" -====================== -Cloud and Sigma Levels -====================== - -Choose custom sigma levels and display point cloud. - -In this example we display more sigma levels, turn on the point cloud, and -disable the parameter summaries on the top of the marginalised distributions. - -Note that because of the very highly correlated distribution we have, it is -useful to increase the number of bins the plots are generated with, to capture the -thinness of the correlation. -""" - -import numpy as np -from numpy.random import normal, multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(1) -cov = normal(size=(3, 3)) -data = multivariate_normal(normal(size=3), np.dot(cov, cov.T), size=100000) - -c = ChainConsumer().add_chain(data, parameters=["$x$", "$y$", "$z$"]) -c.configure(summary=False, bins=1.4, cloud=True, sigmas=np.linspace(0, 2, 10)) -fig = c.plotter.plot() - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_colorpoints.py b/examples/customisations/plot_colorpoints.py deleted file mode 100644 index 63a8c433..00000000 --- a/examples/customisations/plot_colorpoints.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -""" -============= -Colour Points -============= - -Add colour scatter to show an extra dimension. - -If we have a secondary parameter that might not be best displayed -as a posterior surface and would be useful to instead give -context to other surfaces, we can select that point to give a -colour mapped scatter plot. - -We can *also* display this as a posterior surface by setting -`plot_colour_params=True`, if we wanted. -""" - -import numpy as np -from numpy.random import normal, multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(1) -cov = normal(size=(3, 3)) -data = multivariate_normal(normal(size=3), np.dot(cov, cov.T), size=100000) - -c = ChainConsumer().add_chain(data, parameters=["$x$", "$y$", "$z$"]) -c.configure(color_params="$z$") -fig = c.plotter.plot(figsize=1.0) - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# You can also plot the weights or posterior if they are specified. Showing weights here. - -weights = 1 / (1 + data[:, 0]**2 + data[:, 1]**2) -c = ChainConsumer().add_chain(data[:, :2], parameters=["$x$", "$y$"], weights=weights) -c.configure(color_params="weights") -fig = c.plotter.plot(figsize=3.0) - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# And showing the posterior color parameter here - -weights = 1 / (1 + data[:, 0]**2 + data[:, 1]**2) -posterior = np.log(weights) -c = ChainConsumer().add_chain(data[:, :2], parameters=["$x$", "$y$"], weights=weights, posterior=posterior) -c.configure(color_params="posterior") -fig = c.plotter.plot(figsize=3.0) - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_colours_shade.py b/examples/customisations/plot_colours_shade.py deleted file mode 100644 index 6f431bea..00000000 --- a/examples/customisations/plot_colours_shade.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -""" -=================== -Colours and Shading -=================== - -Choose custom colours and plot multiple chains with shading. - -Normally when plotting more than two chains, shading is removed so -you can clearly see the outlines. However, you can turn shading back -on and modify the shade opacity if you prefer colourful plots. - -Note that the contour shading and marginalised shading are separate -and are configured independently. - -Colours should be given as hex colours. -""" - -import numpy as np -from numpy.random import normal, multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(2) -cov = normal(size=(2, 2)) + np.identity(2) -d1 = multivariate_normal(normal(size=2), np.dot(cov, cov.T), size=100000) -cov = normal(size=(2, 2)) + np.identity(2) -d2 = multivariate_normal(normal(size=2), np.dot(cov, cov.T), size=100000) -cov = normal(size=(2, 2)) + np.identity(2) -d3 = multivariate_normal(normal(size=2), np.dot(cov, cov.T), size=100000) - -c = ChainConsumer().add_chain(d1, parameters=["$x$", "$y$"]).add_chain(d2).add_chain(d3) -c.configure(colors=["#B32222", "#D1D10D", "#455A64"], shade=True, shade_alpha=0.2, bar_shade=True) -fig = c.plotter.plot() - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - - diff --git a/examples/customisations/plot_confidence_levels.py b/examples/customisations/plot_confidence_levels.py deleted file mode 100644 index 84aeaffe..00000000 --- a/examples/customisations/plot_confidence_levels.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -================= -Confidence Levels -================= - -When setting the sigma levels for ChainConsumer, we need to be careful -if we are talking about 1D or 2D Gaussians. For 1D Gaussians, 1 and 2 :math:`\sigma` correspond -to 68% and 95% confidence levels. However, for a a 2D Gaussian, integrating over 1 and 2 :math:`\sigma` -levels gives 39% and 86% confidence levels. - -By default ChainConsumer uses the 2D levels, such that the contours will line up and agree with the -marginalised distributions shown above them, however you can also choose to switch to using the 1D -Gaussian method, such that the contour encloses 68% and 95% confidence regions, by switching `sigma2d` to `False` - -""" - -import numpy as np -from numpy.random import multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(0) -data = multivariate_normal([0, 0], [[1, 0], [0, 1]], size=1000000) - -c = ChainConsumer().add_chain(data, parameters=["$x$", "$y$"]) -c.configure(flip=False, sigma2d=False, sigmas=[1, 2]) # The default case, so you don't need to specify sigma2d -fig = c.plotter.plot() - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# Demonstrating the 1D Gaussian confidence levels. Notice the change in contour size -# The contours shown below now show the 68% and 95% confidence regions. - -c = ChainConsumer().add_chain(data, parameters=["$x$", "$y$"]) -c.configure(flip=False, sigma2d=True, sigmas=[1, 2]) -fig = c.plotter.plot()# -*- coding: utf-8 -*- - - - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_contour_labels.py b/examples/customisations/plot_contour_labels.py deleted file mode 100644 index 5fbc8a3d..00000000 --- a/examples/customisations/plot_contour_labels.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -""" -============== -Contour Labels -============== - -Plot contours using labels. - -You can set the contour_labels to display confidence levels, as shown below. - -""" - -from numpy.random import multivariate_normal -from chainconsumer import ChainConsumer - - -data = multivariate_normal([0, 0], [[1, 0.5], [0.5, 1.0]], size=1000000) - - -c = ChainConsumer().add_chain(data).configure(contour_labels="confidence") -fig = c.plotter.plot() -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - - -############################################################################### -# Or you can plot in terms of sigma. Note that most people prefer stating -# the confidence levels, because of the ambiguity over sigma levels introduced -# by the `sigma2d` keyword. - -c = ChainConsumer().add_chain(data).configure(contour_labels="sigma") -fig = c.plotter.plot() -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_dont_flip.py b/examples/customisations/plot_dont_flip.py deleted file mode 100644 index 0e36a523..00000000 --- a/examples/customisations/plot_dont_flip.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -""" -===================== -Flips, Ticks and Size -===================== - -You can stop the second parameter rotating in the plot if you prefer squares! - -Unlike the Introduction example, which shows the rotated plots, this example shows them -without the rotation. - -Also, you can pass in a tuple for the figure size. We also demonstrate adding more -ticks to the axis in this example. Also, I change the colour to red, just for fun. -""" - -import numpy as np -from chainconsumer import ChainConsumer - -np.random.seed(0) -data = np.random.multivariate_normal([1.5, 4.0], [[1.0, 0.7], [0.7, 1.5]], size=1000000) -data[:, 0] = np.abs(data[:, 0]) - -c = ChainConsumer().add_chain(data, parameters=["$x_1$", "$x_2$"]) -c.configure(flip=False, max_ticks=10, colors="#D32F2F") -fig = c.plotter.plot(figsize=(6, 6)) - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_fewer_parameters.py b/examples/customisations/plot_fewer_parameters.py deleted file mode 100644 index 3f43be1b..00000000 --- a/examples/customisations/plot_fewer_parameters.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -""" -==================== -Truncated Parameters -==================== - -If you have a large model, you don't need to plot all parameters at once. - -Here we only plot the first four parameters. You could also simply pass the number four, -which means the *first* four parameters. - -For fun, we also plot everything in green. Note you don't need to give multiple colours, -the shading is all computed from the colour hex code. -""" - -import numpy as np -from numpy.random import normal, random, multivariate_normal -from chainconsumer import ChainConsumer - - -np.random.seed(0) -cov = random(size=(6, 6)) -data = multivariate_normal(normal(size=6), np.dot(cov, cov.T), size=200000) -parameters = ["$x$", "$y$", "$z$", "$a$", "$b$", "$c$"] -c = ChainConsumer().add_chain(data, parameters=parameters).configure(colors="#388E3C") -fig = c.plotter.plot(parameters=parameters[:4], figsize="page") - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_font_changes.py b/examples/customisations/plot_font_changes.py deleted file mode 100644 index c21f3e23..00000000 --- a/examples/customisations/plot_font_changes.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -""" -=================== -Change Font Options -=================== - -Control tick rotation and font sizes. - -Here the tick rotation has been turned off, ticks made smaller, -more ticks added, and label size increased! -""" - -import numpy as np -from numpy.random import multivariate_normal -from chainconsumer import ChainConsumer - - -np.random.seed(0) -data = multivariate_normal([0, 1, 2], np.eye(3) + 0.2, size=100000) - -# If you pass in parameter labels and only one chain, you can also get parameter bounds -c = ChainConsumer() -c.add_chain(data, parameters=["$x$", "$y^2$", r"$\Omega_\beta$"], name="Example") -c.configure(diagonal_tick_labels=False, tick_font_size=8, label_font_size=25, max_ticks=8) -fig = c.plotter.plot(figsize="column", legend=True) - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_kde_extents.py b/examples/customisations/plot_kde_extents.py deleted file mode 100644 index da592d66..00000000 --- a/examples/customisations/plot_kde_extents.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -""" -======================== -Gaussian KDE and Extents -======================== - -Smooth marginalised distributions with a Gaussian KDE, and pick custom extents. - - -Note that invoking the KDE on large data sets will significantly increase rendering time when -you have a large number of points. You can also pass a float to your KDE to modify the width -of the bandpass by that factor! - -You can see the increase in contour smoothness (without broadening) for when you have a -low number of samples in your chains! -""" - -import numpy as np -from chainconsumer import ChainConsumer - -np.random.seed(0) -data = np.random.multivariate_normal([0.0, 4.0], [[1.0, -0.7], [-0.7, 1.5]], size=3000) - -c = ChainConsumer() -c.add_chain(data, name="KDE on") -c.add_chain(data + 1, name="KDE off") -c.add_chain(data + 2, name="KDE x2!") -c.configure(kde=[True, False, 2.0], shade_alpha=0.1, flip=False) -fig = c.plotter.plot(extents=[(-2, 4), (0, 9)]) - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_legend_options.py b/examples/customisations/plot_legend_options.py deleted file mode 100644 index 94b6d4bd..00000000 --- a/examples/customisations/plot_legend_options.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -""" -============== -Legend Options -============== - -Legends are hard. - -Because of that, you can pass any keywords to the legend call you want via `legend_kwargs`. -""" - -import numpy as np -from numpy.random import multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(0) -data1 = multivariate_normal([0, 0], [[1, 0], [0, 1]], size=1000000) -data2 = data1 + 2 - -c = ChainConsumer() -c.add_chain(data1, parameters=["$x$", "$y$"], name="Chain 1") -c.add_chain(data2, parameters=["$x$", "$y$"], name="Chain 2") -c.configure(colors=['lb', 'g']) -fig = c.plotter.plot() -fig.set_size_inches(2.5 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# If the linestyles are different and the colours are the same, the artists -# will reappear. - -c = ChainConsumer() -c.add_chain(data1, parameters=["$x$", "$y$"], name="Chain 1") -c.add_chain(data2, parameters=["$x$", "$y$"], name="Chain 2") -c.configure(colors=['lb', 'lb'], linestyles=["-", "--"]) -fig = c.plotter.plot() -fig.set_size_inches(2.5 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# You might also want to relocate the legend to another subplot if your -# contours don't have enough space for the legend! - -c = ChainConsumer() -c.add_chain(data1, parameters=["$x$", "$y$"], name="Chain 1") -c.add_chain(data2, parameters=["$x$", "$y$"], name="Chain 2") -c.configure(linestyles=["-", "--"], sigmas=[0, 1, 2, 3], - legend_kwargs={"loc": "upper left", "fontsize": 10}, - legend_color_text=False, legend_location=(0, 0)) -fig = c.plotter.plot(figsize=1.5) -fig.set_size_inches(2.5 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_linestyles.py b/examples/customisations/plot_linestyles.py deleted file mode 100644 index e347dee1..00000000 --- a/examples/customisations/plot_linestyles.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -""" -=================== -Changing Linestyles -=================== - -Customise the plot line styles. - -In this example we customise the line styles used, and make use of -the ability to pass lists of parameters to the configuration methods. - -""" - -import numpy as np -from numpy.random import normal, multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(1) -cov = normal(size=(3, 3)) -data = multivariate_normal(normal(size=3), np.dot(cov, cov.T), size=100000) -data2 = data * 1.1 + 0.5 - -c = ChainConsumer().add_chain(data, parameters=["$x$", "$y$", "$z$"]).add_chain(data2) -c.configure(linestyles=["-", "--"], linewidths=[1.0, 2.0], - shade=[True, False], shade_alpha=[0.2, 0.0]) -fig = c.plotter.plot() - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_lists.py b/examples/customisations/plot_lists.py deleted file mode 100644 index 3aed8e0b..00000000 --- a/examples/customisations/plot_lists.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -""" -================== -Using List Options -================== - -Utilise all the list options in the configuration! - -This is a general example to illustrate that most parameters -that you can pass to the configuration methods accept lists. - -""" - -import numpy as np -from numpy.random import normal, multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(2) -cov = normal(size=(2, 2)) + np.identity(2) -d1 = multivariate_normal(normal(size=2), np.dot(cov, cov.T), size=100000) -cov = normal(size=(2, 2)) + np.identity(2) -d2 = multivariate_normal(normal(size=2), np.dot(cov, cov.T), size=100000) -cov = normal(size=(2, 2)) + np.identity(2) -d3 = multivariate_normal(normal(size=2), np.dot(cov, cov.T), size=1000000) - -c = ChainConsumer() -c.add_chain(d1, parameters=["$x$", "$y$"]) -c.add_chain(d2) -c.add_chain(d3) - -c.configure(linestyles=["-", "--", "-"], linewidths=[1.0, 3.0, 1.0], - bins=[3.0, 1.0, 1.0], colors=["#1E88E5", "#D32F2F", "#111111"], - smooth=[0, 1, 2], shade=[True, True, False], - shade_alpha=[0.2, 0.1, 0.0], bar_shade=[True, False, False]) -fig = c.plotter.plot() - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - -############################################################################### -# List options are useful for when the properties of the chains are -# interconnected. But if you know the properties at the start, -# you can define them when adding the chains. List options will not -# override explicit chain configurations, so you can use the global -# configure to set options for all chains you haven't explicitly specified. -# -# Note here how even though we set 'all' chains to dotted lines of width 2, our third -# chain, with its explicit options, ignores that. - -c = ChainConsumer() -c.add_chain(d1, parameters=["$x$", "$y$"]).add_chain(d2).add_chain(d3, linestyle="-", linewidth=5) - -c.configure(linestyles=":", linewidths=2) -fig = c.plotter.plot() - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. \ No newline at end of file diff --git a/examples/customisations/plot_logscale.py b/examples/customisations/plot_logscale.py deleted file mode 100644 index 32baf584..00000000 --- a/examples/customisations/plot_logscale.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -""" -======== -Logscale -======== - -For when linear is just not good enough. - -You can set the `log_scale` property with a boolean, or specify a list of bools, or a list of parameter indexes, -or a list of parameter names, or a dictionary from parameter names to boolean values. Most things work, just give -it a crack. - -""" - -import numpy as np -from chainconsumer import ChainConsumer -from scipy.stats import lognorm - -data = lognorm.rvs(0.95, loc=0, size=(100000, 2)) - -c = ChainConsumer() -c.add_chain(data, parameters=["$x_1$", "$x_2$"]) - -fig = c.plotter.plot(figsize="column", log_scales=True) -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - - -############################################################################### -# It's not just for the main corner plot, you can do it anywhere. - -fig = c.plotter.plot_walks(log_scales={"$x_1$": False}) # Dict example -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - - -fig = c.plotter.plot_distributions(log_scales=[True, False]) # list[bool] example -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - diff --git a/examples/customisations/plot_no_histograms.py b/examples/customisations/plot_no_histograms.py deleted file mode 100644 index 6471cbf8..00000000 --- a/examples/customisations/plot_no_histograms.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -""" -============= -No Histograms -============= - -Sometimes marginalised histograms are not needed. - -""" - - -from numpy.random import multivariate_normal, normal, seed -import numpy as np -from chainconsumer import ChainConsumer - -seed(0) -cov = normal(size=(3, 3)) -data = multivariate_normal(normal(size=3), np.dot(cov, cov.T), size=100000) - -c = ChainConsumer().add_chain(data) -c.configure(plot_hists=False) -fig = c.plotter.plot() - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - diff --git a/examples/customisations/plot_no_smooth.py b/examples/customisations/plot_no_smooth.py deleted file mode 100644 index 4d779079..00000000 --- a/examples/customisations/plot_no_smooth.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" -============ -No Smoothing -============ - -We can turn off the default gaussian filter on marginalised distributions. - -This can be done by setting ``smooth`` to either ``0``, ``None`` or ``False``. -Note that the parameter summaries also have smoothing turned off, and -thus summaries may change. - -Fun colour change! And thicker lines! - -""" - -import numpy as np -from chainconsumer import ChainConsumer - -data = np.random.multivariate_normal([0.0, 4.0], [[1.0, 0.7], [0.7, 1.5]], size=100000) - -c = ChainConsumer() -c.add_chain(data, parameters=["$x_1$", "$x_2$"]) -c.configure(smooth=0, linewidths=2, colors="#673AB7") -fig = c.plotter.plot(figsize="column", truth=[0.0, 4.0]) - -# If we wanted to save to file, we would instead have written -# fig = c.plotter.plot(filename="location", figsize="column", truth=[0.0, 4.0]) - -# If we wanted to display the plot interactively... -# fig = c.plotter.plot(display=True, figsize="column", truth=[0.0, 4.0]) - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_one_chain.py b/examples/customisations/plot_one_chain.py deleted file mode 100644 index fd4da18e..00000000 --- a/examples/customisations/plot_one_chain.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -""" -========= -One Chain -========= - -Plot one chain with parameter names. - -Because we are only plotting one chain, we will get -parameter bounds on the marginalised surfaces by -default. -""" - -import numpy as np -from numpy.random import normal, multivariate_normal -from chainconsumer import ChainConsumer - - -np.random.seed(0) -cov = 1e2 * normal(size=(3, 3)) -data = multivariate_normal(1e3 * normal(size=3), np.dot(cov, cov.T), size=100000) - -# If you pass in parameter labels and only one chain, you can also get parameter bounds -fig = ChainConsumer().add_chain(data, parameters=["$x$", "$y$", r"$\epsilon$"]).plotter.plot() - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_preliminary.py b/examples/customisations/plot_preliminary.py deleted file mode 100644 index 43ab9e98..00000000 --- a/examples/customisations/plot_preliminary.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -""" -================== -Watermarking Plots -================== - -Make it obvious that those results are preliminary! - - -It's easy to do, just supply a string to the `watermark` option when plotting your contours, -and remember that when using TeX `matplotlib` settings like `weight` don't do anything - -if you want bold text make it TeX bold. - -The code for this is based off the preliminize github repo at -https://github.com/cpadavis/preliminize, which will add watermark to arbitrary -figures! - -""" - -import numpy as np -from numpy.random import multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(0) -data1 = multivariate_normal([3, 5], [[1, 0], [0, 1]], size=1000000) -data2 = multivariate_normal([5, 3], [[1, 0], [0, 1]], size=10000) - - -c = ChainConsumer() -c.add_chain(data1, parameters=["$x$", "$y$"], name="Good results") -c.add_chain(data2, name="Unfinished results") -fig = c.plotter.plot(watermark=r"\textbf{Preliminary}", figsize=2.0) - - -############################################################################### -# You can also control the text options sent to the matplotlib text call. - -c = ChainConsumer() -c.add_chain(data1, parameters=["$x$", "$y$"], name="Good results") -c.add_chain(data2, name="Unfinished results") -kwargs = {"color": "purple", "alpha": 1.0, "family": "sanserif", "usetex": False, "weight": "bold"} -c.configure(watermark_text_kwargs=kwargs, flip=True) -fig = c.plotter.plot(watermark="SECRET RESULTS", figsize=2.0) - diff --git a/examples/customisations/plot_rainbow_serif_bins.py b/examples/customisations/plot_rainbow_serif_bins.py deleted file mode 100644 index 790acba3..00000000 --- a/examples/customisations/plot_rainbow_serif_bins.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -""" -======================= -Cmap and Custom Bins -======================= - -Invoke the cmap colour scheme and choose how many bins to use with your data. - -By default, the cmap colour scheme is used if you have many, many chains. You can -enable it before that point if you wish and pass in the cmap you want to use. - -You can also pick how many bins you want to display your data with. - -You can see that in this example, we pick too many bins and would not get good -summaries. If you simply want more (or less) bins than the default estimate, -if you input a float instead of an integer, the number of bins will simply scale -by that amount. For example, if the estimated picks 20 bins, and you set ``bins=1.5`` -your plots and summaries would be calculated with 30 bins. - -""" -import numpy as np -from numpy.random import normal, random, multivariate_normal -from chainconsumer import ChainConsumer - - -np.random.seed(0) -cov = 0.3 * random(size=(3, 3)) + np.identity(3) -data = multivariate_normal(normal(size=3), np.dot(cov, cov.T), size=100000) -cov = 0.3 * random(size=(3, 3)) + np.identity(3) -data2 = multivariate_normal(normal(size=3), np.dot(cov, cov.T), size=100000) -cov = 0.3 * random(size=(3, 3)) + np.identity(3) -data3 = multivariate_normal(normal(size=3), np.dot(cov, cov.T), size=100000) -cov = 0.3 * random(size=(3, 3)) + np.identity(3) -data4 = multivariate_normal(normal(size=3), np.dot(cov, cov.T), size=100000) - -c = ChainConsumer() -c.add_chain(data, name="A") -c.add_chain(data2, name="B") -c.add_chain(data3, name="C") -c.add_chain(data4, name="D") -c.configure(bins=50, cmap="plasma") -fig = c.plotter.plot(figsize=0.75) # Also making the figure 75% of its original size, for fun - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_selected_chains.py b/examples/customisations/plot_selected_chains.py deleted file mode 100644 index 42903905..00000000 --- a/examples/customisations/plot_selected_chains.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -""" -================ -Excluding Chains -================ - -You don't have to plot everything at once! - - -For the main plotting methods you can specify which chains you want to plot. You can -do this using either the chain index or using the chain names. Like so: - -""" - -import numpy as np -from numpy.random import multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(0) -data1 = multivariate_normal([0, 0], [[1, 0], [0, 1]], size=1000000) -data2 = multivariate_normal([2, 0], [[1, 0], [0, 1]], size=1000000) -data3 = multivariate_normal([4, 0], [[1, 0], [0, 1]], size=1000000) - -c = ChainConsumer() -c.add_chain(data1, parameters=["$x$", "$y$"], name="Chain A") -c.add_chain(data2, name="Chain B") -c.add_chain(data3, name="Chain C") -fig = c.plotter.plot(chains=["Chain A", "Chain C"]) - -fig.set_size_inches(2.5 + fig.get_size_inches()) # Resize fig for doco. You don't need this. \ No newline at end of file diff --git a/examples/customisations/plot_shade_gradient.py b/examples/customisations/plot_shade_gradient.py deleted file mode 100644 index 38c818bf..00000000 --- a/examples/customisations/plot_shade_gradient.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -""" -============== -Shade Gradient -============== - -Control contour contrast! - -To help make your confidence levels more obvious, you can play with the gradient steepness and -resulting contrast in your contours. -""" - -import numpy as np -from numpy.random import multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(0) -data1 = multivariate_normal([0, 0], [[1, 0], [0, 1]], size=1000000) -data2 = multivariate_normal([4, -4], [[1, 0], [0, 1]], size=1000000) - -c = ChainConsumer() -c.add_chain(data1, parameters=["$x$", "$y$"]) -c.add_chain(data2, parameters=["$x$", "$y$"]) -c.configure(shade_gradient=[0.1, 3.0], colors=['o', 'k'], sigmas=[0, 1, 2, 3], shade_alpha=1.0) -fig = c.plotter.plot() - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_shift.py b/examples/customisations/plot_shift.py deleted file mode 100644 index d82e8ba2..00000000 --- a/examples/customisations/plot_shift.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -""" -============== -Shifting Plots -============== - -Shift all your plots to the same location for blind uncertainty comparison. - - -Plots will shift to the location you tell them to, in the same format as a truth dictionary. -So you can use truth dict for both! Takes a list or a dict as input for convenience. - -""" - -import numpy as np -from numpy.random import multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(0) -data1 = multivariate_normal([1, 0], [[3, 2], [2, 3]], size=300000) -data2 = multivariate_normal([0, 0.5], [[1, -0.7], [-0.7, 1]], size=300000) -data3 = multivariate_normal([2, -1], [[0.5, 0], [0, 0.5]], size=300000) - -############################################################################### -# And this is how easy it is to shift them. Note the different means for each dataset! - -truth = {"$x$": 1, "$y$": 0} -c = ChainConsumer() -c.add_chain(data1, parameters=["$x$", "$y$"], name="Chain A", shift_params=truth) -c.add_chain(data2, name="Chain B", shift_params=truth) -c.add_chain(data3, name="Chain C", shift_params=truth) -fig = c.plotter.plot(truth=truth) - -fig.set_size_inches(2.5 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_spacing.py b/examples/customisations/plot_spacing.py deleted file mode 100644 index f199239e..00000000 --- a/examples/customisations/plot_spacing.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -""" -=============== -Subplot Spacing -=============== - -By default ChainConsumer will reduce subplot whitespace when you hit -a certain dimensionality, but you can also customise this yourself. -""" - -import numpy as np -from numpy.random import normal, random, multivariate_normal -from chainconsumer import ChainConsumer - - -np.random.seed(0) -cov = random(size=(3, 3)) -data = multivariate_normal(normal(size=3), np.dot(cov, cov.T), size=200000) - -c = ChainConsumer().add_chain(data, parameters=["$x$", "$y$", "$z$"]) -c.configure(spacing=0.0) -fig = c.plotter.plot(figsize="column") - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_three_chains.py b/examples/customisations/plot_three_chains.py deleted file mode 100644 index fbc22467..00000000 --- a/examples/customisations/plot_three_chains.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" -============ -Three Chains -============ - -Plot three chains together. Name the chains to get a legend. - - -""" -import numpy as np -from numpy.random import normal, random, multivariate_normal -from chainconsumer import ChainConsumer - - -if __name__ == "__main__": - np.random.seed(0) - cov = random(size=(3, 3)) + np.identity(3) - data = multivariate_normal(normal(size=3), np.dot(cov, cov.T), size=100000) - cov = random(size=(3, 3)) + np.identity(3) - data2 = multivariate_normal(normal(size=3), np.dot(cov, cov.T), size=100000) - cov = random(size=(3, 3)) + np.identity(3) - data3 = multivariate_normal(normal(size=3), np.dot(cov, cov.T), size=100000) - - # If the parameters are the same between chains, you can just pass it the - # first time, and they will become the default parameters. - fig = ChainConsumer()\ - .add_chain(data, parameters=["$x$", "$y$", r"$\epsilon$"], name="Test chain")\ - .add_chain(data2, name="Chain2")\ - .add_chain(data3, name="Chain3") \ - .plotter.plot() - - fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/customisations/plot_zorder.py b/examples/customisations/plot_zorder.py deleted file mode 100644 index ff74e0cb..00000000 --- a/examples/customisations/plot_zorder.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -""" -================ -Changing Z-Order -================ - -Force matplotlib to show the plots we want. - -Here is a bad plot because it's hiding what we want. - - -""" - -import numpy as np -from numpy.random import multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(0) -data1 = multivariate_normal([3, 5], [[1, 0], [0, 1]], size=100000) -data2 = multivariate_normal([3, 5], [[0.2, 0.1], [0.1, 0.3]], size=100000) - - -c = ChainConsumer() -c.add_chain(data1, parameters=["$x$", "$y$"], color="k", shade_alpha=0.7, zorder=1) -c.add_chain(data2, color="o", shade_alpha=0.7, zorder=2) -c.configure(spacing=0) -c.plotter.plot(display=True, figsize=2.0) - -############################################################################### -# Reversing the zorder - -c = ChainConsumer() -c.add_chain(data1, parameters=["$x$", "$y$"], color="k", shade_alpha=0.7, zorder=2) -c.add_chain(data2, color="o", shade_alpha=0.7, zorder=1) -c.configure(spacing=0) -c.plotter.plot(display=True, figsize=2.0) - diff --git a/examples/more/README.txt b/examples/more/README.txt deleted file mode 100644 index 4fd19851..00000000 --- a/examples/more/README.txt +++ /dev/null @@ -1,7 +0,0 @@ -.. _more_examples: - -More Complicated Examples -------------------------- - -To illustrate some of the interesting ways you can use the options in ChainConsumer, see below. - diff --git a/examples/more/plot_colorpoints2.py b/examples/more/plot_colorpoints2.py deleted file mode 100644 index e476a27f..00000000 --- a/examples/more/plot_colorpoints2.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" -======================== -Multiple Colour Scatter! -======================== - -Why show only one colour, when you can display more! - -In the basic colour example, we showed one parameter being used -to give colour information. However, you can pick a different colour, or no colour (`None`), -for each chain. - -You can also pick the same parameter in multiple chains, and all the scatter points will be put -on the same colour scale. The underlying contours will still be distinguishable automatically -by adding alternative linestyles, as shown below. -""" - -import numpy as np -from numpy.random import normal, multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(1) -cov = normal(size=(4, 4)) -data = multivariate_normal(normal(size=4), np.dot(cov, cov.T), size=100000) -cov = 1 + 0.5 * normal(size=(4, 4)) -data2 = multivariate_normal(4+normal(size=4), np.dot(cov, cov.T), size=100000) - -c = ChainConsumer().add_chain(data, parameters=["$x$", "$y$", "$z$", "$g$"], name="a") -c.add_chain(data2, parameters=["$x$", "$y$", "$z$", "$t$"], name="b") -c.configure(color_params=["$g$", "$t$"]) -fig = c.plotter.plot(figsize=1.75) - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/more/plot_divide_chain.py b/examples/more/plot_divide_chain.py deleted file mode 100644 index f5f6a729..00000000 --- a/examples/more/plot_divide_chain.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -""" -================ -Dividing a chain -================ - -ChainConsumer can split one chain into many! - -If you use a sampling algorithm with multiple walkers (which -is fairly common), it can be useful to plot each walker as a separate chain -so that you can verify that your walkers have all converged to the same place. - -You can use the `plot_walks` method for this, or the convergence diagnostics, -but the more options the better! - -In the below example, I assume the generated data was created using ten walkers. -I introduce some fake shift in the data to badly emulate walker drift. - -""" - -import numpy as np -from numpy.random import multivariate_normal -from chainconsumer import ChainConsumer - - -np.random.seed(0) -data = multivariate_normal([0.0, 4.0], [[1.0, 0.7], [0.7, 1.5]], size=1000000) -data[:, 0] += np.linspace(0, 1, data.shape[0]) - -c = ChainConsumer().add_chain(data, parameters=["$x$", "$y$"], walkers=5) -c2 = c.divide_chain() -fig = c2.plotter.plot() - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/more/plot_many.py b/examples/more/plot_many.py deleted file mode 100644 index b6152cfa..00000000 --- a/examples/more/plot_many.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -""" -========================= -Plot Many Things For Fun! -========================= - -Lets try a few things to give what might be a usable plot to through into our latest paper. - -Or something. - -First lets mock some highly correlated data with colour scatter. And then throw a few more -data sets in to get some overlap. -""" - -import numpy as np -from numpy.random import normal, multivariate_normal, uniform -from chainconsumer import ChainConsumer - -np.random.seed(1) -n = 1000000 -data = multivariate_normal([0.4, 1], [[0.01, -0.003], [-0.003, 0.001]], size=n) -data = np.hstack((data, (67 + 10 * data[:, 0] - data[:, 1] ** 2)[:, None])) -data2 = np.vstack((uniform(-0.1, 1.1, n), normal(1.2, 0.1, n))).T -data2[:, 1] -= (data2[:, 0] ** 2) -data3 = multivariate_normal([0.3, 0.7], [[0.02, 0.05], [0.05, 0.1]], size=n) - -c = ChainConsumer() -c.add_chain(data2, parameters=["$\Omega_m$", "$-w$"], name="B") -c.add_chain(data3, name="S") -c.add_chain(data, parameters=["$\Omega_m$", "$-w$", "$H_0$"], name="P") - -c.configure(color_params="$H_0$", shade=[True, True, False], - shade_alpha=0.2, bar_shade=True, linestyles=["-", "--", "-"]) -fig = c.plotter.plot(figsize=2.0, extents=[[0, 1], [0, 1.5]]) - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/plot_correlations.py b/examples/plot_correlations.py deleted file mode 100644 index e9670acd..00000000 --- a/examples/plot_correlations.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" -=========================== -Plot Parameter Correlations -=========================== - -You can also get LaTeX tables for parameter correlations. - -Turned into glorious LaTeX, we would get something like the following: - -.. figure:: ../../examples/resources/correlations.png - :align: center - :width: 60% - -""" - -############################################################################### -# The code to produce this, and the raw LaTeX, is given below: - - -import numpy as np -from chainconsumer import ChainConsumer - - -cov = [[1, 0.5, 0.2], [0.5, 1, 0.3], [0.2, 0.3, 1.0]] -data = np.random.multivariate_normal([0, 0, 1], cov, size=100000) -parameters = ["x", "y", "z"] -c = ChainConsumer() -c.add_chain(data, parameters=parameters) -latex_table = c.analysis.get_correlation_table() - -print(latex_table) - diff --git a/examples/plot_covariance.py b/examples/plot_covariance.py deleted file mode 100644 index 416f7294..00000000 --- a/examples/plot_covariance.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -""" -========================= -Plot Parameter Covariance -========================= - -You can also get LaTeX tables for parameter covariance. - -Turned into glorious LaTeX, we would get something like the following: - -.. figure:: ../../examples/resources/covariance.png - :align: center - :width: 60% - -""" - -############################################################################### -# The code to produce this, and the raw LaTeX, is given below: - - -import numpy as np -from chainconsumer import ChainConsumer - - -cov = [[1.0, 0.5, 0.2], [0.5, 2.0, 0.3], [0.2, 0.3, 3.0]] -data = np.random.multivariate_normal([0, 0, 1], cov, size=1000000) -parameters = ["x", "y", "z"] -c = ChainConsumer() -c.add_chain(data, parameters=parameters) -latex_table = c.analysis.get_covariance_table() -print(latex_table) - diff --git a/examples/plot_distributions.py b/examples/plot_distributions.py deleted file mode 100644 index 01171e45..00000000 --- a/examples/plot_distributions.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -""" -================== -Plot Distributions -================== - -If you want a fast check of your distributions for high dimensional spaces (such -that you can only generate a surfaces for a subset of parameters), you can -simply plot all of the marginalised distributions using this method. - - -""" - -import numpy as np -from numpy.random import random, multivariate_normal -from chainconsumer import ChainConsumer - -np.random.seed(0) -means, cov = np.arange(8), random(size=(8, 8)) -data = multivariate_normal(means, np.dot(cov, cov.T), size=1000000) - -params = ["$x$", "$y$", "$z$", "a", "b", "c", "d", "e"] -c = ChainConsumer().add_chain(data, parameters=params) - -fig = c.plotter.plot_distributions(truth=means) - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/plot_introduction.py b/examples/plot_introduction.py deleted file mode 100644 index 1e25894d..00000000 --- a/examples/plot_introduction.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- -""" -============= -Plot Contours -============= - -A trivial example using data from a multivariate normal. - -We give truth values, parameter labels and set the figure size to -fit one column of a two column document. - -It is important to note that (in future examples) we always call the -configuration methods *after* loading in all the data. - -Note that the parameter summaries are all calculated from the chosen bin size and take -into account if the data is being smoothed or not. It is thus important to consider -whether you want smoothing enabled or (depending on your surfaces) more or less -bins than automatically estimated. See the extended customisation examples for -more information. - -""" - -import numpy as np -from chainconsumer import ChainConsumer - -np.random.seed(0) -data = np.random.multivariate_normal([0.0, 4.0], [[1.0, 0.7], [0.7, 1.5]], size=1000000) - -c = ChainConsumer() -c.add_chain(data, parameters=["$x_1$", "$x_2$"]) -fig = c.plotter.plot(figsize="column", truth=[0.0, 4.0]) - -# If we wanted to save to file, we would instead have written -# fig = c.plotter.plot(filename="location", figsize="column", truth=[0.0, 4.0]) - -# If we wanted to display the plot interactively... -# fig = c.plotter.plot(display=True, figsize="column", truth=[0.0, 4.0]) - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. diff --git a/examples/plot_model_selection.py b/examples/plot_model_selection.py deleted file mode 100644 index ea82f9f1..00000000 --- a/examples/plot_model_selection.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -""" -===================== -Plot Model Comparison -===================== - -You can also get LaTeX tables for model comparison. - -Turned into glorious LaTeX, we would get something like the following: - -.. figure:: ../../examples/resources/table_comparison.png - :align: center - :width: 60% - -""" - -############################################################################### -# The code to produce this, and the raw LaTeX, is given below: - - -from scipy.stats import norm -from chainconsumer import ChainConsumer - - -n = 10000 -d1 = norm.rvs(size=n) -p1 = norm.logpdf(d1) -p2 = norm.logpdf(d1, scale=1.1) - -c = ChainConsumer() -c.add_chain(d1, posterior=p1, name="Model A", num_eff_data_points=n, num_free_params=4) -c.add_chain(d1, posterior=p2, name="Model B", num_eff_data_points=n, num_free_params=5) -c.add_chain(d1, posterior=p2, name="Model C", num_eff_data_points=n, num_free_params=4) -c.add_chain(d1, posterior=p1, name="Model D", num_eff_data_points=n, num_free_params=14) -table = c.comparison.comparison_table(caption="Model comparisons!") -print(table) diff --git a/examples/plot_summary.py b/examples/plot_summary.py deleted file mode 100644 index 07bf7752..00000000 --- a/examples/plot_summary.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -""" -============ -Plot Summary -============ - -Have a bunch of models and want to compare summaries, but in a plot instead of LaTeX? Can do! - - -""" - -############################################################################### -# Lets add a bunch of chains represnting all these different models of ours. - -import numpy as np -from chainconsumer import ChainConsumer - - -def get_instance(): - np.random.seed(0) - c = ChainConsumer() - parameters = ["$x$", r"$\Omega_\epsilon$", "$r^2(x_0)$"] - for name in ["Ref. model", "Test A", "Test B", "Test C"]: - # Add some random data - mean = np.random.normal(loc=0, scale=3, size=3) - sigma = np.random.uniform(low=1, high=3, size=3) - data = np.random.multivariate_normal(mean=mean, cov=np.diag(sigma**2), size=100000) - c.add_chain(data, parameters=parameters, name=name) - return c - -############################################################################### -# If we want the full shape of the distributions, well, thats the default -# behaviour! -c = get_instance() -c.configure(bar_shade=True) -c.plotter.plot_summary() - -############################################################################### -# But lets make some changes. Say we don't like the colourful text. And we -# want errorbars, not distributions. And some fun truth values. - -c = get_instance() -c.configure(legend_color_text=False) -c.configure_truth(ls=":", color="#FB8C00") -c.plotter.plot_summary(errorbar=True, truth=[[0], [-1, 1], [-2, 0, 2]]) - -############################################################################### -# Even better, lets use our reference model as the truth value and not plot -# it with the others - -c = get_instance() -c.configure(legend_color_text=False) -c.configure_truth(ls="-", color="#555555") -c.plotter.plot_summary(errorbar=True, truth="Ref. model", include_truth_chain=False, extra_parameter_spacing=1.5) diff --git a/examples/plot_table.py b/examples/plot_table.py deleted file mode 100644 index 43e02cbe..00000000 --- a/examples/plot_table.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -""" -=========== -Plot Tables -=========== - -You can also get LaTeX tables for parameter summaries. - -Turned into glorious LaTeX, we would get something like the following: - -.. figure:: ../../examples/resources/table.png - :align: center - -""" - -############################################################################### -# The code to produce this, and the raw LaTeX, is given below: - - -import numpy as np -from chainconsumer import ChainConsumer - - -ndim, nsamples = 4, 200000 -np.random.seed(0) - -data = np.random.randn(nsamples, ndim) -data[:, 2] += data[:, 1] * data[:, 2] -data[:, 1] = data[:, 1] * 3 + 5 -data[:, 3] /= (np.abs(data[:, 1]) + 1) - -data2 = np.random.randn(nsamples, ndim) -data2[:, 0] -= 1 -data2[:, 2] += data2[:, 1]**2 -data2[:, 1] = data2[:, 1] * 2 - 5 -data2[:, 3] = data2[:, 3] * 1.5 + 2 - -# If you pass in parameter labels and only one chain, you can also get parameter bounds -c = ChainConsumer() -c.add_chain(data, parameters=["$x$", "$y$", r"$\alpha$", r"$\beta$"], name="Model A") -c.add_chain(data2, parameters=["$x$", "$y$", r"$\alpha$", r"$\gamma$"], name="Model B") -table = c.analysis.get_latex_table(caption="Results for the tested models", label="tab:example") -print(table) diff --git a/examples/plot_walk.py b/examples/plot_walk.py deleted file mode 100644 index 560ba51a..00000000 --- a/examples/plot_walk.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -""" -========== -Plot Walks -========== - -You can also plot the walks that your chains have undertaken. - -This is a very helpful plot to create when determining if your chains have -converged and mixed. Below is an example walk from a Metropolis-Hastings run, -where we have set the optional parameters for the weights and posteriors, -giving the top two subplots. - -.. figure:: ../../examples/resources/exampleWalk.png - :align: center - -""" - - -############################################################################### -# To generate your own walk, with a 100 point smoothed walk overplotting, -# you can use the following code: - -import numpy as np -from chainconsumer import ChainConsumer - -np.random.seed(0) -data1 = np.random.randn(100000, 2) -data2 = np.random.randn(100000, 2) - 2 -data1[:, 1] += 1 - -c = ChainConsumer() -c.add_chain(data1, parameters=["$x$", "$y$"]) -c.add_chain(data2, parameters=["$x$", "$z$"]) -fig = c.plotter.plot_walks(truth={"$x$": -1, "$y$": 1, "$z$": -2}, convolve=100) - -fig.set_size_inches(3 + fig.get_size_inches()) # Resize fig for doco. You don't need this. - diff --git a/examples/resources/correlations.png b/examples/resources/correlations.png deleted file mode 100644 index 5bf1ea83..00000000 Binary files a/examples/resources/correlations.png and /dev/null differ diff --git a/examples/resources/covariance.png b/examples/resources/covariance.png deleted file mode 100644 index b015dac5..00000000 Binary files a/examples/resources/covariance.png and /dev/null differ diff --git a/examples/resources/exampleWalk.png b/examples/resources/exampleWalk.png deleted file mode 100644 index 8722fbf4..00000000 Binary files a/examples/resources/exampleWalk.png and /dev/null differ diff --git a/examples/resources/summary.png b/examples/resources/summary.png deleted file mode 100644 index 569cf032..00000000 Binary files a/examples/resources/summary.png and /dev/null differ diff --git a/examples/resources/table.png b/examples/resources/table.png deleted file mode 100644 index d62124d5..00000000 Binary files a/examples/resources/table.png and /dev/null differ diff --git a/examples/resources/table_comparison.png b/examples/resources/table_comparison.png deleted file mode 100644 index 0098a18e..00000000 Binary files a/examples/resources/table_comparison.png and /dev/null differ diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..19cadd8a --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,75 @@ +site_name: ChainConsumer +site_url: https://samreay.github.io/chainconsumer/ +site_author: Samuel Hinton + +edit_uri: "" +repo_name: samreay/chainconsumer +repo_url: https://github.com/samreay/chainconsumer + +theme: + name: material + icon: + repo: fontawesome/brands/github + features: + - search.suggest + - search.highlight + - search.tabs.link + - navigation.expand + - toc.follow + - navigation.tracking + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: teal + accent: light green + toggle: + icon: material/weather-sunny + name: Switch to light mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: teal + accent: light green + toggle: + icon: material/weather-night + name: Switch to dark mode + +plugins: + autorefs: {} + mkdocstrings: + handlers: + python: + paths: [.] + options: + members_order: source + separate_signature: true + filters: ["!^_"] + show_root_heading: true + show_if_no_docstring: true + show_signature_annotations: true + gallery: + examples_dirs: docs/examples # path to your example scripts + gallery_dirs: docs/generated/gallery # where to save generated gallery + image_srcset: ['2x'] + within_subsection_order: FileNameSortKey + search: {} + +markdown_extensions: + - mdx_include: + base_path: docs + - toc: + permalink: true + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.details + - pymdownx.snippets + - pymdownx.superfences + +watch: + - docs + - src +nav: + - Home: index.md + - Usage: usage.md + - Python API: api.md + - Examples: generated/gallery diff --git a/paper/paper.md b/paper/paper.md index 81e89d87..ba16ea92 100644 --- a/paper/paper.md +++ b/paper/paper.md @@ -16,22 +16,22 @@ bibliography: paper.bib ChainConsumer is a python package written to consume the output chains of Monte-Carlo processes and fitting algorithms, such as the results -of MCMC. +of MCMC. -ChainConsumer's main function is to produce plots of the likelihood +ChainConsumer's main function is to produce plots of the likelihood surface inferred from the supplied chain. In addition to showing the two-dimensional marginalised likelihood surfaces, marginalised parameter distributions are given, and maximum-likelihood statistics -are used to present parameter constraints. +are used to present parameter constraints. In addition to this, parameter constraints can be output -in the form of a LaTeX table. Finally, ChainConsumer also provides -the functionality to plot the chains as a series of walks in -parameter values, which provides an easy visual check on chain +in the form of a LaTeX table. Finally, ChainConsumer also provides +the functionality to plot the chains as a series of walks in +parameter values, which provides an easy visual check on chain mixing and chain convergence. -Plotting is performed via the matplotlib library [@matplotlib], and +Plotting is performed via the matplotlib library [@matplotlib], and makes use of various numpy [@numpy] and scipy [@scipy] functions. The optional KDE feature makes use of [@statsmodels]. diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..2b6a8b66 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,3710 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + +[[package]] +name = "anyio" +version = "4.0.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.0.0-py3-none-any.whl", hash = "sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f"}, + {file = "anyio-4.0.0.tar.gz", hash = "sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.22)"] + +[[package]] +name = "appnope" +version = "0.1.3" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = "*" +files = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] + +[[package]] +name = "argon2-cffi" +version = "23.1.0" +description = "Argon2 for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"}, + {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"}, +] + +[package.dependencies] +argon2-cffi-bindings = "*" + +[package.extras] +dev = ["argon2-cffi[tests,typing]", "tox (>4)"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"] +tests = ["hypothesis", "pytest"] +typing = ["mypy"] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +description = "Low-level CFFI bindings for Argon2" +optional = false +python-versions = ">=3.6" +files = [ + {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, + {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, +] + +[package.dependencies] +cffi = ">=1.0.1" + +[package.extras] +dev = ["cogapp", "pre-commit", "pytest", "wheel"] +tests = ["pytest"] + +[[package]] +name = "arrow" +version = "1.3.0" +description = "Better dates & times for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, + {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}, +] + +[package.dependencies] +python-dateutil = ">=2.7.0" +types-python-dateutil = ">=2.8.10" + +[package.extras] +doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] +test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] + +[[package]] +name = "asttokens" +version = "2.4.0" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.0-py2.py3-none-any.whl", hash = "sha256:cf8fc9e61a86461aa9fb161a14a0841a03c405fa829ac6b202670b3495d2ce69"}, + {file = "asttokens-2.4.0.tar.gz", hash = "sha256:2e0171b991b2c959acc6c49318049236844a5da1d65ba2672c4880c1c894834e"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +test = ["astroid", "pytest"] + +[[package]] +name = "async-lru" +version = "2.0.4" +description = "Simple LRU cache for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "async-lru-2.0.4.tar.gz", hash = "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627"}, + {file = "async_lru-2.0.4-py3-none-any.whl", hash = "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + +[[package]] +name = "babel" +version = "2.13.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.13.0-py3-none-any.whl", hash = "sha256:fbfcae1575ff78e26c7449136f1abbefc3c13ce542eeb13d43d50d8b047216ec"}, + {file = "Babel-2.13.0.tar.gz", hash = "sha256:04c3e2d28d2b7681644508f836be388ae49e0cfe91465095340395b60d00f210"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +optional = false +python-versions = "*" +files = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] + +[[package]] +name = "beautifulsoup4" +version = "4.12.2" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "black" +version = "23.9.1" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, + {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, + {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, + {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, + {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, + {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, + {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, + {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, + {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, + {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, + {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "bleach" +version = "6.1.0" +description = "An easy safelist-based HTML-sanitizing tool." +optional = false +python-versions = ">=3.8" +files = [ + {file = "bleach-6.1.0-py3-none-any.whl", hash = "sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6"}, + {file = "bleach-6.1.0.tar.gz", hash = "sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe"}, +] + +[package.dependencies] +six = ">=1.9.0" +webencodings = "*" + +[package.extras] +css = ["tinycss2 (>=1.1.0,<1.3)"] + +[[package]] +name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, + {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "comm" +version = "0.1.4" +description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +optional = false +python-versions = ">=3.6" +files = [ + {file = "comm-0.1.4-py3-none-any.whl", hash = "sha256:6d52794cba11b36ed9860999cd10fd02d6b2eac177068fdd585e1e2f8a96e67a"}, + {file = "comm-0.1.4.tar.gz", hash = "sha256:354e40a59c9dd6db50c5cc6b4acc887d82e9603787f83b68c01a80a923984d15"}, +] + +[package.dependencies] +traitlets = ">=4" + +[package.extras] +lint = ["black (>=22.6.0)", "mdformat (>0.7)", "mdformat-gfm (>=0.3.5)", "ruff (>=0.0.156)"] +test = ["pytest"] +typing = ["mypy (>=0.990)"] + +[[package]] +name = "contourpy" +version = "1.1.1" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.8" +files = [ + {file = "contourpy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:46e24f5412c948d81736509377e255f6040e94216bf1a9b5ea1eaa9d29f6ec1b"}, + {file = "contourpy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e48694d6a9c5a26ee85b10130c77a011a4fedf50a7279fa0bdaf44bafb4299d"}, + {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a66045af6cf00e19d02191ab578a50cb93b2028c3eefed999793698e9ea768ae"}, + {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ebf42695f75ee1a952f98ce9775c873e4971732a87334b099dde90b6af6a916"}, + {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6aec19457617ef468ff091669cca01fa7ea557b12b59a7908b9474bb9674cf0"}, + {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:462c59914dc6d81e0b11f37e560b8a7c2dbab6aca4f38be31519d442d6cde1a1"}, + {file = "contourpy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6d0a8efc258659edc5299f9ef32d8d81de8b53b45d67bf4bfa3067f31366764d"}, + {file = "contourpy-1.1.1-cp310-cp310-win32.whl", hash = "sha256:d6ab42f223e58b7dac1bb0af32194a7b9311065583cc75ff59dcf301afd8a431"}, + {file = "contourpy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:549174b0713d49871c6dee90a4b499d3f12f5e5f69641cd23c50a4542e2ca1eb"}, + {file = "contourpy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:407d864db716a067cc696d61fa1ef6637fedf03606e8417fe2aeed20a061e6b2"}, + {file = "contourpy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe80c017973e6a4c367e037cb31601044dd55e6bfacd57370674867d15a899b"}, + {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e30aaf2b8a2bac57eb7e1650df1b3a4130e8d0c66fc2f861039d507a11760e1b"}, + {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3de23ca4f381c3770dee6d10ead6fff524d540c0f662e763ad1530bde5112532"}, + {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:566f0e41df06dfef2431defcfaa155f0acfa1ca4acbf8fd80895b1e7e2ada40e"}, + {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04c2f0adaf255bf756cf08ebef1be132d3c7a06fe6f9877d55640c5e60c72c5"}, + {file = "contourpy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0c188ae66b772d9d61d43c6030500344c13e3f73a00d1dc241da896f379bb62"}, + {file = "contourpy-1.1.1-cp311-cp311-win32.whl", hash = "sha256:0683e1ae20dc038075d92e0e0148f09ffcefab120e57f6b4c9c0f477ec171f33"}, + {file = "contourpy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:8636cd2fc5da0fb102a2504fa2c4bea3cbc149533b345d72cdf0e7a924decc45"}, + {file = "contourpy-1.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:560f1d68a33e89c62da5da4077ba98137a5e4d3a271b29f2f195d0fba2adcb6a"}, + {file = "contourpy-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24216552104ae8f3b34120ef84825400b16eb6133af2e27a190fdc13529f023e"}, + {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56de98a2fb23025882a18b60c7f0ea2d2d70bbbcfcf878f9067234b1c4818442"}, + {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:07d6f11dfaf80a84c97f1a5ba50d129d9303c5b4206f776e94037332e298dda8"}, + {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1eaac5257a8f8a047248d60e8f9315c6cff58f7803971170d952555ef6344a7"}, + {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19557fa407e70f20bfaba7d55b4d97b14f9480856c4fb65812e8a05fe1c6f9bf"}, + {file = "contourpy-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:081f3c0880712e40effc5f4c3b08feca6d064cb8cfbb372ca548105b86fd6c3d"}, + {file = "contourpy-1.1.1-cp312-cp312-win32.whl", hash = "sha256:059c3d2a94b930f4dafe8105bcdc1b21de99b30b51b5bce74c753686de858cb6"}, + {file = "contourpy-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f44d78b61740e4e8c71db1cf1fd56d9050a4747681c59ec1094750a658ceb970"}, + {file = "contourpy-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:70e5a10f8093d228bb2b552beeb318b8928b8a94763ef03b858ef3612b29395d"}, + {file = "contourpy-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8394e652925a18ef0091115e3cc191fef350ab6dc3cc417f06da66bf98071ae9"}, + {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5bd5680f844c3ff0008523a71949a3ff5e4953eb7701b28760805bc9bcff217"}, + {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66544f853bfa85c0d07a68f6c648b2ec81dafd30f272565c37ab47a33b220684"}, + {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0c02b75acfea5cab07585d25069207e478d12309557f90a61b5a3b4f77f46ce"}, + {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41339b24471c58dc1499e56783fedc1afa4bb018bcd035cfb0ee2ad2a7501ef8"}, + {file = "contourpy-1.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f29fb0b3f1217dfe9362ec55440d0743fe868497359f2cf93293f4b2701b8251"}, + {file = "contourpy-1.1.1-cp38-cp38-win32.whl", hash = "sha256:f9dc7f933975367251c1b34da882c4f0e0b2e24bb35dc906d2f598a40b72bfc7"}, + {file = "contourpy-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:498e53573e8b94b1caeb9e62d7c2d053c263ebb6aa259c81050766beb50ff8d9"}, + {file = "contourpy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ba42e3810999a0ddd0439e6e5dbf6d034055cdc72b7c5c839f37a7c274cb4eba"}, + {file = "contourpy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c06e4c6e234fcc65435223c7b2a90f286b7f1b2733058bdf1345d218cc59e34"}, + {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6fab080484e419528e98624fb5c4282148b847e3602dc8dbe0cb0669469887"}, + {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93df44ab351119d14cd1e6b52a5063d3336f0754b72736cc63db59307dabb718"}, + {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eafbef886566dc1047d7b3d4b14db0d5b7deb99638d8e1be4e23a7c7ac59ff0f"}, + {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efe0fab26d598e1ec07d72cf03eaeeba8e42b4ecf6b9ccb5a356fde60ff08b85"}, + {file = "contourpy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f08e469821a5e4751c97fcd34bcb586bc243c39c2e39321822060ba902eac49e"}, + {file = "contourpy-1.1.1-cp39-cp39-win32.whl", hash = "sha256:bfc8a5e9238232a45ebc5cb3bfee71f1167064c8d382cadd6076f0d51cff1da0"}, + {file = "contourpy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:c84fdf3da00c2827d634de4fcf17e3e067490c4aea82833625c4c8e6cdea0887"}, + {file = "contourpy-1.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:229a25f68046c5cf8067d6d6351c8b99e40da11b04d8416bf8d2b1d75922521e"}, + {file = "contourpy-1.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a10dab5ea1bd4401c9483450b5b0ba5416be799bbd50fc7a6cc5e2a15e03e8a3"}, + {file = "contourpy-1.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4f9147051cb8fdb29a51dc2482d792b3b23e50f8f57e3720ca2e3d438b7adf23"}, + {file = "contourpy-1.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a75cc163a5f4531a256f2c523bd80db509a49fc23721b36dd1ef2f60ff41c3cb"}, + {file = "contourpy-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b53d5769aa1f2d4ea407c65f2d1d08002952fac1d9e9d307aa2e1023554a163"}, + {file = "contourpy-1.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11b836b7dbfb74e049c302bbf74b4b8f6cb9d0b6ca1bf86cfa8ba144aedadd9c"}, + {file = "contourpy-1.1.1.tar.gz", hash = "sha256:96ba37c2e24b7212a77da85004c38e7c4d155d3e72a45eeaf22c1f03f607e8ab"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.16,<2.0", markers = "python_version <= \"3.11\""}, + {version = ">=1.26.0rc1,<2.0", markers = "python_version >= \"3.12\""}, +] + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.4.1)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "wurlitzer"] + +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "cyclic" +version = "1.0.0" +description = "Handle cyclic relations" +optional = false +python-versions = "*" +files = [ + {file = "cyclic-1.0.0-py3-none-any.whl", hash = "sha256:32d8181d7698f426bce6f14f4c3921ef95b6a84af9f96192b59beb05bc00c3ed"}, + {file = "cyclic-1.0.0.tar.gz", hash = "sha256:ecddd56cb831ee3e6b79f61ecb0ad71caee606c507136867782911aa01c3e5eb"}, +] + +[[package]] +name = "debugpy" +version = "1.8.0" +description = "An implementation of the Debug Adapter Protocol for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "debugpy-1.8.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:7fb95ca78f7ac43393cd0e0f2b6deda438ec7c5e47fa5d38553340897d2fbdfb"}, + {file = "debugpy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef9ab7df0b9a42ed9c878afd3eaaff471fce3fa73df96022e1f5c9f8f8c87ada"}, + {file = "debugpy-1.8.0-cp310-cp310-win32.whl", hash = "sha256:a8b7a2fd27cd9f3553ac112f356ad4ca93338feadd8910277aff71ab24d8775f"}, + {file = "debugpy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5d9de202f5d42e62f932507ee8b21e30d49aae7e46d5b1dd5c908db1d7068637"}, + {file = "debugpy-1.8.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:ef54404365fae8d45cf450d0544ee40cefbcb9cb85ea7afe89a963c27028261e"}, + {file = "debugpy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60009b132c91951354f54363f8ebdf7457aeb150e84abba5ae251b8e9f29a8a6"}, + {file = "debugpy-1.8.0-cp311-cp311-win32.whl", hash = "sha256:8cd0197141eb9e8a4566794550cfdcdb8b3db0818bdf8c49a8e8f8053e56e38b"}, + {file = "debugpy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:a64093656c4c64dc6a438e11d59369875d200bd5abb8f9b26c1f5f723622e153"}, + {file = "debugpy-1.8.0-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:b05a6b503ed520ad58c8dc682749113d2fd9f41ffd45daec16e558ca884008cd"}, + {file = "debugpy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c6fb41c98ec51dd010d7ed650accfd07a87fe5e93eca9d5f584d0578f28f35f"}, + {file = "debugpy-1.8.0-cp38-cp38-win32.whl", hash = "sha256:46ab6780159eeabb43c1495d9c84cf85d62975e48b6ec21ee10c95767c0590aa"}, + {file = "debugpy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:bdc5ef99d14b9c0fcb35351b4fbfc06ac0ee576aeab6b2511702e5a648a2e595"}, + {file = "debugpy-1.8.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:61eab4a4c8b6125d41a34bad4e5fe3d2cc145caecd63c3fe953be4cc53e65bf8"}, + {file = "debugpy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:125b9a637e013f9faac0a3d6a82bd17c8b5d2c875fb6b7e2772c5aba6d082332"}, + {file = "debugpy-1.8.0-cp39-cp39-win32.whl", hash = "sha256:57161629133113c97b387382045649a2b985a348f0c9366e22217c87b68b73c6"}, + {file = "debugpy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:e3412f9faa9ade82aa64a50b602544efcba848c91384e9f93497a458767e6926"}, + {file = "debugpy-1.8.0-py2.py3-none-any.whl", hash = "sha256:9c9b0ac1ce2a42888199df1a1906e45e6f3c9555497643a85e0bf2406e3ffbc4"}, + {file = "debugpy-1.8.0.zip", hash = "sha256:12af2c55b419521e33d5fb21bd022df0b5eb267c3e178f1d374a63a2a6bdccd0"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] + +[[package]] +name = "distlib" +version = "0.3.7" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.1.3" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "executing" +version = "2.0.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = "*" +files = [ + {file = "executing-2.0.0-py2.py3-none-any.whl", hash = "sha256:06df6183df67389625f4e763921c6cf978944721abf3e714000200aab95b0657"}, + {file = "executing-2.0.0.tar.gz", hash = "sha256:0ff053696fdeef426cda5bd18eacd94f82c91f49823a2e9090124212ceea9b08"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + +[[package]] +name = "fastjsonschema" +version = "2.18.1" +description = "Fastest Python implementation of JSON schema" +optional = false +python-versions = "*" +files = [ + {file = "fastjsonschema-2.18.1-py3-none-any.whl", hash = "sha256:aec6a19e9f66e9810ab371cc913ad5f4e9e479b63a7072a2cd060a9369e329a8"}, + {file = "fastjsonschema-2.18.1.tar.gz", hash = "sha256:06dc8680d937628e993fa0cd278f196d20449a1adc087640710846b324d422ea"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "filelock" +version = "3.12.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, + {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] +typing = ["typing-extensions (>=4.7.1)"] + +[[package]] +name = "fonttools" +version = "4.43.1" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.43.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bf11e2cca121df35e295bd34b309046c29476ee739753bc6bc9d5050de319273"}, + {file = "fonttools-4.43.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10b3922875ffcba636674f406f9ab9a559564fdbaa253d66222019d569db869c"}, + {file = "fonttools-4.43.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f727c3e3d08fd25352ed76cc3cb61486f8ed3f46109edf39e5a60fc9fecf6ca"}, + {file = "fonttools-4.43.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad0b3f6342cfa14be996971ea2b28b125ad681c6277c4cd0fbdb50340220dfb6"}, + {file = "fonttools-4.43.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b7ad05b2beeebafb86aa01982e9768d61c2232f16470f9d0d8e385798e37184"}, + {file = "fonttools-4.43.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c54466f642d2116686268c3e5f35ebb10e49b0d48d41a847f0e171c785f7ac7"}, + {file = "fonttools-4.43.1-cp310-cp310-win32.whl", hash = "sha256:1e09da7e8519e336239fbd375156488a4c4945f11c4c5792ee086dd84f784d02"}, + {file = "fonttools-4.43.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cf9e974f63b1080b1d2686180fc1fbfd3bfcfa3e1128695b5de337eb9075cef"}, + {file = "fonttools-4.43.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5db46659cfe4e321158de74c6f71617e65dc92e54980086823a207f1c1c0e24b"}, + {file = "fonttools-4.43.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1952c89a45caceedf2ab2506d9a95756e12b235c7182a7a0fff4f5e52227204f"}, + {file = "fonttools-4.43.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c36da88422e0270fbc7fd959dc9749d31a958506c1d000e16703c2fce43e3d0"}, + {file = "fonttools-4.43.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bbbf8174501285049e64d174e29f9578495e1b3b16c07c31910d55ad57683d8"}, + {file = "fonttools-4.43.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d4071bd1c183b8d0b368cc9ed3c07a0f6eb1bdfc4941c4c024c49a35429ac7cd"}, + {file = "fonttools-4.43.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d21099b411e2006d3c3e1f9aaf339e12037dbf7bf9337faf0e93ec915991f43b"}, + {file = "fonttools-4.43.1-cp311-cp311-win32.whl", hash = "sha256:b84a1c00f832feb9d0585ca8432fba104c819e42ff685fcce83537e2e7e91204"}, + {file = "fonttools-4.43.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a2f0aa6ca7c9bc1058a9d0b35483d4216e0c1bbe3962bc62ce112749954c7b8"}, + {file = "fonttools-4.43.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4d9740e3783c748521e77d3c397dc0662062c88fd93600a3c2087d3d627cd5e5"}, + {file = "fonttools-4.43.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:884ef38a5a2fd47b0c1291647b15f4e88b9de5338ffa24ee52c77d52b4dfd09c"}, + {file = "fonttools-4.43.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9648518ef687ba818db3fcc5d9aae27a369253ac09a81ed25c3867e8657a0680"}, + {file = "fonttools-4.43.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e974d70238fc2be5f444fa91f6347191d0e914d5d8ae002c9aa189572cc215"}, + {file = "fonttools-4.43.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:34f713dad41aa21c637b4e04fe507c36b986a40f7179dcc86402237e2d39dcd3"}, + {file = "fonttools-4.43.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:360201d46165fc0753229afe785900bc9596ee6974833124f4e5e9f98d0f592b"}, + {file = "fonttools-4.43.1-cp312-cp312-win32.whl", hash = "sha256:bb6d2f8ef81ea076877d76acfb6f9534a9c5f31dc94ba70ad001267ac3a8e56f"}, + {file = "fonttools-4.43.1-cp312-cp312-win_amd64.whl", hash = "sha256:25d3da8a01442cbc1106490eddb6d31d7dffb38c1edbfabbcc8db371b3386d72"}, + {file = "fonttools-4.43.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8da417431bfc9885a505e86ba706f03f598c85f5a9c54f67d63e84b9948ce590"}, + {file = "fonttools-4.43.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51669b60ee2a4ad6c7fc17539a43ffffc8ef69fd5dbed186a38a79c0ac1f5db7"}, + {file = "fonttools-4.43.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748015d6f28f704e7d95cd3c808b483c5fb87fd3eefe172a9da54746ad56bfb6"}, + {file = "fonttools-4.43.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7a58eb5e736d7cf198eee94844b81c9573102ae5989ebcaa1d1a37acd04b33d"}, + {file = "fonttools-4.43.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6bb5ea9076e0e39defa2c325fc086593ae582088e91c0746bee7a5a197be3da0"}, + {file = "fonttools-4.43.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5f37e31291bf99a63328668bb83b0669f2688f329c4c0d80643acee6e63cd933"}, + {file = "fonttools-4.43.1-cp38-cp38-win32.whl", hash = "sha256:9c60ecfa62839f7184f741d0509b5c039d391c3aff71dc5bc57b87cc305cff3b"}, + {file = "fonttools-4.43.1-cp38-cp38-win_amd64.whl", hash = "sha256:fe9b1ec799b6086460a7480e0f55c447b1aca0a4eecc53e444f639e967348896"}, + {file = "fonttools-4.43.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13a9a185259ed144def3682f74fdcf6596f2294e56fe62dfd2be736674500dba"}, + {file = "fonttools-4.43.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2adca1b46d69dce4a37eecc096fe01a65d81a2f5c13b25ad54d5430ae430b13"}, + {file = "fonttools-4.43.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18eefac1b247049a3a44bcd6e8c8fd8b97f3cad6f728173b5d81dced12d6c477"}, + {file = "fonttools-4.43.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2062542a7565091cea4cc14dd99feff473268b5b8afdee564f7067dd9fff5860"}, + {file = "fonttools-4.43.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18a2477c62a728f4d6e88c45ee9ee0229405e7267d7d79ce1f5ce0f3e9f8ab86"}, + {file = "fonttools-4.43.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a7a06f8d95b7496e53af80d974d63516ffb263a468e614978f3899a6df52d4b3"}, + {file = "fonttools-4.43.1-cp39-cp39-win32.whl", hash = "sha256:10003ebd81fec0192c889e63a9c8c63f88c7d72ae0460b7ba0cd2a1db246e5ad"}, + {file = "fonttools-4.43.1-cp39-cp39-win_amd64.whl", hash = "sha256:e117a92b07407a061cde48158c03587ab97e74e7d73cb65e6aadb17af191162a"}, + {file = "fonttools-4.43.1-py3-none-any.whl", hash = "sha256:4f88cae635bfe4bbbdc29d479a297bb525a94889184bb69fa9560c2d4834ddb9"}, + {file = "fonttools-4.43.1.tar.gz", hash = "sha256:17dbc2eeafb38d5d0e865dcce16e313c58265a6d2d20081c435f84dc5a9d8212"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "scipy"] +lxml = ["lxml (>=4.0,<5)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.0.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "fqdn" +version = "1.5.1" +description = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers" +optional = false +python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4" +files = [ + {file = "fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014"}, + {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, +] + +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +optional = false +python-versions = "*" +files = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "griffe" +version = "0.36.4" +description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." +optional = false +python-versions = ">=3.8" +files = [ + {file = "griffe-0.36.4-py3-none-any.whl", hash = "sha256:4e37a723891fa774fafdd67240571801a1d90d0236562c178707e5c37fb3ebe2"}, + {file = "griffe-0.36.4.tar.gz", hash = "sha256:7b5968f5cc6446637ed0d3ded9de07d6a928f10ccb24116b1dd843635bf1994a"}, +] + +[package.dependencies] +colorama = ">=0.4" + +[[package]] +name = "identify" +version = "2.5.30" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.30-py2.py3-none-any.whl", hash = "sha256:afe67f26ae29bab007ec21b03d4114f41316ab9dd15aa8736a167481e108da54"}, + {file = "identify-2.5.30.tar.gz", hash = "sha256:f302a4256a15c849b91cfcdcec052a8ce914634b2f77ae87dad29cd749f2d88d"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "ipykernel" +version = "6.25.2" +description = "IPython Kernel for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ipykernel-6.25.2-py3-none-any.whl", hash = "sha256:2e2ee359baba19f10251b99415bb39de1e97d04e1fab385646f24f0596510b77"}, + {file = "ipykernel-6.25.2.tar.gz", hash = "sha256:f468ddd1f17acb48c8ce67fcfa49ba6d46d4f9ac0438c1f441be7c3d1372230b"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "platform_system == \"Darwin\""} +comm = ">=0.1.1" +debugpy = ">=1.6.5" +ipython = ">=7.23.1" +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +matplotlib-inline = ">=0.1" +nest-asyncio = "*" +packaging = "*" +psutil = "*" +pyzmq = ">=20" +tornado = ">=6.1" +traitlets = ">=5.4.0" + +[package.extras] +cov = ["coverage[toml]", "curio", "matplotlib", "pytest-cov", "trio"] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] +pyqt5 = ["pyqt5"] +pyside6 = ["pyside6"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "ipython" +version = "8.16.1" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.9" +files = [ + {file = "ipython-8.16.1-py3-none-any.whl", hash = "sha256:0852469d4d579d9cd613c220af7bf0c9cc251813e12be647cb9d463939db9b1e"}, + {file = "ipython-8.16.1.tar.gz", hash = "sha256:ad52f58fca8f9f848e256c629eff888efc0528c12fe0f8ec14f33205f23ef938"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" + +[package.extras] +all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] + +[[package]] +name = "ipython-genutils" +version = "0.2.0" +description = "Vestigial utilities from IPython" +optional = false +python-versions = "*" +files = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] + +[[package]] +name = "ipywidgets" +version = "8.1.1" +description = "Jupyter interactive widgets" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ipywidgets-8.1.1-py3-none-any.whl", hash = "sha256:2b88d728656aea3bbfd05d32c747cfd0078f9d7e159cf982433b58ad717eed7f"}, + {file = "ipywidgets-8.1.1.tar.gz", hash = "sha256:40211efb556adec6fa450ccc2a77d59ca44a060f4f9f136833df59c9f538e6e8"}, +] + +[package.dependencies] +comm = ">=0.1.3" +ipython = ">=6.1.0" +jupyterlab-widgets = ">=3.0.9,<3.1.0" +traitlets = ">=4.3.1" +widgetsnbextension = ">=4.0.9,<4.1.0" + +[package.extras] +test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] + +[[package]] +name = "isoduration" +version = "20.11.0" +description = "Operations with ISO 8601 durations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042"}, + {file = "isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9"}, +] + +[package.dependencies] +arrow = ">=0.15.0" + +[[package]] +name = "jedi" +version = "0.19.1" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "json5" +version = "0.9.14" +description = "A Python implementation of the JSON5 data format." +optional = false +python-versions = "*" +files = [ + {file = "json5-0.9.14-py2.py3-none-any.whl", hash = "sha256:740c7f1b9e584a468dbb2939d8d458db3427f2c93ae2139d05f47e453eae964f"}, + {file = "json5-0.9.14.tar.gz", hash = "sha256:9ed66c3a6ca3510a976a9ef9b8c0787de24802724ab1860bc0153c7fdd589b02"}, +] + +[package.extras] +dev = ["hypothesis"] + +[[package]] +name = "jsonpointer" +version = "2.4" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, + {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, +] + +[[package]] +name = "jsonschema" +version = "4.19.1" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.19.1-py3-none-any.whl", hash = "sha256:cd5f1f9ed9444e554b38ba003af06c0a8c2868131e56bfbef0550fb450c0330e"}, + {file = "jsonschema-4.19.1.tar.gz", hash = "sha256:ec84cc37cfa703ef7cd4928db24f9cb31428a5d0fa77747b8b51a847458e0bbf"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +fqdn = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +idna = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +isoduration = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +jsonpointer = {version = ">1.13", optional = true, markers = "extra == \"format-nongpl\""} +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rfc3339-validator = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +rfc3986-validator = {version = ">0.1.0", optional = true, markers = "extra == \"format-nongpl\""} +rpds-py = ">=0.7.1" +uri-template = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +webcolors = {version = ">=1.11", optional = true, markers = "extra == \"format-nongpl\""} + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "jsonschema-specifications" +version = "2023.7.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.7.1-py3-none-any.whl", hash = "sha256:05adf340b659828a004220a9613be00fa3f223f2b82002e273dee62fd50524b1"}, + {file = "jsonschema_specifications-2023.7.1.tar.gz", hash = "sha256:c91a50404e88a1f6ba40636778e2ee08f6e24c5613fe4c53ac24578a5a7f72bb"}, +] + +[package.dependencies] +referencing = ">=0.28.0" + +[[package]] +name = "jupyter" +version = "1.0.0" +description = "Jupyter metapackage. Install all the Jupyter components in one go." +optional = false +python-versions = "*" +files = [ + {file = "jupyter-1.0.0-py2.py3-none-any.whl", hash = "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78"}, + {file = "jupyter-1.0.0.tar.gz", hash = "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"}, + {file = "jupyter-1.0.0.zip", hash = "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7"}, +] + +[package.dependencies] +ipykernel = "*" +ipywidgets = "*" +jupyter-console = "*" +nbconvert = "*" +notebook = "*" +qtconsole = "*" + +[[package]] +name = "jupyter-client" +version = "8.3.1" +description = "Jupyter protocol implementation and client libraries" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_client-8.3.1-py3-none-any.whl", hash = "sha256:5eb9f55eb0650e81de6b7e34308d8b92d04fe4ec41cd8193a913979e33d8e1a5"}, + {file = "jupyter_client-8.3.1.tar.gz", hash = "sha256:60294b2d5b869356c893f57b1a877ea6510d60d45cf4b38057f1672d85699ac9"}, +] + +[package.dependencies] +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +python-dateutil = ">=2.8.2" +pyzmq = ">=23.0" +tornado = ">=6.2" +traitlets = ">=5.3" + +[package.extras] +docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] + +[[package]] +name = "jupyter-console" +version = "6.6.3" +description = "Jupyter terminal console" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485"}, + {file = "jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539"}, +] + +[package.dependencies] +ipykernel = ">=6.14" +ipython = "*" +jupyter-client = ">=7.0.0" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +prompt-toolkit = ">=3.0.30" +pygments = "*" +pyzmq = ">=17" +traitlets = ">=5.4" + +[package.extras] +test = ["flaky", "pexpect", "pytest"] + +[[package]] +name = "jupyter-core" +version = "5.3.2" +description = "Jupyter core package. A base package on which Jupyter projects rely." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_core-5.3.2-py3-none-any.whl", hash = "sha256:a4af53c3fa3f6330cebb0d9f658e148725d15652811d1c32dc0f63bb96f2e6d6"}, + {file = "jupyter_core-5.3.2.tar.gz", hash = "sha256:0c28db6cbe2c37b5b398e1a1a5b22f84fd64cd10afc1f6c05b02fb09481ba45f"}, +] + +[package.dependencies] +platformdirs = ">=2.5" +pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = ">=5.3" + +[package.extras] +docs = ["myst-parser", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "jupyter-events" +version = "0.7.0" +description = "Jupyter Event System library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_events-0.7.0-py3-none-any.whl", hash = "sha256:4753da434c13a37c3f3c89b500afa0c0a6241633441421f6adafe2fb2e2b924e"}, + {file = "jupyter_events-0.7.0.tar.gz", hash = "sha256:7be27f54b8388c03eefea123a4f79247c5b9381c49fb1cd48615ee191eb12615"}, +] + +[package.dependencies] +jsonschema = {version = ">=4.18.0", extras = ["format-nongpl"]} +python-json-logger = ">=2.0.4" +pyyaml = ">=5.3" +referencing = "*" +rfc3339-validator = "*" +rfc3986-validator = ">=0.1.1" +traitlets = ">=5.3" + +[package.extras] +cli = ["click", "rich"] +docs = ["jupyterlite-sphinx", "myst-parser", "pydata-sphinx-theme", "sphinxcontrib-spelling"] +test = ["click", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "pytest-console-scripts", "rich"] + +[[package]] +name = "jupyter-lsp" +version = "2.2.0" +description = "Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter-lsp-2.2.0.tar.gz", hash = "sha256:8ebbcb533adb41e5d635eb8fe82956b0aafbf0fd443b6c4bfa906edeeb8635a1"}, + {file = "jupyter_lsp-2.2.0-py3-none-any.whl", hash = "sha256:9e06b8b4f7dd50300b70dd1a78c0c3b0c3d8fa68e0f2d8a5d1fbab62072aca3f"}, +] + +[package.dependencies] +jupyter-server = ">=1.1.2" + +[[package]] +name = "jupyter-server" +version = "2.7.3" +description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_server-2.7.3-py3-none-any.whl", hash = "sha256:8e4b90380b59d7a1e31086c4692231f2a2ea4cb269f5516e60aba72ce8317fc9"}, + {file = "jupyter_server-2.7.3.tar.gz", hash = "sha256:d4916c8581c4ebbc534cebdaa8eca2478d9f3bfdd88eae29fcab0120eac57649"}, +] + +[package.dependencies] +anyio = ">=3.1.0" +argon2-cffi = "*" +jinja2 = "*" +jupyter-client = ">=7.4.4" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +jupyter-events = ">=0.6.0" +jupyter-server-terminals = "*" +nbconvert = ">=6.4.4" +nbformat = ">=5.3.0" +overrides = "*" +packaging = "*" +prometheus-client = "*" +pywinpty = {version = "*", markers = "os_name == \"nt\""} +pyzmq = ">=24" +send2trash = ">=1.8.2" +terminado = ">=0.8.3" +tornado = ">=6.2.0" +traitlets = ">=5.6.0" +websocket-client = "*" + +[package.extras] +docs = ["ipykernel", "jinja2", "jupyter-client", "jupyter-server", "myst-parser", "nbformat", "prometheus-client", "pydata-sphinx-theme", "send2trash", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-openapi (>=0.8.0)", "sphinxcontrib-spelling", "sphinxemoji", "tornado", "typing-extensions"] +test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-scripts", "pytest-jupyter[server] (>=0.4)", "pytest-timeout", "requests"] + +[[package]] +name = "jupyter-server-terminals" +version = "0.4.4" +description = "A Jupyter Server Extension Providing Terminals." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_server_terminals-0.4.4-py3-none-any.whl", hash = "sha256:75779164661cec02a8758a5311e18bb8eb70c4e86c6b699403100f1585a12a36"}, + {file = "jupyter_server_terminals-0.4.4.tar.gz", hash = "sha256:57ab779797c25a7ba68e97bcfb5d7740f2b5e8a83b5e8102b10438041a7eac5d"}, +] + +[package.dependencies] +pywinpty = {version = ">=2.0.3", markers = "os_name == \"nt\""} +terminado = ">=0.8.3" + +[package.extras] +docs = ["jinja2", "jupyter-server", "mistune (<3.0)", "myst-parser", "nbformat", "packaging", "pydata-sphinx-theme", "sphinxcontrib-github-alt", "sphinxcontrib-openapi", "sphinxcontrib-spelling", "sphinxemoji", "tornado"] +test = ["coverage", "jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-cov", "pytest-jupyter[server] (>=0.5.3)", "pytest-timeout"] + +[[package]] +name = "jupyterlab" +version = "4.0.6" +description = "JupyterLab computational environment" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyterlab-4.0.6-py3-none-any.whl", hash = "sha256:7d9dacad1e3f30fe4d6d4efc97fda25fbb5012012b8f27cc03a2283abcdee708"}, + {file = "jupyterlab-4.0.6.tar.gz", hash = "sha256:6c43ae5a6a1fd2fdfafcb3454004958bde6da76331abb44cffc6f9e436b19ba1"}, +] + +[package.dependencies] +async-lru = ">=1.0.0" +ipykernel = "*" +jinja2 = ">=3.0.3" +jupyter-core = "*" +jupyter-lsp = ">=2.0.0" +jupyter-server = ">=2.4.0,<3" +jupyterlab-server = ">=2.19.0,<3" +notebook-shim = ">=0.2" +packaging = "*" +tomli = {version = "*", markers = "python_version < \"3.11\""} +tornado = ">=6.2.0" +traitlets = "*" + +[package.extras] +dev = ["black[jupyter] (==23.7.0)", "build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.0.286)"] +docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-tornasync", "sphinx (>=1.8,<7.2.0)", "sphinx-copybutton"] +docs-screenshots = ["altair (==5.0.1)", "ipython (==8.14.0)", "ipywidgets (==8.0.6)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.0.post0)", "matplotlib (==3.7.1)", "nbconvert (>=7.0.0)", "pandas (==2.0.2)", "scipy (==1.10.1)", "vega-datasets (==0.9.0)"] +test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "pytest-timeout", "pytest-tornasync", "requests", "requests-cache", "virtualenv"] + +[[package]] +name = "jupyterlab-pygments" +version = "0.2.2" +description = "Pygments theme using JupyterLab CSS variables" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyterlab_pygments-0.2.2-py2.py3-none-any.whl", hash = "sha256:2405800db07c9f770863bcf8049a529c3dd4d3e28536638bd7c1c01d2748309f"}, + {file = "jupyterlab_pygments-0.2.2.tar.gz", hash = "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d"}, +] + +[[package]] +name = "jupyterlab-server" +version = "2.25.0" +description = "A set of server components for JupyterLab and JupyterLab like applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyterlab_server-2.25.0-py3-none-any.whl", hash = "sha256:c9f67a98b295c5dee87f41551b0558374e45d449f3edca153dd722140630dcb2"}, + {file = "jupyterlab_server-2.25.0.tar.gz", hash = "sha256:77c2f1f282d610f95e496e20d5bf1d2a7706826dfb7b18f3378ae2870d272fb7"}, +] + +[package.dependencies] +babel = ">=2.10" +jinja2 = ">=3.0.3" +json5 = ">=0.9.0" +jsonschema = ">=4.18.0" +jupyter-server = ">=1.21,<3" +packaging = ">=21.3" +requests = ">=2.31" + +[package.extras] +docs = ["autodoc-traits", "jinja2 (<3.2.0)", "mistune (<4)", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinxcontrib-openapi (>0.8)"] +openapi = ["openapi-core (>=0.18.0,<0.19.0)", "ruamel-yaml"] +test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-validator (>=0.6.0,<0.7.0)", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "ruamel-yaml", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"] + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.9" +description = "Jupyter interactive widgets for JupyterLab" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyterlab_widgets-3.0.9-py3-none-any.whl", hash = "sha256:3cf5bdf5b897bf3bccf1c11873aa4afd776d7430200f765e0686bd352487b58d"}, + {file = "jupyterlab_widgets-3.0.9.tar.gz", hash = "sha256:6005a4e974c7beee84060fdfba341a3218495046de8ae3ec64888e5fe19fdb4c"}, +] + +[[package]] +name = "kiwisolver" +version = "1.4.5" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.7" +files = [ + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, + {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, +] + +[[package]] +name = "markdown" +version = "3.5" +description = "Python implementation of John Gruber's Markdown." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Markdown-3.5-py3-none-any.whl", hash = "sha256:4afb124395ce5fc34e6d9886dab977fd9ae987fc6e85689f08278cf0c69d4bf3"}, + {file = "Markdown-3.5.tar.gz", hash = "sha256:a807eb2e4778d9156c8f07876c6e4d50b5494c5665c4834f67b06459dfd877b3"}, +] + +[package.extras] +docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "matplotlib" +version = "3.8.0" +description = "Python plotting package" +optional = false +python-versions = ">=3.9" +files = [ + {file = "matplotlib-3.8.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c4940bad88a932ddc69734274f6fb047207e008389489f2b6f77d9ca485f0e7a"}, + {file = "matplotlib-3.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a33bd3045c7452ca1fa65676d88ba940867880e13e2546abb143035fa9072a9d"}, + {file = "matplotlib-3.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea6886e93401c22e534bbfd39201ce8931b75502895cfb115cbdbbe2d31f287"}, + {file = "matplotlib-3.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d670b9348e712ec176de225d425f150dc8e37b13010d85233c539b547da0be39"}, + {file = "matplotlib-3.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7b37b74f00c4cb6af908cb9a00779d97d294e89fd2145ad43f0cdc23f635760c"}, + {file = "matplotlib-3.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:0e723f5b96f3cd4aad99103dc93e9e3cdc4f18afdcc76951f4857b46f8e39d2d"}, + {file = "matplotlib-3.8.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5dc945a9cb2deb7d197ba23eb4c210e591d52d77bf0ba27c35fc82dec9fa78d4"}, + {file = "matplotlib-3.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8b5a1bf27d078453aa7b5b27f52580e16360d02df6d3dc9504f3d2ce11f6309"}, + {file = "matplotlib-3.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f25ffb6ad972cdffa7df8e5be4b1e3cadd2f8d43fc72085feb1518006178394"}, + {file = "matplotlib-3.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee482731c8c17d86d9ddb5194d38621f9b0f0d53c99006275a12523ab021732"}, + {file = "matplotlib-3.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:36eafe2128772195b373e1242df28d1b7ec6c04c15b090b8d9e335d55a323900"}, + {file = "matplotlib-3.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:061ee58facb3580cd2d046a6d227fb77e9295599c5ec6ad069f06b5821ad1cfc"}, + {file = "matplotlib-3.8.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3cc3776836d0f4f22654a7f2d2ec2004618d5cf86b7185318381f73b80fd8a2d"}, + {file = "matplotlib-3.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6c49a2bd6981264bddcb8c317b6bd25febcece9e2ebfcbc34e7f4c0c867c09dc"}, + {file = "matplotlib-3.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ed11654fc83cd6cfdf6170b453e437674a050a452133a064d47f2f1371f8d3"}, + {file = "matplotlib-3.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dae97fdd6996b3a25da8ee43e3fc734fff502f396801063c6b76c20b56683196"}, + {file = "matplotlib-3.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:87df75f528020a6299f76a1d986c0ed4406e3b2bd44bc5e306e46bca7d45e53e"}, + {file = "matplotlib-3.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:90d74a95fe055f73a6cd737beecc1b81c26f2893b7a3751d52b53ff06ca53f36"}, + {file = "matplotlib-3.8.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c3499c312f5def8f362a2bf761d04fa2d452b333f3a9a3f58805273719bf20d9"}, + {file = "matplotlib-3.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31e793c8bd4ea268cc5d3a695c27b30650ec35238626961d73085d5e94b6ab68"}, + {file = "matplotlib-3.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d5ee602ef517a89d1f2c508ca189cfc395dd0b4a08284fb1b97a78eec354644"}, + {file = "matplotlib-3.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5de39dc61ca35342cf409e031f70f18219f2c48380d3886c1cf5ad9f17898e06"}, + {file = "matplotlib-3.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dd386c80a98b5f51571b9484bf6c6976de383cd2a8cd972b6a9562d85c6d2087"}, + {file = "matplotlib-3.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:f691b4ef47c7384d0936b2e8ebdeb5d526c81d004ad9403dfb9d4c76b9979a93"}, + {file = "matplotlib-3.8.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0b11f354aae62a2aa53ec5bb09946f5f06fc41793e351a04ff60223ea9162955"}, + {file = "matplotlib-3.8.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f54b9fb87ca5acbcdd0f286021bedc162e1425fa5555ebf3b3dfc167b955ad9"}, + {file = "matplotlib-3.8.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:60a6e04dfd77c0d3bcfee61c3cd335fff1b917c2f303b32524cd1235e194ef99"}, + {file = "matplotlib-3.8.0.tar.gz", hash = "sha256:df8505e1c19d5c2c26aff3497a7cbd3ccfc2e97043d1e4db3e76afa399164b69"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.0.1" +numpy = ">=1.21,<2" +packaging = ">=20.0" +pillow = ">=6.2.0" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" +setuptools_scm = ">=7" + +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.5" +files = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mdx-include" +version = "1.4.2" +description = "Python Markdown extension to include local or remote files" +optional = false +python-versions = "*" +files = [ + {file = "mdx_include-1.4.2-py3-none-any.whl", hash = "sha256:cfbeadd59985f27a9b70cb7ab0a3d209892fe1bb1aa342df055e0b135b3c9f34"}, + {file = "mdx_include-1.4.2.tar.gz", hash = "sha256:992f9fbc492b5cf43f7d8cb4b90b52a4e4c5fdd7fd04570290a83eea5c84f297"}, +] + +[package.dependencies] +cyclic = "*" +Markdown = ">=2.6" +rcslice = ">=1.1.0" + +[[package]] +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +optional = false +python-versions = ">=3.6" +files = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] + +[[package]] +name = "mistune" +version = "3.0.2" +description = "A sane and fast Markdown parser with useful plugins and renderers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205"}, + {file = "mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8"}, +] + +[[package]] +name = "mkdocs" +version = "1.5.3" +description = "Project documentation with Markdown." +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"}, + {file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} +ghp-import = ">=1.0" +jinja2 = ">=2.11.1" +markdown = ">=3.2.1" +markupsafe = ">=2.0.1" +mergedeep = ">=1.3.4" +packaging = ">=20.5" +pathspec = ">=0.11.1" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +watchdog = ">=2.0" + +[package.extras] +i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] + +[[package]] +name = "mkdocs-autorefs" +version = "0.5.0" +description = "Automatically link across pages in MkDocs." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_autorefs-0.5.0-py3-none-any.whl", hash = "sha256:7930fcb8ac1249f10e683967aeaddc0af49d90702af111a5e390e8b20b3d97ff"}, + {file = "mkdocs_autorefs-0.5.0.tar.gz", hash = "sha256:9a5054a94c08d28855cfab967ada10ed5be76e2bfad642302a610b252c3274c0"}, +] + +[package.dependencies] +Markdown = ">=3.3" +mkdocs = ">=1.1" + +[[package]] +name = "mkdocs-gallery" +version = "0.7.8" +description = "a `mkdocs` plugin to generate example galleries from python scripts, similar to `sphinx-gallery`." +optional = false +python-versions = "*" +files = [ + {file = "mkdocs-gallery-0.7.8.tar.gz", hash = "sha256:281af6c9917643f70bd52f56a81e7bacc4b74a61391d5fa288054079c6a96294"}, + {file = "mkdocs_gallery-0.7.8-py2.py3-none-any.whl", hash = "sha256:d9b137b50cef78f6985eeb6117a29af5575399950724cf87cb93b65f1e5406ca"}, +] + +[package.dependencies] +mkdocs = ">=1,<2" +mkdocs-material = "*" +tqdm = "*" + +[[package]] +name = "mkdocs-material" +version = "9.4.4" +description = "Documentation that simply works" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_material-9.4.4-py3-none-any.whl", hash = "sha256:86fe79253afccc7f085f89a2d8e9e3300f82c4813d9b910d9081ce57a7e68380"}, + {file = "mkdocs_material-9.4.4.tar.gz", hash = "sha256:ab84a7cfaf009c47cd2926cdd7e6040b8cc12c3806cc533e8b16d57bd16d9c47"}, +] + +[package.dependencies] +babel = ">=2.10,<3.0" +colorama = ">=0.4,<1.0" +jinja2 = ">=3.0,<4.0" +markdown = ">=3.2,<4.0" +mkdocs = ">=1.5.3,<2.0" +mkdocs-material-extensions = ">=1.2,<2.0" +paginate = ">=0.5,<1.0" +pygments = ">=2.16,<3.0" +pymdown-extensions = ">=10.2,<11.0" +regex = ">=2022.4" +requests = ">=2.26,<3.0" + +[package.extras] +git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2,<2.0)"] +imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=9.4,<10.0)"] +recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.2" +description = "Extension pack for Python Markdown and MkDocs Material." +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs_material_extensions-1.2-py3-none-any.whl", hash = "sha256:c767bd6d6305f6420a50f0b541b0c9966d52068839af97029be14443849fb8a1"}, + {file = "mkdocs_material_extensions-1.2.tar.gz", hash = "sha256:27e2d1ed2d031426a6e10d5ea06989d67e90bb02acd588bc5673106b5ee5eedf"}, +] + +[[package]] +name = "mkdocstrings" +version = "0.23.0" +description = "Automatic documentation from sources, for MkDocs." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocstrings-0.23.0-py3-none-any.whl", hash = "sha256:051fa4014dfcd9ed90254ae91de2dbb4f24e166347dae7be9a997fe16316c65e"}, + {file = "mkdocstrings-0.23.0.tar.gz", hash = "sha256:d9c6a37ffbe7c14a7a54ef1258c70b8d394e6a33a1c80832bce40b9567138d1c"}, +] + +[package.dependencies] +Jinja2 = ">=2.11.1" +Markdown = ">=3.3" +MarkupSafe = ">=1.1" +mkdocs = ">=1.2" +mkdocs-autorefs = ">=0.3.1" +pymdown-extensions = ">=6.3" + +[package.extras] +crystal = ["mkdocstrings-crystal (>=0.3.4)"] +python = ["mkdocstrings-python (>=0.5.2)"] +python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] + +[[package]] +name = "mkdocstrings-python" +version = "1.7.2" +description = "A Python handler for mkdocstrings." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocstrings_python-1.7.2-py3-none-any.whl", hash = "sha256:2d005729a90f1b86d6d71fad4953d787140996adec5b00a25fafc6ee48e1b79a"}, + {file = "mkdocstrings_python-1.7.2.tar.gz", hash = "sha256:75b6af86f9dcdc2d864072d8fed5b1d45ad94dd2ce97843ef52ca87ad53d9b26"}, +] + +[package.dependencies] +griffe = ">=0.35" +mkdocstrings = ">=0.20" + +[[package]] +name = "mypy" +version = "1.5.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"}, + {file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"}, + {file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"}, + {file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"}, + {file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"}, + {file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"}, + {file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"}, + {file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"}, + {file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"}, + {file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"}, + {file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"}, + {file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"}, + {file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"}, + {file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"}, + {file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"}, + {file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"}, + {file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"}, + {file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nbclient" +version = "0.8.0" +description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "nbclient-0.8.0-py3-none-any.whl", hash = "sha256:25e861299e5303a0477568557c4045eccc7a34c17fc08e7959558707b9ebe548"}, + {file = "nbclient-0.8.0.tar.gz", hash = "sha256:f9b179cd4b2d7bca965f900a2ebf0db4a12ebff2f36a711cb66861e4ae158e55"}, +] + +[package.dependencies] +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +nbformat = ">=5.1" +traitlets = ">=5.4" + +[package.extras] +dev = ["pre-commit"] +docs = ["autodoc-traits", "mock", "moto", "myst-parser", "nbclient[test]", "sphinx (>=1.7)", "sphinx-book-theme", "sphinxcontrib-spelling"] +test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>=7.0.0)", "pytest (>=7.0)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"] + +[[package]] +name = "nbconvert" +version = "7.9.2" +description = "Converting Jupyter Notebooks" +optional = false +python-versions = ">=3.8" +files = [ + {file = "nbconvert-7.9.2-py3-none-any.whl", hash = "sha256:39fe4b8bdd1b0104fdd86fc8a43a9077ba64c720bda4c6132690d917a0a154ee"}, + {file = "nbconvert-7.9.2.tar.gz", hash = "sha256:e56cc7588acc4f93e2bb5a34ec69028e4941797b2bfaf6462f18a41d1cc258c9"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +bleach = "!=5.0.0" +defusedxml = "*" +jinja2 = ">=3.0" +jupyter-core = ">=4.7" +jupyterlab-pygments = "*" +markupsafe = ">=2.0" +mistune = ">=2.0.3,<4" +nbclient = ">=0.5.0" +nbformat = ">=5.7" +packaging = "*" +pandocfilters = ">=1.4.1" +pygments = ">=2.4.1" +tinycss2 = "*" +traitlets = ">=5.1" + +[package.extras] +all = ["nbconvert[docs,qtpdf,serve,test,webpdf]"] +docs = ["ipykernel", "ipython", "myst-parser", "nbsphinx (>=0.2.12)", "pydata-sphinx-theme", "sphinx (==5.0.2)", "sphinxcontrib-spelling"] +qtpdf = ["nbconvert[qtpng]"] +qtpng = ["pyqtwebengine (>=5.15)"] +serve = ["tornado (>=6.1)"] +test = ["flaky", "ipykernel", "ipywidgets (>=7)", "pytest", "pytest-dependency"] +webpdf = ["playwright"] + +[[package]] +name = "nbformat" +version = "5.9.2" +description = "The Jupyter Notebook format" +optional = false +python-versions = ">=3.8" +files = [ + {file = "nbformat-5.9.2-py3-none-any.whl", hash = "sha256:1c5172d786a41b82bcfd0c23f9e6b6f072e8fb49c39250219e4acfff1efe89e9"}, + {file = "nbformat-5.9.2.tar.gz", hash = "sha256:5f98b5ba1997dff175e77e0c17d5c10a96eaed2cbd1de3533d1fc35d5e111192"}, +] + +[package.dependencies] +fastjsonschema = "*" +jsonschema = ">=2.6" +jupyter-core = "*" +traitlets = ">=5.1" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["pep440", "pre-commit", "pytest", "testpath"] + +[[package]] +name = "nest-asyncio" +version = "1.5.8" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +files = [ + {file = "nest_asyncio-1.5.8-py3-none-any.whl", hash = "sha256:accda7a339a70599cb08f9dd09a67e0c2ef8d8d6f4c07f96ab203f2ae254e48d"}, + {file = "nest_asyncio-1.5.8.tar.gz", hash = "sha256:25aa2ca0d2a5b5531956b9e273b45cf664cae2b145101d73b86b199978d48fdb"}, +] + +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "notebook" +version = "7.0.4" +description = "Jupyter Notebook - A web-based notebook environment for interactive computing" +optional = false +python-versions = ">=3.8" +files = [ + {file = "notebook-7.0.4-py3-none-any.whl", hash = "sha256:ee738414ac01773c1ad6834cf76cc6f1ce140ac8197fd13b3e2d44d89e257f72"}, + {file = "notebook-7.0.4.tar.gz", hash = "sha256:0c1b458f72ce8774445c8ef9ed2492bd0b9ce9605ac996e2b066114f69795e71"}, +] + +[package.dependencies] +jupyter-server = ">=2.4.0,<3" +jupyterlab = ">=4.0.2,<5" +jupyterlab-server = ">=2.22.1,<3" +notebook-shim = ">=0.2,<0.3" +tornado = ">=6.2.0" + +[package.extras] +dev = ["hatch", "pre-commit"] +docs = ["myst-parser", "nbsphinx", "pydata-sphinx-theme", "sphinx (>=1.3.6)", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["importlib-resources (>=5.0)", "ipykernel", "jupyter-server[test] (>=2.4.0,<3)", "jupyterlab-server[test] (>=2.22.1,<3)", "nbval", "pytest (>=7.0)", "pytest-console-scripts", "pytest-timeout", "pytest-tornasync", "requests"] + +[[package]] +name = "notebook-shim" +version = "0.2.3" +description = "A shim layer for notebook traits and config" +optional = false +python-versions = ">=3.7" +files = [ + {file = "notebook_shim-0.2.3-py3-none-any.whl", hash = "sha256:a83496a43341c1674b093bfcebf0fe8e74cbe7eda5fd2bbc56f8e39e1486c0c7"}, + {file = "notebook_shim-0.2.3.tar.gz", hash = "sha256:f69388ac283ae008cd506dda10d0288b09a017d822d5e8c7129a152cbd3ce7e9"}, +] + +[package.dependencies] +jupyter-server = ">=1.8,<3" + +[package.extras] +test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync"] + +[[package]] +name = "numpy" +version = "1.26.0" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = "<3.13,>=3.9" +files = [ + {file = "numpy-1.26.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8db2f125746e44dce707dd44d4f4efeea8d7e2b43aace3f8d1f235cfa2733dd"}, + {file = "numpy-1.26.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0621f7daf973d34d18b4e4bafb210bbaf1ef5e0100b5fa750bd9cde84c7ac292"}, + {file = "numpy-1.26.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51be5f8c349fdd1a5568e72713a21f518e7d6707bcf8503b528b88d33b57dc68"}, + {file = "numpy-1.26.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:767254ad364991ccfc4d81b8152912e53e103ec192d1bb4ea6b1f5a7117040be"}, + {file = "numpy-1.26.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:436c8e9a4bdeeee84e3e59614d38c3dbd3235838a877af8c211cfcac8a80b8d3"}, + {file = "numpy-1.26.0-cp310-cp310-win32.whl", hash = "sha256:c2e698cb0c6dda9372ea98a0344245ee65bdc1c9dd939cceed6bb91256837896"}, + {file = "numpy-1.26.0-cp310-cp310-win_amd64.whl", hash = "sha256:09aaee96c2cbdea95de76ecb8a586cb687d281c881f5f17bfc0fb7f5890f6b91"}, + {file = "numpy-1.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:637c58b468a69869258b8ae26f4a4c6ff8abffd4a8334c830ffb63e0feefe99a"}, + {file = "numpy-1.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:306545e234503a24fe9ae95ebf84d25cba1fdc27db971aa2d9f1ab6bba19a9dd"}, + {file = "numpy-1.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c6adc33561bd1d46f81131d5352348350fc23df4d742bb246cdfca606ea1208"}, + {file = "numpy-1.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e062aa24638bb5018b7841977c360d2f5917268d125c833a686b7cbabbec496c"}, + {file = "numpy-1.26.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:546b7dd7e22f3c6861463bebb000646fa730e55df5ee4a0224408b5694cc6148"}, + {file = "numpy-1.26.0-cp311-cp311-win32.whl", hash = "sha256:c0b45c8b65b79337dee5134d038346d30e109e9e2e9d43464a2970e5c0e93229"}, + {file = "numpy-1.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:eae430ecf5794cb7ae7fa3808740b015aa80747e5266153128ef055975a72b99"}, + {file = "numpy-1.26.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:166b36197e9debc4e384e9c652ba60c0bacc216d0fc89e78f973a9760b503388"}, + {file = "numpy-1.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f042f66d0b4ae6d48e70e28d487376204d3cbf43b84c03bac57e28dac6151581"}, + {file = "numpy-1.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5e18e5b14a7560d8acf1c596688f4dfd19b4f2945b245a71e5af4ddb7422feb"}, + {file = "numpy-1.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f6bad22a791226d0a5c7c27a80a20e11cfe09ad5ef9084d4d3fc4a299cca505"}, + {file = "numpy-1.26.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4acc65dd65da28060e206c8f27a573455ed724e6179941edb19f97e58161bb69"}, + {file = "numpy-1.26.0-cp312-cp312-win32.whl", hash = "sha256:bb0d9a1aaf5f1cb7967320e80690a1d7ff69f1d47ebc5a9bea013e3a21faec95"}, + {file = "numpy-1.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:ee84ca3c58fe48b8ddafdeb1db87388dce2c3c3f701bf447b05e4cfcc3679112"}, + {file = "numpy-1.26.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a873a8180479bc829313e8d9798d5234dfacfc2e8a7ac188418189bb8eafbd2"}, + {file = "numpy-1.26.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:914b28d3215e0c721dc75db3ad6d62f51f630cb0c277e6b3bcb39519bed10bd8"}, + {file = "numpy-1.26.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c78a22e95182fb2e7874712433eaa610478a3caf86f28c621708d35fa4fd6e7f"}, + {file = "numpy-1.26.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f737708b366c36b76e953c46ba5827d8c27b7a8c9d0f471810728e5a2fe57c"}, + {file = "numpy-1.26.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b44e6a09afc12952a7d2a58ca0a2429ee0d49a4f89d83a0a11052da696440e49"}, + {file = "numpy-1.26.0-cp39-cp39-win32.whl", hash = "sha256:5671338034b820c8d58c81ad1dafc0ed5a00771a82fccc71d6438df00302094b"}, + {file = "numpy-1.26.0-cp39-cp39-win_amd64.whl", hash = "sha256:020cdbee66ed46b671429c7265cf00d8ac91c046901c55684954c3958525dab2"}, + {file = "numpy-1.26.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0792824ce2f7ea0c82ed2e4fecc29bb86bee0567a080dacaf2e0a01fe7654369"}, + {file = "numpy-1.26.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d484292eaeb3e84a51432a94f53578689ffdea3f90e10c8b203a99be5af57d8"}, + {file = "numpy-1.26.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:186ba67fad3c60dbe8a3abff3b67a91351100f2661c8e2a80364ae6279720299"}, + {file = "numpy-1.26.0.tar.gz", hash = "sha256:f93fc78fe8bf15afe2b8d6b6499f1c73953169fad1e9a8dd086cdff3190e7fdf"}, +] + +[[package]] +name = "overrides" +version = "7.4.0" +description = "A decorator to automatically detect mismatch when overriding a method." +optional = false +python-versions = ">=3.6" +files = [ + {file = "overrides-7.4.0-py3-none-any.whl", hash = "sha256:3ad24583f86d6d7a49049695efe9933e67ba62f0c7625d53c59fa832ce4b8b7d"}, + {file = "overrides-7.4.0.tar.gz", hash = "sha256:9502a3cca51f4fac40b5feca985b6703a5c1f6ad815588a7ca9e285b9dca6757"}, +] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "paginate" +version = "0.5.6" +description = "Divides large result sets into pages for easier browsing" +optional = false +python-versions = "*" +files = [ + {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"}, +] + +[[package]] +name = "pandas" +version = "2.1.1" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pandas-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58d997dbee0d4b64f3cb881a24f918b5f25dd64ddf31f467bb9b67ae4c63a1e4"}, + {file = "pandas-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02304e11582c5d090e5a52aec726f31fe3f42895d6bfc1f28738f9b64b6f0614"}, + {file = "pandas-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffa8f0966de2c22de408d0e322db2faed6f6e74265aa0856f3824813cf124363"}, + {file = "pandas-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1f84c144dee086fe4f04a472b5cd51e680f061adf75c1ae4fc3a9275560f8f4"}, + {file = "pandas-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ce97667d06d69396d72be074f0556698c7f662029322027c226fd7a26965cb"}, + {file = "pandas-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:4c3f32fd7c4dccd035f71734df39231ac1a6ff95e8bdab8d891167197b7018d2"}, + {file = "pandas-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e2959720b70e106bb1d8b6eadd8ecd7c8e99ccdbe03ee03260877184bb2877d"}, + {file = "pandas-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25e8474a8eb258e391e30c288eecec565bfed3e026f312b0cbd709a63906b6f8"}, + {file = "pandas-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8bd1685556f3374520466998929bade3076aeae77c3e67ada5ed2b90b4de7f0"}, + {file = "pandas-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc3657869c7902810f32bd072f0740487f9e030c1a3ab03e0af093db35a9d14e"}, + {file = "pandas-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:05674536bd477af36aa2effd4ec8f71b92234ce0cc174de34fd21e2ee99adbc2"}, + {file = "pandas-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:b407381258a667df49d58a1b637be33e514b07f9285feb27769cedb3ab3d0b3a"}, + {file = "pandas-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c747793c4e9dcece7bb20156179529898abf505fe32cb40c4052107a3c620b49"}, + {file = "pandas-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3bcad1e6fb34b727b016775bea407311f7721db87e5b409e6542f4546a4951ea"}, + {file = "pandas-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5ec7740f9ccb90aec64edd71434711f58ee0ea7f5ed4ac48be11cfa9abf7317"}, + {file = "pandas-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29deb61de5a8a93bdd033df328441a79fcf8dd3c12d5ed0b41a395eef9cd76f0"}, + {file = "pandas-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f99bebf19b7e03cf80a4e770a3e65eee9dd4e2679039f542d7c1ace7b7b1daa"}, + {file = "pandas-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:84e7e910096416adec68075dc87b986ff202920fb8704e6d9c8c9897fe7332d6"}, + {file = "pandas-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366da7b0e540d1b908886d4feb3d951f2f1e572e655c1160f5fde28ad4abb750"}, + {file = "pandas-2.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e50e72b667415a816ac27dfcfe686dc5a0b02202e06196b943d54c4f9c7693e"}, + {file = "pandas-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc1ab6a25da197f03ebe6d8fa17273126120874386b4ac11c1d687df288542dd"}, + {file = "pandas-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0dbfea0dd3901ad4ce2306575c54348d98499c95be01b8d885a2737fe4d7a98"}, + {file = "pandas-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0489b0e6aa3d907e909aef92975edae89b1ee1654db5eafb9be633b0124abe97"}, + {file = "pandas-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:4cdb0fab0400c2cb46dafcf1a0fe084c8bb2480a1fa8d81e19d15e12e6d4ded2"}, + {file = "pandas-2.1.1.tar.gz", hash = "sha256:fecb198dc389429be557cde50a2d46da8434a17fe37d7d41ff102e3987fd947b"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.1" + +[package.extras] +all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"] +aws = ["s3fs (>=2022.05.0)"] +clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"] +compression = ["zstandard (>=0.17.0)"] +computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"] +feather = ["pyarrow (>=7.0.0)"] +fss = ["fsspec (>=2022.05.0)"] +gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"] +hdf5 = ["tables (>=3.7.0)"] +html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"] +mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"] +parquet = ["pyarrow (>=7.0.0)"] +performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"] +plot = ["matplotlib (>=3.6.1)"] +postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"] +spss = ["pyreadstat (>=1.1.5)"] +sql-other = ["SQLAlchemy (>=1.4.36)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.8.0)"] + +[[package]] +name = "pandas-stubs" +version = "2.1.1.230928" +description = "Type annotations for pandas" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pandas_stubs-2.1.1.230928-py3-none-any.whl", hash = "sha256:992d97159e054ca3175ebe8321ac5616cf6502dd8218b03bb0eaf3c4f6939037"}, + {file = "pandas_stubs-2.1.1.230928.tar.gz", hash = "sha256:ce1691c71c5d67b8f332da87763f7f54650f46895d99964d588c3a5d79e2cacc"}, +] + +[package.dependencies] +numpy = {version = ">=1.26.0", markers = "python_version < \"3.13\""} +types-pytz = ">=2022.1.1" + +[[package]] +name = "pandocfilters" +version = "1.5.0" +description = "Utilities for writing pandoc filters in python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"}, + {file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"}, +] + +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "patsy" +version = "0.5.3" +description = "A Python package for describing statistical models and for building design matrices." +optional = false +python-versions = "*" +files = [ + {file = "patsy-0.5.3-py2.py3-none-any.whl", hash = "sha256:7eb5349754ed6aa982af81f636479b1b8db9d5b1a6e957a6016ec0534b5c86b7"}, + {file = "patsy-0.5.3.tar.gz", hash = "sha256:bdc18001875e319bc91c812c1eb6a10be4bb13cb81eb763f466179dca3b67277"}, +] + +[package.dependencies] +numpy = ">=1.4" +six = "*" + +[package.extras] +test = ["pytest", "pytest-cov", "scipy"] + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +optional = false +python-versions = "*" +files = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] + +[[package]] +name = "pillow" +version = "10.0.1" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Pillow-10.0.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a"}, + {file = "Pillow-10.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff"}, + {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf"}, + {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd"}, + {file = "Pillow-10.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0"}, + {file = "Pillow-10.0.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1"}, + {file = "Pillow-10.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2"}, + {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b"}, + {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1"}, + {file = "Pillow-10.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088"}, + {file = "Pillow-10.0.1-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b"}, + {file = "Pillow-10.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91"}, + {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4"}, + {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08"}, + {file = "Pillow-10.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08"}, + {file = "Pillow-10.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a"}, + {file = "Pillow-10.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7"}, + {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a"}, + {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7"}, + {file = "Pillow-10.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3"}, + {file = "Pillow-10.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849"}, + {file = "Pillow-10.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145"}, + {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2"}, + {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf"}, + {file = "Pillow-10.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d"}, + {file = "Pillow-10.0.1.tar.gz", hash = "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "platformdirs" +version = "3.11.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + +[[package]] +name = "pluggy" +version = "1.3.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "3.4.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pre_commit-3.4.0-py2.py3-none-any.whl", hash = "sha256:96d529a951f8b677f730a7212442027e8ba53f9b04d217c4c67dc56c393ad945"}, + {file = "pre_commit-3.4.0.tar.gz", hash = "sha256:6bbd5129a64cad4c0dfaeeb12cd8f7ea7e15b77028d985341478c8af3c759522"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "prometheus-client" +version = "0.17.1" +description = "Python client for the Prometheus monitoring system." +optional = false +python-versions = ">=3.6" +files = [ + {file = "prometheus_client-0.17.1-py3-none-any.whl", hash = "sha256:e537f37160f6807b8202a6fc4764cdd19bac5480ddd3e0d463c3002b34462101"}, + {file = "prometheus_client-0.17.1.tar.gz", hash = "sha256:21e674f39831ae3f8acde238afd9a27a37d0d2fb5a28ea094f0ce25d2cbf2091"}, +] + +[package.extras] +twisted = ["twisted"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.39" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, + {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psutil" +version = "5.9.5" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"}, + {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"}, + {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"}, + {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"}, + {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"}, + {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"}, + {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"}, + {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pydantic" +version = "2.4.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-2.4.2-py3-none-any.whl", hash = "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"}, + {file = "pydantic-2.4.2.tar.gz", hash = "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.10.1" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.10.1" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"}, + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"}, + {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"}, + {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"}, + {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"}, + {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"}, + {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"}, + {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"}, + {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"}, + {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"}, + {file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"}, + {file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"}, + {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"}, + {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"}, + {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"}, + {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"}, + {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pygments" +version = "2.16.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pymdown-extensions" +version = "10.3" +description = "Extension pack for Python Markdown." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pymdown_extensions-10.3-py3-none-any.whl", hash = "sha256:77a82c621c58a83efc49a389159181d570e370fff9f810d3a4766a75fc678b66"}, + {file = "pymdown_extensions-10.3.tar.gz", hash = "sha256:94a0d8a03246712b64698af223848fd80aaf1ae4c4be29c8c61939b0467b5722"}, +] + +[package.dependencies] +markdown = ">=3.2" +pyyaml = "*" + +[package.extras] +extra = ["pygments (>=2.12)"] + +[[package]] +name = "pyparsing" +version = "3.1.1" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, + {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "7.4.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-json-logger" +version = "2.0.7" +description = "A python library adding a json log formatter" +optional = false +python-versions = ">=3.6" +files = [ + {file = "python-json-logger-2.0.7.tar.gz", hash = "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c"}, + {file = "python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"}, +] + +[[package]] +name = "pytz" +version = "2023.3.post1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, +] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[[package]] +name = "pywinpty" +version = "2.0.12" +description = "Pseudo terminal support for Windows from Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pywinpty-2.0.12-cp310-none-win_amd64.whl", hash = "sha256:21319cd1d7c8844fb2c970fb3a55a3db5543f112ff9cfcd623746b9c47501575"}, + {file = "pywinpty-2.0.12-cp311-none-win_amd64.whl", hash = "sha256:853985a8f48f4731a716653170cd735da36ffbdc79dcb4c7b7140bce11d8c722"}, + {file = "pywinpty-2.0.12-cp312-none-win_amd64.whl", hash = "sha256:1617b729999eb6713590e17665052b1a6ae0ad76ee31e60b444147c5b6a35dca"}, + {file = "pywinpty-2.0.12-cp38-none-win_amd64.whl", hash = "sha256:189380469ca143d06e19e19ff3fba0fcefe8b4a8cc942140a6b863aed7eebb2d"}, + {file = "pywinpty-2.0.12-cp39-none-win_amd64.whl", hash = "sha256:7520575b6546db23e693cbd865db2764097bd6d4ef5dc18c92555904cd62c3d4"}, + {file = "pywinpty-2.0.12.tar.gz", hash = "sha256:8197de460ae8ebb7f5d1701dfa1b5df45b157bb832e92acba316305e18ca00dd"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +description = "A custom YAML tag for referencing environment variables in YAML files. " +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, + {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, +] + +[package.dependencies] +pyyaml = "*" + +[[package]] +name = "pyzmq" +version = "25.1.1" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:381469297409c5adf9a0e884c5eb5186ed33137badcbbb0560b86e910a2f1e76"}, + {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:955215ed0604dac5b01907424dfa28b40f2b2292d6493445dd34d0dfa72586a8"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:985bbb1316192b98f32e25e7b9958088431d853ac63aca1d2c236f40afb17c83"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afea96f64efa98df4da6958bae37f1cbea7932c35878b185e5982821bc883369"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76705c9325d72a81155bb6ab48d4312e0032bf045fb0754889133200f7a0d849"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:77a41c26205d2353a4c94d02be51d6cbdf63c06fbc1295ea57dad7e2d3381b71"}, + {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:12720a53e61c3b99d87262294e2b375c915fea93c31fc2336898c26d7aed34cd"}, + {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:57459b68e5cd85b0be8184382cefd91959cafe79ae019e6b1ae6e2ba8a12cda7"}, + {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:292fe3fc5ad4a75bc8df0dfaee7d0babe8b1f4ceb596437213821f761b4589f9"}, + {file = "pyzmq-25.1.1-cp310-cp310-win32.whl", hash = "sha256:35b5ab8c28978fbbb86ea54958cd89f5176ce747c1fb3d87356cf698048a7790"}, + {file = "pyzmq-25.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:11baebdd5fc5b475d484195e49bae2dc64b94a5208f7c89954e9e354fc609d8f"}, + {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:d20a0ddb3e989e8807d83225a27e5c2eb2260eaa851532086e9e0fa0d5287d83"}, + {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e1c1be77bc5fb77d923850f82e55a928f8638f64a61f00ff18a67c7404faf008"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d89528b4943d27029a2818f847c10c2cecc79fa9590f3cb1860459a5be7933eb"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90f26dc6d5f241ba358bef79be9ce06de58d477ca8485e3291675436d3827cf8"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2b92812bd214018e50b6380ea3ac0c8bb01ac07fcc14c5f86a5bb25e74026e9"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f957ce63d13c28730f7fd6b72333814221c84ca2421298f66e5143f81c9f91f"}, + {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:047a640f5c9c6ade7b1cc6680a0e28c9dd5a0825135acbd3569cc96ea00b2505"}, + {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7f7e58effd14b641c5e4dec8c7dab02fb67a13df90329e61c869b9cc607ef752"}, + {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c2910967e6ab16bf6fbeb1f771c89a7050947221ae12a5b0b60f3bca2ee19bca"}, + {file = "pyzmq-25.1.1-cp311-cp311-win32.whl", hash = "sha256:76c1c8efb3ca3a1818b837aea423ff8a07bbf7aafe9f2f6582b61a0458b1a329"}, + {file = "pyzmq-25.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:44e58a0554b21fc662f2712814a746635ed668d0fbc98b7cb9d74cb798d202e6"}, + {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:e1ffa1c924e8c72778b9ccd386a7067cddf626884fd8277f503c48bb5f51c762"}, + {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1af379b33ef33757224da93e9da62e6471cf4a66d10078cf32bae8127d3d0d4a"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cff084c6933680d1f8b2f3b4ff5bbb88538a4aac00d199ac13f49d0698727ecb"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2400a94f7dd9cb20cd012951a0cbf8249e3d554c63a9c0cdfd5cbb6c01d2dec"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d81f1ddae3858b8299d1da72dd7d19dd36aab654c19671aa8a7e7fb02f6638a"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:255ca2b219f9e5a3a9ef3081512e1358bd4760ce77828e1028b818ff5610b87b"}, + {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a882ac0a351288dd18ecae3326b8a49d10c61a68b01419f3a0b9a306190baf69"}, + {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:724c292bb26365659fc434e9567b3f1adbdb5e8d640c936ed901f49e03e5d32e"}, + {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ca1ed0bb2d850aa8471387882247c68f1e62a4af0ce9c8a1dbe0d2bf69e41fb"}, + {file = "pyzmq-25.1.1-cp312-cp312-win32.whl", hash = "sha256:b3451108ab861040754fa5208bca4a5496c65875710f76789a9ad27c801a0075"}, + {file = "pyzmq-25.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:eadbefd5e92ef8a345f0525b5cfd01cf4e4cc651a2cffb8f23c0dd184975d787"}, + {file = "pyzmq-25.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:db0b2af416ba735c6304c47f75d348f498b92952f5e3e8bff449336d2728795d"}, + {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c133e93b405eb0d36fa430c94185bdd13c36204a8635470cccc200723c13bb"}, + {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:273bc3959bcbff3f48606b28229b4721716598d76b5aaea2b4a9d0ab454ec062"}, + {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cbc8df5c6a88ba5ae385d8930da02201165408dde8d8322072e3e5ddd4f68e22"}, + {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:18d43df3f2302d836f2a56f17e5663e398416e9dd74b205b179065e61f1a6edf"}, + {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:73461eed88a88c866656e08f89299720a38cb4e9d34ae6bf5df6f71102570f2e"}, + {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:34c850ce7976d19ebe7b9d4b9bb8c9dfc7aac336c0958e2651b88cbd46682123"}, + {file = "pyzmq-25.1.1-cp36-cp36m-win32.whl", hash = "sha256:d2045d6d9439a0078f2a34b57c7b18c4a6aef0bee37f22e4ec9f32456c852c71"}, + {file = "pyzmq-25.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:458dea649f2f02a0b244ae6aef8dc29325a2810aa26b07af8374dc2a9faf57e3"}, + {file = "pyzmq-25.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7cff25c5b315e63b07a36f0c2bab32c58eafbe57d0dce61b614ef4c76058c115"}, + {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1579413ae492b05de5a6174574f8c44c2b9b122a42015c5292afa4be2507f28"}, + {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3d0a409d3b28607cc427aa5c30a6f1e4452cc44e311f843e05edb28ab5e36da0"}, + {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21eb4e609a154a57c520e3d5bfa0d97e49b6872ea057b7c85257b11e78068222"}, + {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:034239843541ef7a1aee0c7b2cb7f6aafffb005ede965ae9cbd49d5ff4ff73cf"}, + {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f8115e303280ba09f3898194791a153862cbf9eef722ad8f7f741987ee2a97c7"}, + {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1a5d26fe8f32f137e784f768143728438877d69a586ddeaad898558dc971a5ae"}, + {file = "pyzmq-25.1.1-cp37-cp37m-win32.whl", hash = "sha256:f32260e556a983bc5c7ed588d04c942c9a8f9c2e99213fec11a031e316874c7e"}, + {file = "pyzmq-25.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:abf34e43c531bbb510ae7e8f5b2b1f2a8ab93219510e2b287a944432fad135f3"}, + {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:87e34f31ca8f168c56d6fbf99692cc8d3b445abb5bfd08c229ae992d7547a92a"}, + {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c9c6c9b2c2f80747a98f34ef491c4d7b1a8d4853937bb1492774992a120f475d"}, + {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5619f3f5a4db5dbb572b095ea3cb5cc035335159d9da950830c9c4db2fbb6995"}, + {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5a34d2395073ef862b4032343cf0c32a712f3ab49d7ec4f42c9661e0294d106f"}, + {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25f0e6b78220aba09815cd1f3a32b9c7cb3e02cb846d1cfc526b6595f6046618"}, + {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3669cf8ee3520c2f13b2e0351c41fea919852b220988d2049249db10046a7afb"}, + {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2d163a18819277e49911f7461567bda923461c50b19d169a062536fffe7cd9d2"}, + {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:df27ffddff4190667d40de7beba4a950b5ce78fe28a7dcc41d6f8a700a80a3c0"}, + {file = "pyzmq-25.1.1-cp38-cp38-win32.whl", hash = "sha256:a382372898a07479bd34bda781008e4a954ed8750f17891e794521c3e21c2e1c"}, + {file = "pyzmq-25.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:52533489f28d62eb1258a965f2aba28a82aa747202c8fa5a1c7a43b5db0e85c1"}, + {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:03b3f49b57264909aacd0741892f2aecf2f51fb053e7d8ac6767f6c700832f45"}, + {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:330f9e188d0d89080cde66dc7470f57d1926ff2fb5576227f14d5be7ab30b9fa"}, + {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2ca57a5be0389f2a65e6d3bb2962a971688cbdd30b4c0bd188c99e39c234f414"}, + {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d457aed310f2670f59cc5b57dcfced452aeeed77f9da2b9763616bd57e4dbaae"}, + {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c56d748ea50215abef7030c72b60dd723ed5b5c7e65e7bc2504e77843631c1a6"}, + {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f03d3f0d01cb5a018debeb412441996a517b11c5c17ab2001aa0597c6d6882c"}, + {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:820c4a08195a681252f46926de10e29b6bbf3e17b30037bd4250d72dd3ddaab8"}, + {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17ef5f01d25b67ca8f98120d5fa1d21efe9611604e8eb03a5147360f517dd1e2"}, + {file = "pyzmq-25.1.1-cp39-cp39-win32.whl", hash = "sha256:04ccbed567171579ec2cebb9c8a3e30801723c575601f9a990ab25bcac6b51e2"}, + {file = "pyzmq-25.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:e61f091c3ba0c3578411ef505992d356a812fb200643eab27f4f70eed34a29ef"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ade6d25bb29c4555d718ac6d1443a7386595528c33d6b133b258f65f963bb0f6"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0c95ddd4f6e9fca4e9e3afaa4f9df8552f0ba5d1004e89ef0a68e1f1f9807c7"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48e466162a24daf86f6b5ca72444d2bf39a5e58da5f96370078be67c67adc978"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abc719161780932c4e11aaebb203be3d6acc6b38d2f26c0f523b5b59d2fc1996"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ccf825981640b8c34ae54231b7ed00271822ea1c6d8ba1090ebd4943759abf5"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c2f20ce161ebdb0091a10c9ca0372e023ce24980d0e1f810f519da6f79c60800"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:deee9ca4727f53464daf089536e68b13e6104e84a37820a88b0a057b97bba2d2"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aa8d6cdc8b8aa19ceb319aaa2b660cdaccc533ec477eeb1309e2a291eaacc43a"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:019e59ef5c5256a2c7378f2fb8560fc2a9ff1d315755204295b2eab96b254d0a"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b9af3757495c1ee3b5c4e945c1df7be95562277c6e5bccc20a39aec50f826cd0"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:548d6482dc8aadbe7e79d1b5806585c8120bafa1ef841167bc9090522b610fa6"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:057e824b2aae50accc0f9a0570998adc021b372478a921506fddd6c02e60308e"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2243700cc5548cff20963f0ca92d3e5e436394375ab8a354bbea2b12911b20b0"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79986f3b4af059777111409ee517da24a529bdbd46da578b33f25580adcff728"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:11d58723d44d6ed4dd677c5615b2ffb19d5c426636345567d6af82be4dff8a55"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:49d238cf4b69652257db66d0c623cd3e09b5d2e9576b56bc067a396133a00d4a"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fedbdc753827cf014c01dbbee9c3be17e5a208dcd1bf8641ce2cd29580d1f0d4"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc16ac425cc927d0a57d242589f87ee093884ea4804c05a13834d07c20db203c"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11c1d2aed9079c6b0c9550a7257a836b4a637feb334904610f06d70eb44c56d2"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e8a701123029cc240cea61dd2d16ad57cab4691804143ce80ecd9286b464d180"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61706a6b6c24bdece85ff177fec393545a3191eeda35b07aaa1458a027ad1304"}, + {file = "pyzmq-25.1.1.tar.gz", hash = "sha256:259c22485b71abacdfa8bf79720cd7bcf4b9d128b30ea554f01ae71fdbfdaa23"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + +[[package]] +name = "qtconsole" +version = "5.4.4" +description = "Jupyter Qt console" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "qtconsole-5.4.4-py3-none-any.whl", hash = "sha256:a3b69b868e041c2c698bdc75b0602f42e130ffb256d6efa48f9aa756c97672aa"}, + {file = "qtconsole-5.4.4.tar.gz", hash = "sha256:b7ffb53d74f23cee29f4cdb55dd6fabc8ec312d94f3c46ba38e1dde458693dfb"}, +] + +[package.dependencies] +ipykernel = ">=4.1" +ipython-genutils = "*" +jupyter-client = ">=4.1" +jupyter-core = "*" +packaging = "*" +pygments = "*" +pyzmq = ">=17.1" +qtpy = ">=2.4.0" +traitlets = "<5.2.1 || >5.2.1,<5.2.2 || >5.2.2" + +[package.extras] +doc = ["Sphinx (>=1.3)"] +test = ["flaky", "pytest", "pytest-qt"] + +[[package]] +name = "qtpy" +version = "2.4.0" +description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." +optional = false +python-versions = ">=3.7" +files = [ + {file = "QtPy-2.4.0-py3-none-any.whl", hash = "sha256:4d4f045a41e09ac9fa57fcb47ef05781aa5af294a0a646acc1b729d14225e741"}, + {file = "QtPy-2.4.0.tar.gz", hash = "sha256:db2d508167aa6106781565c8da5c6f1487debacba33519cedc35fa8997d424d4"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] + +[[package]] +name = "rcslice" +version = "1.1.0" +description = "Slice a list of sliceables (1 indexed, start and end index both are inclusive)" +optional = false +python-versions = "*" +files = [ + {file = "rcslice-1.1.0-py3-none-any.whl", hash = "sha256:1b12fc0c0ca452e8a9fd2b56ac008162f19e250906a4290a7e7a98be3200c2a6"}, + {file = "rcslice-1.1.0.tar.gz", hash = "sha256:a2ce70a60690eb63e52b722e046b334c3aaec5e900b28578f529878782ee5c6e"}, +] + +[[package]] +name = "referencing" +version = "0.30.2" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "referencing-0.30.2-py3-none-any.whl", hash = "sha256:449b6669b6121a9e96a7f9e410b245d471e8d48964c67113ce9afe50c8dd7bdf"}, + {file = "referencing-0.30.2.tar.gz", hash = "sha256:794ad8003c65938edcdbc027f1933215e0d0ccc0291e3ce20a4d87432b59efc0"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" + +[[package]] +name = "regex" +version = "2023.10.3" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.7" +files = [ + {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, + {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, + {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, + {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, + {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, + {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, + {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, + {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, + {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, + {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, + {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, + {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, + {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, + {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, + {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, + {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +description = "A pure python RFC3339 validator" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"}, + {file = "rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +description = "Pure python rfc3986 validator" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9"}, + {file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"}, +] + +[[package]] +name = "rpds-py" +version = "0.10.4" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rpds_py-0.10.4-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:e41824343c2c129599645373992b1ce17720bb8a514f04ff9567031e1c26951e"}, + {file = "rpds_py-0.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b9d8884d58ea8801e5906a491ab34af975091af76d1a389173db491ee7e316bb"}, + {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5db93f9017b384a4f194e1d89e1ce82d0a41b1fafdbbd3e0c8912baf13f2950f"}, + {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c31ecfc53ac03dad4928a1712f3a2893008bfba1b3cde49e1c14ff67faae2290"}, + {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f92d2372ec992c82fd7c74aa21e2a1910b3dcdc6a7e6392919a138f21d528a3"}, + {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7ea49ddf51d5ec0c3cbd95190dd15e077a3153c8d4b22a33da43b5dd2b3c640"}, + {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c27942722cd5039bbf5098c7e21935a96243fed00ea11a9589f3c6c6424bd84"}, + {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:08f07150c8ebbdbce1d2d51b8e9f4d588749a2af6a98035485ebe45c7ad9394e"}, + {file = "rpds_py-0.10.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f3331a3684192659fa1090bf2b448db928152fcba08222e58106f44758ef25f7"}, + {file = "rpds_py-0.10.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:efffa359cc69840c8793f0c05a7b663de6afa7b9078fa6c80309ee38b9db677d"}, + {file = "rpds_py-0.10.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:86e8d6ff15fa7a9590c0addaf3ce52fb58bda4299cab2c2d0afa404db6848dab"}, + {file = "rpds_py-0.10.4-cp310-none-win32.whl", hash = "sha256:8f90fc6dd505867514c8b8ef68a712dc0be90031a773c1ae2ad469f04062daef"}, + {file = "rpds_py-0.10.4-cp310-none-win_amd64.whl", hash = "sha256:9f9184744fb800c9f28e155a5896ecb54816296ee79d5d1978be6a2ae60f53c4"}, + {file = "rpds_py-0.10.4-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:72e9b1e92830c876cd49565d8404e4dcc9928302d348ea2517bc3f9e3a873a2a"}, + {file = "rpds_py-0.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3650eae998dc718960e90120eb45d42bd57b18b21b10cb9ee05f91bff2345d48"}, + {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f40413d2859737ce6d95c29ce2dde0ef7cdc3063b5830ae4342fef5922c3bba7"}, + {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b953d11b544ca5f2705bb77b177d8e17ab1bfd69e0fd99790a11549d2302258c"}, + {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28b4942ec7d9d6114c1e08cace0157db92ef674636a38093cab779ace5742d3a"}, + {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e0e2e01c5f61ddf47e3ed2d1fe1c9136e780ca6222d57a2517b9b02afd4710c"}, + {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:927e3461dae0c09b1f2e0066e50c1a9204f8a64a3060f596e9a6742d3b307785"}, + {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e69bbe0ede8f7fe2616e779421bbdb37f025c802335a90f6416e4d98b368a37"}, + {file = "rpds_py-0.10.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cc688a59c100f038fa9fec9e4ab457c2e2d1fca350fe7ea395016666f0d0a2dc"}, + {file = "rpds_py-0.10.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ec001689402b9104700b50a005c2d3d0218eae90eaa8bdbbd776fe78fe8a74b7"}, + {file = "rpds_py-0.10.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:628fbb8be71a103499d10b189af7764996ab2634ed7b44b423f1e19901606e0e"}, + {file = "rpds_py-0.10.4-cp311-none-win32.whl", hash = "sha256:e3f9c9e5dd8eba4768e15f19044e1b5e216929a43a54b4ab329e103aed9f3eda"}, + {file = "rpds_py-0.10.4-cp311-none-win_amd64.whl", hash = "sha256:3bc561c183684636c0099f9c3fbab8c1671841942edbce784bb01b4707d17924"}, + {file = "rpds_py-0.10.4-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:36ff30385fb9fb3ac23a28bffdd4a230a5229ed5b15704b708b7c84bfb7fce51"}, + {file = "rpds_py-0.10.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db0589e0bf41ff6ce284ab045ca89f27be1adf19e7bce26c2e7de6739a70c18b"}, + {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c330cb125983c5d380fef4a4155248a276297c86d64625fdaf500157e1981c"}, + {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d230fddc60caced271cc038e43e6fb8f4dd6b2dbaa44ac9763f2d76d05b0365a"}, + {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a9e864ec051a58fdb6bb2e6da03942adb20273897bc70067aee283e62bbac4d"}, + {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e41d5b334e8de4bc3f38843f31b2afa9a0c472ebf73119d3fd55cde08974bdf"}, + {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bb3f3cb6072c73e6ec1f865d8b80419b599f1597acf33f63fbf02252aab5a03"}, + {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:576d48e1e45c211e99fc02655ade65c32a75d3e383ccfd98ce59cece133ed02c"}, + {file = "rpds_py-0.10.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b28b9668a22ca2cfca4433441ba9acb2899624a323787a509a3dc5fbfa79c49d"}, + {file = "rpds_py-0.10.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ddbd113a37307638f94be5ae232a325155fd24dbfae2c56455da8724b471e7be"}, + {file = "rpds_py-0.10.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd0ad98c7d72b0e4cbfe89cdfa12cd07d2fd6ed22864341cdce12b318a383442"}, + {file = "rpds_py-0.10.4-cp312-none-win32.whl", hash = "sha256:2a97406d5e08b7095428f01dac0d3c091dc072351151945a167e7968d2755559"}, + {file = "rpds_py-0.10.4-cp312-none-win_amd64.whl", hash = "sha256:aab24b9bbaa3d49e666e9309556591aa00748bd24ea74257a405f7fed9e8b10d"}, + {file = "rpds_py-0.10.4-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6c5ca3eb817fb54bfd066740b64a2b31536eb8fe0b183dc35b09a7bd628ed680"}, + {file = "rpds_py-0.10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fd37ab9a24021821b715478357af1cf369d5a42ac7405e83e5822be00732f463"}, + {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2573ec23ad3a59dd2bc622befac845695972f3f2d08dc1a4405d017d20a6c225"}, + {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:362faeae52dc6ccc50c0b6a01fa2ec0830bb61c292033f3749a46040b876f4ba"}, + {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40f6e53461b19ddbb3354fe5bcf3d50d4333604ae4bf25b478333d83ca68002c"}, + {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6090ba604ea06b525a231450ae5d343917a393cbf50423900dea968daf61d16f"}, + {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28e29dac59df890972f73c511948072897f512974714a803fe793635b80ff8c7"}, + {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f82abb5c5b83dc30e96be99ce76239a030b62a73a13c64410e429660a5602bfd"}, + {file = "rpds_py-0.10.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a3628815fd170a64624001bfb4e28946fd515bd672e68a1902d9e0290186eaf3"}, + {file = "rpds_py-0.10.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d37f27ad80f742ef82796af3fe091888864958ad0bc8bab03da1830fa00c6004"}, + {file = "rpds_py-0.10.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:255a23bded80605e9f3997753e3a4b89c9aec9efb07ec036b1ca81440efcc1a9"}, + {file = "rpds_py-0.10.4-cp38-none-win32.whl", hash = "sha256:049098dabfe705e9638c55a3321137a821399c50940041a6fcce267a22c70db2"}, + {file = "rpds_py-0.10.4-cp38-none-win_amd64.whl", hash = "sha256:aa45cc71bf23a3181b8aa62466b5a2b7b7fb90fdc01df67ca433cd4fce7ec94d"}, + {file = "rpds_py-0.10.4-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:3507c459767cf24c11e9520e2a37c89674266abe8e65453e5cb66398aa47ee7b"}, + {file = "rpds_py-0.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2603e084054351cc65097da326570102c4c5bd07426ba8471ceaefdb0b642cc9"}, + {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0f1d336786cb62613c72c00578c98e5bb8cd57b49c5bae5d4ab906ca7872f98"}, + {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf032367f921201deaecf221d4cc895ea84b3decf50a9c73ee106f961885a0ad"}, + {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f050ceffd8c730c1619a16bbf0b9cd037dcdb94b54710928ba38c7bde67e4a4"}, + {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8709eb4ab477c533b7d0a76cd3065d7d95c9e25e6b9f6e27caeeb8c63e8799c9"}, + {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc20dadb102140dff63529e08ce6f9745dbd36e673ebb2b1c4a63e134bca81c2"}, + {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cd7da2adc721ccf19ac7ec86cae3a4fcaba03d9c477d5bd64ded6e9bb817bf3f"}, + {file = "rpds_py-0.10.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e5dba1c11e089b526379e74f6c636202e4c5bad9a48c7416502b8a5b0d026c91"}, + {file = "rpds_py-0.10.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ffd539d213c1ea2989ab92a5b9371ae7159c8c03cf2bcb9f2f594752f755ecd3"}, + {file = "rpds_py-0.10.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e791e3d13b14d0a7921804d0efe4d7bd15508bbcf8cb7a0c1ee1a27319a5f033"}, + {file = "rpds_py-0.10.4-cp39-none-win32.whl", hash = "sha256:2f2ac8bb01f705c5caaa7fe77ffd9b03f92f1b5061b94228f6ea5eaa0fca68ad"}, + {file = "rpds_py-0.10.4-cp39-none-win_amd64.whl", hash = "sha256:7c7ca791bedda059e5195cf7c6b77384657a51429357cdd23e64ac1d4973d6dc"}, + {file = "rpds_py-0.10.4-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:9c7e7bd1fa1f535af71dfcd3700fc83a6dc261a1204f8f5327d8ffe82e52905d"}, + {file = "rpds_py-0.10.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7089d8bfa8064b28b2e39f5af7bf12d42f61caed884e35b9b4ea9e6fb1175077"}, + {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1f191befea279cb9669b57be97ab1785781c8bab805900e95742ebfaa9cbf1d"}, + {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:98c0aecf661c175ce9cb17347fc51a5c98c3e9189ca57e8fcd9348dae18541db"}, + {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d81359911c3bb31c899c6a5c23b403bdc0279215e5b3bc0d2a692489fed38632"}, + {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:83da147124499fe41ed86edf34b4e81e951b3fe28edcc46288aac24e8a5c8484"}, + {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49db6c0a0e6626c2b97f5e7f8f7074da21cbd8ec73340c25e839a2457c007efa"}, + {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:125776d5db15162fdd9135372bef7fe4fb7c5f5810cf25898eb74a06a0816aec"}, + {file = "rpds_py-0.10.4-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:32819b662e3b4c26355a4403ea2f60c0a00db45b640fe722dd12db3d2ef807fb"}, + {file = "rpds_py-0.10.4-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:3bd38b80491ef9686f719c1ad3d24d14fbd0e069988fdd4e7d1a6ffcdd7f4a13"}, + {file = "rpds_py-0.10.4-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2e79eeeff8394284b09577f36316d410525e0cf0133abb3de10660e704d3d38e"}, + {file = "rpds_py-0.10.4-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3e37f1f134037601eb4b1f46854194f0cc082435dac2ee3de11e51529f7831f2"}, + {file = "rpds_py-0.10.4-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:ba3246c60303eab3d0e562addf25a983d60bddc36f4d1edc2510f056d19df255"}, + {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9123ba0f3f98ff79780eebca9984a2b525f88563844b740f94cffb9099701230"}, + {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d98802b78093c7083cc51f83da41a5be5a57d406798c9f69424bd75f8ae0812a"}, + {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:58bae860d1d116e6b4e1aad0cdc48a187d5893994f56d26db0c5534df7a47afd"}, + {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd7e62e7d5bcfa38a62d8397fba6d0428b970ab7954c2197501cd1624f7f0bbb"}, + {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83f5228459b84fa6279e4126a53abfdd73cd9cc183947ee5084153880f65d7"}, + {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4bcb1abecd998a72ad4e36a0fca93577fd0c059a6aacc44f16247031b98f6ff4"}, + {file = "rpds_py-0.10.4-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:9e7b3ad9f53ea9e085b3d27286dd13f8290969c0a153f8a52c8b5c46002c374b"}, + {file = "rpds_py-0.10.4-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:cbec8e43cace64e63398155dc585dc479a89fef1e57ead06c22d3441e1bd09c3"}, + {file = "rpds_py-0.10.4-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ad21c60fc880204798f320387164dcacc25818a7b4ec2a0bf6b6c1d57b007d23"}, + {file = "rpds_py-0.10.4-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:6baea8a4f6f01e69e75cfdef3edd4a4d1c4b56238febbdf123ce96d09fbff010"}, + {file = "rpds_py-0.10.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:94876c21512535955a960f42a155213315e6ab06a4ce8ce372341a2a1b143eeb"}, + {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cb55454a20d1b935f9eaab52e6ceab624a2efd8b52927c7ae7a43e02828dbe0"}, + {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13cbd79ccedc6b39c279af31ebfb0aec0467ad5d14641ddb15738bf6e4146157"}, + {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00a88003db3cc953f8656b59fc9af9d0637a1fb93c235814007988f8c153b2f2"}, + {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0f7f77a77c37159c9f417b8dd847f67a29e98c6acb52ee98fc6b91efbd1b2b6"}, + {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70563a1596d2e0660ca2cebb738443437fc0e38597e7cbb276de0a7363924a52"}, + {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3ece9aa6d07e18c966f14b4352a4c6f40249f6174d3d2c694c1062e19c6adbb"}, + {file = "rpds_py-0.10.4-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d5ad7b1a1f6964d19b1a8acfc14bf7864f39587b3e25c16ca04f6cd1815026b3"}, + {file = "rpds_py-0.10.4-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:60018626e637528a1fa64bb3a2b3e46ab7bf672052316d61c3629814d5e65052"}, + {file = "rpds_py-0.10.4-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ae8a32ab77a84cc870bbfb60645851ca0f7d58fd251085ad67464b1445d632ca"}, + {file = "rpds_py-0.10.4.tar.gz", hash = "sha256:18d5ff7fbd305a1d564273e9eb22de83ae3cd9cd6329fddc8f12f6428a711a6a"}, +] + +[[package]] +name = "ruff" +version = "0.0.292" +description = "An extremely fast Python linter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.0.292-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:02f29db018c9d474270c704e6c6b13b18ed0ecac82761e4fcf0faa3728430c96"}, + {file = "ruff-0.0.292-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:69654e564342f507edfa09ee6897883ca76e331d4bbc3676d8a8403838e9fade"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c3c91859a9b845c33778f11902e7b26440d64b9d5110edd4e4fa1726c41e0a4"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4476f1243af2d8c29da5f235c13dca52177117935e1f9393f9d90f9833f69e4"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be8eb50eaf8648070b8e58ece8e69c9322d34afe367eec4210fdee9a555e4ca7"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9889bac18a0c07018aac75ef6c1e6511d8411724d67cb879103b01758e110a81"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bdfabd4334684a4418b99b3118793f2c13bb67bf1540a769d7816410402a205"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7c77c53bfcd75dbcd4d1f42d6cabf2485d2e1ee0678da850f08e1ab13081a8"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e087b24d0d849c5c81516ec740bf4fd48bf363cfb104545464e0fca749b6af9"}, + {file = "ruff-0.0.292-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f160b5ec26be32362d0774964e218f3fcf0a7da299f7e220ef45ae9e3e67101a"}, + {file = "ruff-0.0.292-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ac153eee6dd4444501c4bb92bff866491d4bfb01ce26dd2fff7ca472c8df9ad0"}, + {file = "ruff-0.0.292-py3-none-musllinux_1_2_i686.whl", hash = "sha256:87616771e72820800b8faea82edd858324b29bb99a920d6aa3d3949dd3f88fb0"}, + {file = "ruff-0.0.292-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b76deb3bdbea2ef97db286cf953488745dd6424c122d275f05836c53f62d4016"}, + {file = "ruff-0.0.292-py3-none-win32.whl", hash = "sha256:e854b05408f7a8033a027e4b1c7f9889563dd2aca545d13d06711e5c39c3d003"}, + {file = "ruff-0.0.292-py3-none-win_amd64.whl", hash = "sha256:f27282bedfd04d4c3492e5c3398360c9d86a295be00eccc63914438b4ac8a83c"}, + {file = "ruff-0.0.292-py3-none-win_arm64.whl", hash = "sha256:7f67a69c8f12fbc8daf6ae6d36705037bde315abf8b82b6e1f4c9e74eb750f68"}, + {file = "ruff-0.0.292.tar.gz", hash = "sha256:1093449e37dd1e9b813798f6ad70932b57cf614e5c2b5c51005bf67d55db33ac"}, +] + +[[package]] +name = "scipy" +version = "1.11.3" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = "<3.13,>=3.9" +files = [ + {file = "scipy-1.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:370f569c57e1d888304052c18e58f4a927338eafdaef78613c685ca2ea0d1fa0"}, + {file = "scipy-1.11.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:9885e3e4f13b2bd44aaf2a1a6390a11add9f48d5295f7a592393ceb8991577a3"}, + {file = "scipy-1.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e04aa19acc324a1a076abb4035dabe9b64badb19f76ad9c798bde39d41025cdc"}, + {file = "scipy-1.11.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e1a8a4657673bfae1e05e1e1d6e94b0cabe5ed0c7c144c8aa7b7dbb774ce5c1"}, + {file = "scipy-1.11.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7abda0e62ef00cde826d441485e2e32fe737bdddee3324e35c0e01dee65e2a88"}, + {file = "scipy-1.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:033c3fd95d55012dd1148b201b72ae854d5086d25e7c316ec9850de4fe776929"}, + {file = "scipy-1.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:925c6f09d0053b1c0f90b2d92d03b261e889b20d1c9b08a3a51f61afc5f58165"}, + {file = "scipy-1.11.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5664e364f90be8219283eeb844323ff8cd79d7acbd64e15eb9c46b9bc7f6a42a"}, + {file = "scipy-1.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00f325434b6424952fbb636506f0567898dca7b0f7654d48f1c382ea338ce9a3"}, + {file = "scipy-1.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f290cf561a4b4edfe8d1001ee4be6da60c1c4ea712985b58bf6bc62badee221"}, + {file = "scipy-1.11.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:91770cb3b1e81ae19463b3c235bf1e0e330767dca9eb4cd73ba3ded6c4151e4d"}, + {file = "scipy-1.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1f97cd89c0fe1a0685f8f89d85fa305deb3067d0668151571ba50913e445820"}, + {file = "scipy-1.11.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dfcc1552add7cb7c13fb70efcb2389d0624d571aaf2c80b04117e2755a0c5d15"}, + {file = "scipy-1.11.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0d3a136ae1ff0883fffbb1b05b0b2fea251cb1046a5077d0b435a1839b3e52b7"}, + {file = "scipy-1.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bae66a2d7d5768eaa33008fa5a974389f167183c87bf39160d3fefe6664f8ddc"}, + {file = "scipy-1.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2f6dee6cbb0e263b8142ed587bc93e3ed5e777f1f75448d24fb923d9fd4dce6"}, + {file = "scipy-1.11.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:74e89dc5e00201e71dd94f5f382ab1c6a9f3ff806c7d24e4e90928bb1aafb280"}, + {file = "scipy-1.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:90271dbde4be191522b3903fc97334e3956d7cfb9cce3f0718d0ab4fd7d8bfd6"}, + {file = "scipy-1.11.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a63d1ec9cadecce838467ce0631c17c15c7197ae61e49429434ba01d618caa83"}, + {file = "scipy-1.11.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:5305792c7110e32ff155aed0df46aa60a60fc6e52cd4ee02cdeb67eaccd5356e"}, + {file = "scipy-1.11.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ea7f579182d83d00fed0e5c11a4aa5ffe01460444219dedc448a36adf0c3917"}, + {file = "scipy-1.11.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c77da50c9a91e23beb63c2a711ef9e9ca9a2060442757dffee34ea41847d8156"}, + {file = "scipy-1.11.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15f237e890c24aef6891c7d008f9ff7e758c6ef39a2b5df264650eb7900403c0"}, + {file = "scipy-1.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:4b4bb134c7aa457e26cc6ea482b016fef45db71417d55cc6d8f43d799cdf9ef2"}, + {file = "scipy-1.11.3.tar.gz", hash = "sha256:bba4d955f54edd61899776bad459bf7326e14b9fa1c552181f0479cc60a568cd"}, +] + +[package.dependencies] +numpy = ">=1.21.6,<1.28.0" + +[package.extras] +dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] +doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] +test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "send2trash" +version = "1.8.2" +description = "Send file to trash natively under Mac OS X, Windows and Linux" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "Send2Trash-1.8.2-py3-none-any.whl", hash = "sha256:a384719d99c07ce1eefd6905d2decb6f8b7ed054025bb0e618919f945de4f679"}, + {file = "Send2Trash-1.8.2.tar.gz", hash = "sha256:c132d59fa44b9ca2b1699af5c86f57ce9f4c5eb56629d5d55fbb7a35f84e2312"}, +] + +[package.extras] +nativelib = ["pyobjc-framework-Cocoa", "pywin32"] +objc = ["pyobjc-framework-Cocoa"] +win32 = ["pywin32"] + +[[package]] +name = "setuptools" +version = "68.2.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "setuptools-scm" +version = "8.0.4" +description = "the blessed package to manage your versions by scm tags" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-scm-8.0.4.tar.gz", hash = "sha256:b5f43ff6800669595193fd09891564ee9d1d7dcb196cab4b2506d53a2e1c95c7"}, + {file = "setuptools_scm-8.0.4-py3-none-any.whl", hash = "sha256:b47844cd2a84b83b3187a5782c71128c28b4c94cad8bfb871da2784a5cb54c4f"}, +] + +[package.dependencies] +packaging = ">=20" +setuptools = "*" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} +typing-extensions = "*" + +[package.extras] +docs = ["entangled-cli[rich]", "mkdocs", "mkdocs-entangled-plugin", "mkdocs-material", "mkdocstrings[python]", "pygments"] +rich = ["rich"] +test = ["build", "pytest", "rich", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "soupsieve" +version = "2.5" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "statsmodels" +version = "0.14.0" +description = "Statistical computations and models for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "statsmodels-0.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:16bfe0c96a53b20fa19067e3b6bd2f1d39e30d4891ea0d7bc20734a0ae95942d"}, + {file = "statsmodels-0.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a6a0a1a06ff79be8aa89c8494b33903442859add133f0dda1daf37c3c71682e"}, + {file = "statsmodels-0.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77b3cd3a5268ef966a0a08582c591bd29c09c88b4566c892a7c087935234f285"}, + {file = "statsmodels-0.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c64ebe9cf376cba0c31aed138e15ed179a1d128612dd241cdf299d159e5e882"}, + {file = "statsmodels-0.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb471f757fc45102a87e5d86e87dc2c8c78b34ad4f203679a46520f1d863b9da"}, + {file = "statsmodels-0.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:582f9e41092e342aaa04920d17cc3f97240e3ee198672f194719b5a3d08657d6"}, + {file = "statsmodels-0.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7ebe885ccaa64b4bc5ad49ac781c246e7a594b491f08ab4cfd5aa456c363a6f6"}, + {file = "statsmodels-0.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b587ee5d23369a0e881da6e37f78371dce4238cf7638a455db4b633a1a1c62d6"}, + {file = "statsmodels-0.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ef7fa4813c7a73b0d8a0c830250f021c102c71c95e9fe0d6877bcfb56d38b8c"}, + {file = "statsmodels-0.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:a6ad7b8aadccd4e4dd7f315a07bef1bca41d194eeaf4ec600d20dea02d242fce"}, + {file = "statsmodels-0.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3757542c95247e4ab025291a740efa5da91dc11a05990c033d40fce31c450dc9"}, + {file = "statsmodels-0.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:de489e3ed315bdba55c9d1554a2e89faa65d212e365ab81bc323fa52681fc60e"}, + {file = "statsmodels-0.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e290f4718177bffa8823a780f3b882d56dd64ad1c18cfb4bc8b5558f3f5757"}, + {file = "statsmodels-0.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71054f9dbcead56def14e3c9db6f66f943110fdfb19713caf0eb0f08c1ec03fd"}, + {file = "statsmodels-0.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:d7fda067837df94e0a614d93d3a38fb6868958d37f7f50afe2a534524f2660cb"}, + {file = "statsmodels-0.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1c7724ad573af26139a98393ae64bc318d1b19762b13442d96c7a3e793f495c3"}, + {file = "statsmodels-0.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3b0a135f3bfdeec987e36e3b3b4c53e0bb87a8d91464d2fcc4d169d176f46fdb"}, + {file = "statsmodels-0.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce28eb1c397dba437ec39b9ab18f2101806f388c7a0cf9cdfd8f09294ad1c799"}, + {file = "statsmodels-0.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b1c768dd94cc5ba8398121a632b673c625491aa7ed627b82cb4c880a25563f"}, + {file = "statsmodels-0.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:8d1e3e10dfbfcd58119ba5a4d3c7d519182b970a2aebaf0b6f539f55ae16058d"}, + {file = "statsmodels-0.14.0.tar.gz", hash = "sha256:6875c7d689e966d948f15eb816ab5616f4928706b180cf470fd5907ab6f647a4"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.22.3", markers = "python_version == \"3.10\" and platform_system == \"Windows\" and platform_python_implementation != \"PyPy\""}, + {version = ">=1.18", markers = "python_version != \"3.10\" or platform_system != \"Windows\" or platform_python_implementation == \"PyPy\""}, +] +packaging = ">=21.3" +pandas = ">=1.0" +patsy = ">=0.5.2" +scipy = ">=1.4,<1.9.2 || >1.9.2" + +[package.extras] +build = ["cython (>=0.29.26)"] +develop = ["colorama", "cython (>=0.29.26)", "cython (>=0.29.28,<3.0.0)", "flake8", "isort", "joblib", "matplotlib (>=3)", "oldest-supported-numpy (>=2022.4.18)", "pytest (>=7.0.1,<7.1.0)", "pytest-randomly", "pytest-xdist", "pywinpty", "setuptools-scm[toml] (>=7.0.0,<7.1.0)"] +docs = ["ipykernel", "jupyter-client", "matplotlib", "nbconvert", "nbformat", "numpydoc", "pandas-datareader", "sphinx"] + +[[package]] +name = "terminado" +version = "0.17.1" +description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." +optional = false +python-versions = ">=3.7" +files = [ + {file = "terminado-0.17.1-py3-none-any.whl", hash = "sha256:8650d44334eba354dd591129ca3124a6ba42c3d5b70df5051b6921d506fdaeae"}, + {file = "terminado-0.17.1.tar.gz", hash = "sha256:6ccbbcd3a4f8a25a5ec04991f39a0b8db52dfcd487ea0e578d977e6752380333"}, +] + +[package.dependencies] +ptyprocess = {version = "*", markers = "os_name != \"nt\""} +pywinpty = {version = ">=1.1.0", markers = "os_name == \"nt\""} +tornado = ">=6.1.0" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"] + +[[package]] +name = "tinycss2" +version = "1.2.1" +description = "A tiny CSS parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, + {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, +] + +[package.dependencies] +webencodings = ">=0.4" + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["flake8", "isort", "pytest"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tornado" +version = "6.3.3" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">= 3.8" +files = [ + {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d"}, + {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a"}, + {file = "tornado-6.3.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f"}, + {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a"}, + {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2"}, + {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0"}, + {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16"}, + {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17"}, + {file = "tornado-6.3.3-cp38-abi3-win32.whl", hash = "sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3"}, + {file = "tornado-6.3.3-cp38-abi3-win_amd64.whl", hash = "sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5"}, + {file = "tornado-6.3.3.tar.gz", hash = "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe"}, +] + +[[package]] +name = "tqdm" +version = "4.66.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, + {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "traitlets" +version = "5.11.2" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.11.2-py3-none-any.whl", hash = "sha256:98277f247f18b2c5cabaf4af369187754f4fb0e85911d473f72329db8a7f4fae"}, + {file = "traitlets-5.11.2.tar.gz", hash = "sha256:7564b5bf8d38c40fa45498072bf4dc5e8346eb087bbf1e2ae2d8774f6a0f078e"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.5.1)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "types-python-dateutil" +version = "2.8.19.14" +description = "Typing stubs for python-dateutil" +optional = false +python-versions = "*" +files = [ + {file = "types-python-dateutil-2.8.19.14.tar.gz", hash = "sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b"}, + {file = "types_python_dateutil-2.8.19.14-py3-none-any.whl", hash = "sha256:f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9"}, +] + +[[package]] +name = "types-pytz" +version = "2023.3.1.1" +description = "Typing stubs for pytz" +optional = false +python-versions = "*" +files = [ + {file = "types-pytz-2023.3.1.1.tar.gz", hash = "sha256:cc23d0192cd49c8f6bba44ee0c81e4586a8f30204970fc0894d209a6b08dab9a"}, + {file = "types_pytz-2023.3.1.1-py3-none-any.whl", hash = "sha256:1999a123a3dc0e39a2ef6d19f3f8584211de9e6a77fe7a0259f04a524e90a5cf"}, +] + +[[package]] +name = "typing-extensions" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, +] + +[[package]] +name = "tzdata" +version = "2023.3" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, + {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, +] + +[[package]] +name = "uri-template" +version = "1.3.0" +description = "RFC 6570 URI Template Processor" +optional = false +python-versions = ">=3.7" +files = [ + {file = "uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7"}, + {file = "uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363"}, +] + +[package.extras] +dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake8-commas", "flake8-comprehensions", "flake8-continuation", "flake8-datetimez", "flake8-docstrings", "flake8-import-order", "flake8-literal", "flake8-modern-annotations", "flake8-noqa", "flake8-pyproject", "flake8-requirements", "flake8-typechecking-import", "flake8-use-fstring", "mypy", "pep8-naming", "types-PyYAML"] + +[[package]] +name = "urllib3" +version = "2.0.6" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, + {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.24.5" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, + {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<4" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[[package]] +name = "watchdog" +version = "3.0.0" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.7" +files = [ + {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"}, + {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"}, + {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"}, + {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"}, + {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"}, + {file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"}, + {file = "watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674"}, + {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f"}, + {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc"}, + {file = "watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3"}, + {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3"}, + {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0"}, + {file = "watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8"}, + {file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"}, + {file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"}, + {file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"}, + {file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"}, + {file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"}, + {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"}, + {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "wcwidth" +version = "0.2.8" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.8-py2.py3-none-any.whl", hash = "sha256:77f719e01648ed600dfa5402c347481c0992263b81a027344f3e1ba25493a704"}, + {file = "wcwidth-0.2.8.tar.gz", hash = "sha256:8705c569999ffbb4f6a87c6d1b80f324bd6db952f5eb0b95bc07517f4c1813d4"}, +] + +[[package]] +name = "webcolors" +version = "1.13" +description = "A library for working with the color formats defined by HTML and CSS." +optional = false +python-versions = ">=3.7" +files = [ + {file = "webcolors-1.13-py3-none-any.whl", hash = "sha256:29bc7e8752c0a1bd4a1f03c14d6e6a72e93d82193738fa860cbff59d0fcc11bf"}, + {file = "webcolors-1.13.tar.gz", hash = "sha256:c225b674c83fa923be93d235330ce0300373d02885cef23238813b0d5668304a"}, +] + +[package.extras] +docs = ["furo", "sphinx", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-notfound-page", "sphinxext-opengraph"] +tests = ["pytest", "pytest-cov"] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +optional = false +python-versions = "*" +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + +[[package]] +name = "websocket-client" +version = "1.6.4" +description = "WebSocket client for Python with low level API options" +optional = false +python-versions = ">=3.8" +files = [ + {file = "websocket-client-1.6.4.tar.gz", hash = "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df"}, + {file = "websocket_client-1.6.4-py3-none-any.whl", hash = "sha256:084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24"}, +] + +[package.extras] +docs = ["Sphinx (>=6.0)", "sphinx-rtd-theme (>=1.1.0)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[[package]] +name = "widgetsnbextension" +version = "4.0.9" +description = "Jupyter interactive widgets for Jupyter Notebook" +optional = false +python-versions = ">=3.7" +files = [ + {file = "widgetsnbextension-4.0.9-py3-none-any.whl", hash = "sha256:91452ca8445beb805792f206e560c1769284267a30ceb1cec9f5bcc887d15175"}, + {file = "widgetsnbextension-4.0.9.tar.gz", hash = "sha256:3c1f5e46dc1166dfd40a42d685e6a51396fd34ff878742a3e47c6f0cc4a2a385"}, +] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.10,<3.13" +content-hash = "948e746594bb624b9047c335105a87e6f5bcd432fa8e1be0b387f14c0182cb0d" diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 00000000..53b35d37 --- /dev/null +++ b/poetry.toml @@ -0,0 +1,3 @@ +[virtualenvs] +create = true +in-project = true diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..7f2554e7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,88 @@ +[build-system] +build-backend = "poetry.core.masonry.api" +requires = ["poetry-core"] + +[tool.poetry] +authors = ["Samuel Hinton "] +description = "ChainConsumer: Consumer your MCMC chains" +name = "ChainConsumer" +packages = [{include = "chainconsumer", from = "src"}] +readme = "README.md" +version = "v0.34.0" + +[tool.poetry-version-plugin] +source = "git-tag" + +[tool.poetry.dependencies] +python = ">=3.10,<3.13" +numpy = "^1.23.0" +scipy = "^1.8.0" +matplotlib = "^3.6.0" +statsmodels = "^0.14.0" +pandas = "^2.1.1" +pillow = "^10.0.1" +pydantic = "^2.2.0" + + +[tool.poetry.group.test.dependencies] +pytest = "^7.4.0" + +[tool.poetry.group.dev.dependencies] +black = ">=23.3.0" +pre-commit = ">=3.3.3" +ruff = ">=0.0.276, <1" +mypy = "^1.4.1" +pandas-stubs = "^2.1.1.230928" + + +[tool.poetry.group.docs.dependencies] +jupyter = "^1.0.0" +Markdown = "^3.3.7" +mkdocs = "^1.1.2" +mkdocs-material = "^9.1.19" +pyyaml = "^6.0.1" +mdx-include = ">=1.4.1" +mkdocstrings-python = ">=0.8.3" +mkdocs-gallery = ">=0.7.8" + +[tool.black] +line-length = 120 +target-version = ['py310'] + +[tool.ruff] +src = ["src"] +select = ["E", "F", "I", "N", "W", "D207", "D208", "D300", "UP", "YTT", "ASYNC", "DTZ", "G10", "G101", "G201", "G202", "INP001", "PIE", "T20", "SIM", "PTH", "PD", "PLE", "PLR", "PLW", "TRY", "NPY", "RUF"] +ignore = ["PD010", "PD901", "PLR2004", "UP017", "PLR0915", "TRY003", "INP001", "PLR0912", "PLR0913", "TRY300", "E712"] +line-length = 120 +target-version = "py310" + +[tool.ruff.extend-per-file-ignores] +"test/***" = ["INP001"] +"__init__.py" = ["E402", "F401"] +"examples/***" = ["T201", "NPY002"] +"**/plot_*" = ["T201"] +"docs/examples/***" = ["T201"] + +[tool.mypy] +plugins = ["numpy.typing.mypy_plugin", "pydantic.mypy"] +ignore_missing_imports = true +disallow_untyped_defs = false +disallow_any_unimported = false +disallow_any_generics = false +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = false +implicit_reexport = false +no_implicit_optional = false +python_version = "3.10" +strict_equality = true +show_error_codes = true +warn_redundant_casts = true +warn_return_any = true +warn_unused_ignores = true + +[tool.pytest.ini_options] +addopts = "-v" +pythonpath = ["src"] +testpaths = ["test"] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 72a713e8..00000000 --- a/requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ -numpy -scipy -matplotlib -statsmodels -pytest -pytest-cov -sphinx -numpydoc -sphinx_rtd_theme -codecov -Pillow -sphinx_gallery -pandas \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 0812d7e7..00000000 --- a/setup.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python -import re -from setuptools import setup - -# Synchronize version from code. -with open("chainconsumer/chainconsumer.py") as f: - version = re.findall(r"__version__ = \"(.*?)\"", f.read())[0] - -setup(name="ChainConsumer", - version=version, - description="Consume chains and produce plots and tables", - long_description="Package documentation: http://samreay.github.io/ChainConsumer", - classifiers=["Development Status :: 4 - Beta", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Topic :: Scientific/Engineering", - "Topic :: Scientific/Engineering :: Astronomy", - "Intended Audience :: Science/Research", - "Operating System :: OS Independent"], - packages=["chainconsumer"], - url="http://github.com/samreay/ChainConsumer", - author="Samuel Hinton", - author_email="samuelreay@gmail.com", - install_requires=["numpy", "scipy", "matplotlib>1.6.0,!=2.1.*,!=2.2.*", "statsmodels>=0.7.0", "pandas"]) - diff --git a/src/chainconsumer/TODO.md b/src/chainconsumer/TODO.md new file mode 100644 index 00000000..4c3ec7d2 --- /dev/null +++ b/src/chainconsumer/TODO.md @@ -0,0 +1,16 @@ +- [ ] Add max_ticks to plotter +- [ ] plot_hists +- [ ] flip +- [ ] serif +- [ ] sigma2d +- [ ] usetex +- [ ] diagonal_tick_labels +- [ ] label_font_size +- [ ] tick_font_size +- [ ] spacing +- [ ] contour_label_font_size +- [ ] legend_kwargs +- [ ] legend_location +- [ ] legend_artists +- [ ] legend_color_text +- [ ] watermark_text_kwargs \ No newline at end of file diff --git a/src/chainconsumer/__init__.py b/src/chainconsumer/__init__.py new file mode 100644 index 00000000..c65bfd99 --- /dev/null +++ b/src/chainconsumer/__init__.py @@ -0,0 +1,13 @@ +import importlib.metadata as importlib_metadata + +from .analysis import Bound +from .chain import Chain, ChainConfig +from .chainconsumer import ChainConsumer +from .color_finder import colors +from .examples import make_sample +from .plotter import PlotConfig +from .truth import Truth + +__all__ = ["ChainConsumer", "Chain", "ChainConfig", "Truth", "PlotConfig", "make_sample", "Bound", "colors"] + +__version__ = importlib_metadata.version(__name__) diff --git a/src/chainconsumer/analysis.py b/src/chainconsumer/analysis.py new file mode 100644 index 00000000..da67b11e --- /dev/null +++ b/src/chainconsumer/analysis.py @@ -0,0 +1,470 @@ +from __future__ import annotations + +import logging +from collections.abc import Callable +from pathlib import Path + +import numpy as np +from pydantic import Field +from scipy.integrate import simps +from scipy.interpolate import interp1d +from scipy.ndimage import gaussian_filter + +from .base import BetterBase +from .chain import Chain, ChainName, ColumnName, MaxPosterior, Named2DMatrix +from .helpers import get_bins, get_grid_bins, get_latex_table_frame, get_smoothed_bins +from .kde import MegKDE +from .statistics import SummaryStatistic + + +class Bound(BetterBase): + lower: float | None = Field(default=None) + center: float | None = Field(default=None) + upper: float | None = Field(default=None) + + @property + def array(self) -> np.ndarray: + return np.array( + [ + self.lower if self.lower is not None else np.NaN, + self.center if self.center is not None else np.NaN, + self.upper if self.upper is not None else np.NaN, + ] + ) + + @property + def all_none(self) -> bool: + return self.lower is None and self.center is None and self.upper is None + + @classmethod + def from_array(cls, array: np.ndarray | list[float]) -> Bound: + assert len(array) == 3, "Array must have 3 elements" + lower, center, upper = array + return cls(lower=lower, center=center, upper=upper) + + +class Analysis: + def __init__(self, parent: ChainConsumer): + self.parent = parent + self._logger = logging.getLogger("chainconsumer") + + self._summaries: dict[SummaryStatistic, Callable[[Chain, ColumnName], Bound | None]] = { + SummaryStatistic.MAX: self.get_parameter_summary_max, + SummaryStatistic.MEAN: self.get_parameter_summary_mean, + SummaryStatistic.CUMULATIVE: self.get_parameter_summary_cumulative, + SummaryStatistic.MAX_CENTRAL: self.get_parameter_summary_max_central, + } + + def get_latex_table( + self, + chains: list[ChainName | Chain] | None = None, + columns: list[ColumnName] | None = None, + transpose: bool = False, + caption: str | None = None, + label: str = "tab:model_params", + hlines: bool = True, + blank_fill: str = "--", + filename: str | Path | None = None, + ) -> str: # pragma: no cover + """Generates a LaTeX table from parameter summaries. + + Args: + chains: + Used to specify which chain to show if more than one chain is loaded in. + Can be an integer, specifying the + chain index, or a str, specifying the chain name. + columns: + If set, only creates a plot for those specific parameters (if list). If an + integer is given, only plots the fist so many parameters. + transpose : bool, optional + Defaults to False, which gives each column as a parameter, each chain (framework) + as a row. You can swap it so that you have a parameter each row and a framework + each column by setting this to True + caption : str, optional + If you want to generate a caption for the table through Python, use this. + Defaults to an empty string + label : str, optional + If you want to generate a label for the table through Python, use this. + Defaults to an empty string + hlines : bool, optional + Inserts ``\\hline`` before and after the header, and at the end of table. + blank_fill : str, optional + If a framework does not have a particular parameter, will fill that cell of + the table with this string. + filename : str | Path, optional + The file to save the output string to + + Returns: + str: the LaTeX table. + """ + final_chains = self.parent.plotter._sanitise_chains(chains) + final_columns = self.parent.plotter._sanitise_columns(columns, final_chains) + blind = self.parent.plotter._sanitise_blinds(self.parent.plotter.config.blind, final_columns) + + final_columns = [c for c in final_columns if c not in blind] + num_chains = len(final_chains) + num_parameters = len(final_columns) + fit_values = self.get_summary(chains=final_chains) + if label is None: + label = "" + if caption is None: + caption = "" + + end_text = " \\\\ \n" + column_text = "c" * (num_chains + 1) if transpose else "c" * (num_parameters + 1) + + center_text = "" + hline_text = "\\hline\n" + if hlines: + center_text += hline_text + "\t\t" + if transpose: + center_text += " & ".join(["Parameter"] + [c.name for c in final_chains]) + end_text + if hlines: + center_text += "\t\t" + hline_text + for p in final_columns: + arr = ["\t\t" + self.parent.plotter.config.get_label(p)] + for _, column_results in fit_values.items(): + if p in column_results: + arr.append(self.get_parameter_text(column_results[p], wrap=True)) + else: + arr.append(blank_fill) + center_text += " & ".join(arr) + end_text + else: + center_text += ( + " & ".join(["Model", *[self.parent.plotter.config.get_label(c) for c in final_columns]]) + end_text + ) + if hlines: + center_text += "\t\t" + hline_text + for name, chain_res in fit_values.items(): + arr = ["\t\t" + name] + for p in final_columns: + if p in chain_res: + arr.append(self.get_parameter_text(chain_res[p], wrap=True)) + else: + arr.append(blank_fill) + center_text += " & ".join(arr) + end_text + if hlines: + center_text += "\t\t" + hline_text + final_text = get_latex_table_frame(caption, label) % (column_text, center_text) + + if filename is not None: + if isinstance(filename, str): + filename = Path(filename) + with Path.open(filename, "w") as f: + f.write(final_text) + + return final_text + + def get_summary( + self, + chains: list[Chain] | None = None, + columns: list[ColumnName] | None = None, + ) -> dict[ChainName, dict[ColumnName, Bound]]: + """Gets a summary of the marginalised parameter distributions. + + Args: + parameters (list[str], optional): A list of parameters which to generate summaries for. + chains (dict[str, Chain] | list[str], optional): A list of chains to generate summaries for. + + Returns: + dict[ChainName, dict[ColumnName, Bound]]: A map from chain name to column name to bound. + """ + results = {} + if chains is None: + chains = self.parent.plotter._sanitise_chains(None, include_skip=True) + if columns is None: + columns = self.parent.plotter._sanitise_columns(None, chains) + + for chain in chains: + res = {} + params_to_find = columns if columns is not None else chain.data_columns + for p in params_to_find: + if p not in chain.samples: + continue + summary = self.get_parameter_summary(chain, p) + res[p] = summary + results[chain.name] = res + + return results + + def get_max_posteriors(self, chains: dict[str, Chain] | list[str] | None = None) -> dict[ChainName, MaxPosterior]: + """Gets the maximum posterior point in parameter space from the passed parameters. + + Requires the chains to have set `posterior` values. + + Args: + chains (dict[str, Chain] | list[str], optional): A list of chains to generate summaries for. + + Returns: + dict[ChainName, MaxPosterior]: A map from chain name to max posterior point. + """ + + results = {} + if chains is None: + chains = self.parent._chains + if isinstance(chains, list): + chains = {c: self.parent._chains[c] for c in chains} + + for chain_name, chain in chains.items(): + max_posterior = chain.get_max_posterior_point() + if max_posterior is None: + continue + results[chain_name] = max_posterior + + return results + + def get_parameter_summary(self, chain: Chain, column: ColumnName) -> Bound | None: + callback = self._summaries[chain.statistics] + return callback(chain, column) + + def get_correlation_table( + self, + chain: str | Chain, + columns: list[str] | None = None, + caption: str = "Parameter Correlations", + label: str = "tab:parameter_correlations", + ) -> str: + """ + Gets a LaTeX table of parameter correlations. + + Args: + chain (str|Chain, optional_: The chain index or name. Defaults to first chain. + columns (list[str], optional): The list of parameters to compute correlations. Defaults to all columns + caption (str, optional): The LaTeX table caption. + label (str, optional): The LaTeX table label. + + Returns: + str: The LaTeX table ready to go! + """ + if isinstance(chain, str): + assert chain in self.parent._chains, f"Chain {chain} not found!" + chain = self.parent._chains[chain] + if chain is None: + assert len(self.parent._chains) == 1, "You must specify a chain if there are multiple chains" + chain = next(iter(self.parent._chains.values())) + + correlations = chain.get_correlation(columns=columns) + return self._get_2d_latex_table(correlations, caption, label) + + def get_covariance_table( + self, + chain: str | Chain, + columns: list[str] | None = None, + caption: str = "Parameter Covariance", + label: str = "tab:parameter_covariance", + ) -> str: + """ + Gets a LaTeX table of parameter covariances. + + Args: + chain (str|Chain, optional_: The chain index or name. Defaults to first chain. + columns (list[str], optional): The list of parameters to compute covariances on. Defaults to all columns + caption (str, optional): The LaTeX table caption. + label (str, optional): The LaTeX table label. + + Returns: + str: The LaTeX table ready to go! + """ + if isinstance(chain, str): + assert chain in self.parent._chains, f"Chain {chain} not found!" + chain = self.parent._chains[chain] + if chain is None: + assert len(self.parent._chains) == 1, "You must specify a chain if there are multiple chains" + chain = next(iter(self.parent._chains.values())) + + covariance = chain.get_covariance(columns=columns) + return self._get_2d_latex_table(covariance, caption, label) + + def _get_smoothed_histogram( + self, chain: Chain, column: ColumnName, pad: bool = False + ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + data = chain.get_data(column) + if chain.grid: + bins = get_grid_bins(data) + else: + bins, _ = get_smoothed_bins(chain.smooth, get_bins(chain), data, chain.weights, pad=pad) + + hist, edges = np.histogram(data, bins=bins, density=True, weights=chain.weights) + if chain.power is not None: + hist = hist**chain.power + edge_centers = 0.5 * (edges[1:] + edges[:-1]) + xs = np.linspace(edge_centers[0], edge_centers[-1], 10000) + + if chain.smooth: + hist = gaussian_filter(hist, chain.smooth, mode="reflect") + if chain.kde: + kde_xs = np.linspace(edge_centers[0], edge_centers[-1], max(200, int(bins.max()))) + factor = chain.kde if isinstance(chain.kde, int | float) else 1.0 + ys = MegKDE(data.to_numpy(), chain.weights, factor=factor).evaluate(kde_xs) + area = simps(ys, x=kde_xs) + ys = ys / area + ys = interp1d(kde_xs, ys, kind="linear")(xs) + else: + ys = interp1d(edge_centers, hist, kind="linear")(xs) + cs = ys.cumsum() + cs /= cs.max() + return xs, ys, cs + + def _get_2d_latex_table(self, named_matrix: Named2DMatrix, caption: str, label: str) -> str: + parameters = [self.parent.plotter.config.get_label(c) for c in named_matrix.columns] + matrix = named_matrix.matrix + latex_table = get_latex_table_frame(caption=caption, label=label) + column_def = "c|%s" % ("c" * len(parameters)) + hline_text = " \\hline\n" + + table = "" + table += " & ".join(["", *parameters]) + "\\\\ \n" + table += hline_text + max_len = max([len(s) for s in parameters]) + format_string = " %%%ds" % max_len + for p, row in zip(parameters, matrix): + table += format_string % p + for r in row: + table += " & %5.2f" % r + table += " \\\\ \n" + table += hline_text + return latex_table % (column_def, table) + + def get_parameter_text(self, bound: Bound, wrap: bool = False): + """Generates LaTeX appropriate text from marginalised parameter bounds. + + Parameters + ---------- + lower : float + The lower bound on the parameter + maximum : float + The value of the parameter with maximum probability + upper : float + The upper bound on the parameter + wrap : bool + Wrap output text in dollar signs for LaTeX + + Returns + ------- + str + The formatted text given the parameter bounds + """ + if bound.lower is None or bound.upper is None or bound.center is None: + return "" + upper_error = bound.upper - bound.center + lower_error = bound.center - bound.lower + if upper_error != 0 and lower_error != 0: + resolution = min(np.floor(np.log10(np.abs(upper_error))), np.floor(np.log10(np.abs(lower_error)))) + elif upper_error == 0 and lower_error != 0: + resolution = np.floor(np.log10(np.abs(lower_error))) + elif upper_error != 0 and lower_error == 0: + resolution = np.floor(np.log10(np.abs(upper_error))) + else: + resolution = np.floor(np.log10(np.abs(bound.center))) + factor = 0 + fmt = "%0.1f" + r = 1 + if np.abs(resolution) > 2: + factor = -resolution + if resolution == 2: + fmt = "%0.0f" + factor = -1 + r = 0 + if resolution == 1: + fmt = "%0.0f" + if resolution == -1: + fmt = "%0.2f" + r = 2 + elif resolution == -2: + fmt = "%0.3f" + r = 3 + upper_error *= 10**factor + lower_error *= 10**factor + maximum = bound.center * 10**factor + upper_error = round(upper_error, r) + lower_error = round(lower_error, r) + maximum = round(maximum, r) + if maximum == -0.0: + maximum = 0.0 + if resolution == 2: + upper_error *= 10**-factor + lower_error *= 10**-factor + maximum *= 10**-factor + factor = 0 + fmt = "%0.0f" + upper_error_text = fmt % upper_error + lower_error_text = fmt % lower_error + if upper_error_text == lower_error_text: + text = r"{}\pm {}".format(fmt, "%s") % (maximum, lower_error_text) + else: + text = r"{}^{{+{}}}_{{-{}}}".format(fmt, "%s", "%s") % (maximum, upper_error_text, lower_error_text) + if factor != 0: + text = r"\left( %s \right) \times 10^{%d}" % (text, -factor) + if wrap: + text = "$%s$" % text + return text + + def get_parameter_summary_mean(self, chain: Chain, column: ColumnName) -> Bound | None: + xs, _, cs = self._get_smoothed_histogram(chain, column) + vals = [0.5 - chain.summary_area / 2, 0.5, 0.5 + chain.summary_area / 2] + bounds = interp1d(cs, xs)(vals) + bounds[1] = 0.5 * (bounds[0] + bounds[2]) + return Bound(lower=bounds[0], center=bounds[1], upper=bounds[2]) + + def get_parameter_summary_cumulative(self, chain: Chain, column: ColumnName) -> Bound | None: + xs, _, cs = self._get_smoothed_histogram(chain, column) + vals = [0.5 - chain.summary_area / 2, 0.5, 0.5 + chain.summary_area / 2] + bounds = interp1d(cs, xs)(vals) + return Bound(lower=bounds[0], center=bounds[1], upper=bounds[2]) + + def get_parameter_summary_max(self, chain: Chain, column: ColumnName) -> Bound | None: + xs, ys, cs = self._get_smoothed_histogram(chain, column) + n_pad = 1000 + x_start = xs[0] * np.ones(n_pad) + x_end = xs[-1] * np.ones(n_pad) + y_start = np.linspace(0, ys[0], n_pad) + y_end = np.linspace(ys[-1], 0, n_pad) + xs = np.concatenate((x_start, xs, x_end)) + ys = np.concatenate((y_start, ys, y_end)) + cs = ys.cumsum() + cs = cs / cs.max() + start_index = ys.argmax() + max_val = ys[start_index] + min_val = 0 + threshold = 0.003 + x1 = None + x2 = None + count = 0 + while x1 is None: + mid = (max_val + min_val) / 2.0 + count += 1 + try: + if count > 50: + raise ValueError("Failed to converge") # noqa: TRY301 + i1 = start_index - np.where(ys[:start_index][::-1] < mid)[0][0] + i2 = start_index + np.where(ys[start_index:] < mid)[0][0] + area = cs[i2] - cs[i1] + deviation = np.abs(area - chain.summary_area) + if deviation < threshold: + x1 = float(xs[i1]) + x2 = float(xs[i2]) + elif area < chain.summary_area: + max_val = mid + elif area > chain.summary_area: + min_val = mid + except ValueError: + self._logger.warning(f"Parameter {column} in chain {chain.name} is not constrained") + return Bound(lower=None, center=float(xs[start_index]), upper=None) + + return Bound(lower=x1, center=float(xs[start_index]), upper=x2) + + def get_parameter_summary_max_central(self, chain, parameter): + xs, ys, cs = self._get_smoothed_histogram(chain, parameter) + + c_to_x = interp1d(cs, xs) + max_index = ys.argmax() + x = xs[max_index] + + vals = [0.5 - 0.5 * chain.summary_area, 0.5 + 0.5 * chain.summary_area] + xvals = c_to_x(vals) + + return Bound(lower=xvals[0], center=x, upper=xvals[1]) + + +if __name__ == "__main__": + from .chainconsumer import ChainConsumer diff --git a/src/chainconsumer/base.py b/src/chainconsumer/base.py new file mode 100644 index 00000000..a3985448 --- /dev/null +++ b/src/chainconsumer/base.py @@ -0,0 +1,16 @@ +from typing import Any + +from pydantic import BaseModel, ConfigDict + + +class BetterBase(BaseModel): + _user_specified: set[str] = set() + + def __init__(self, **kwargs: Any) -> None: + super().__init__(**kwargs) + self._user_specified = set(kwargs.keys()) + + model_config = ConfigDict(arbitrary_types_allowed=True) + + def get_user_specified_dump(self) -> dict[str, Any]: + return {key: getattr(self, key) for key in self._user_specified} diff --git a/src/chainconsumer/chain.py b/src/chainconsumer/chain.py new file mode 100644 index 00000000..49e317ac --- /dev/null +++ b/src/chainconsumer/chain.py @@ -0,0 +1,370 @@ +"""# Chains and ChainConfigs + +This file is where I expect most people will focus their time. It contains a general configuration class, +which stores non-unique things that chains use. Like line styles, colours, etc. + +It is then extended by the `Chain` class, which contains the actual data. + +There are also a few helper functions and objects in here, like the `MaxPosterior` class which +provides the log posterior and the coordinate at which it can be found for the chain.""" +from __future__ import annotations + +import logging +from typing import Any, TypeAlias + +import numpy as np +import pandas as pd +from pydantic import Field, field_validator, model_validator + +from .base import BetterBase +from .color_finder import ColorInput, colors +from .statistics import SummaryStatistic + +ChainName: TypeAlias = str +ColumnName: TypeAlias = str + + +class Named2DMatrix(BetterBase): + """A 2D matrix with named columns. Used for covariance and correlation matrices.""" + + columns: list[str] + matrix: np.ndarray # type: ignore + + +class ChainConfig(BetterBase): + """The configuration for a chain. This is used to set the default values for + plotting chains, and is also used to store the configuration of a chain. + + Note that some attributes are defaulted to None instead of their type hint. + Like color. This indicates that this parameter should be inferred if not explicitly + set, and that this inference requires knowledge of the other chains. For example, + if you have two chains, you probably want them to be different colors. + """ + + statistics: SummaryStatistic = Field(default=SummaryStatistic.MAX, description="The summary statistic to use") + summary_area: float = Field(default=0.6827, ge=0, le=1.0, description="The area to use for summary statistics") + sigmas: list[float] = Field(default=[0, 1, 2], description="The sigmas to use for summary statistics") + color: ColorInput | None = Field(default=None, description="The color of the chain") # type: ignore + linestyle: str = Field(default="-", description="The line style of the chain") + linewidth: float = Field(default=1.0, description="The line width of the chain") + show_contour_labels: bool = Field(default=False, description="Whether to show contour labels") + shade: bool = Field(default=True, description="Whether to shade the chain") + shade_alpha: float = Field(default=0.5, description="The alpha of the shading") + shade_gradient: float = Field(default=1.0, description="The contrast between contour levels") + bar_shade: bool = Field(default=True, description="Whether to shade marginalised distributions") + bins: int | None = Field(default=None, description="The number of bins to use for histograms.") + kde: int | float | bool = Field(default=False, description="The bandwidth for KDEs") + smooth: int = Field(default=3, description="The smoothing for histograms. Set to 0 for no smoothing") + color_param: str | None = Field(default=None, description="The parameter (column) to use for coloring") + cmap: str = Field(default="viridis", description="The colormap to use for shading cloud points") + num_cloud: int | float = Field(default=10000, description="The number of points in the cloud") + plot_cloud: bool = Field(default=False, description="Whether to plot the cloud") + plot_contour: bool = Field(default=True, description="Whether to plot contours") + plot_point: bool = Field(default=False, description="Whether to plot points") + marker_style: str = Field(default=".", description="The marker style to use") + marker_size: int | float = Field(default=10.0, ge=1, description="The marker size to use") + marker_alpha: int | float = Field(default=1.0, description="The marker alpha to use") + zorder: int = Field(default=10, description="The zorder to use") + shift_params: bool = Field( + default=False, + description="Whether to shift the parameters by subtracting each parameters mean", + ) + + @field_validator("color") + @classmethod + def _convert_color(cls, v: ColorInput | None) -> str | None: + if v is None: + return None + return colors.format(v) + + def _apply_if_not_user_set(self, **kwargs: Any) -> None: + for key, value in kwargs.items(): + if key not in self._user_specified: + setattr(self, key, value) + + def _apply(self, **kwargs: Any) -> None: + for key, value in kwargs.items(): + setattr(self, key, value) + + +class Chain(ChainConfig): + """The numerical chain with its configuration.""" + + samples: pd.DataFrame = Field( + default=..., + description="The chain data as a pandas DataFrame", + ) + name: ChainName = Field( + default=..., + description="The name of the chain", + ) + + weight_column: ColumnName = Field( + default="weight", + description="The name of the weight column, if it exists", + ) + posterior_column: ColumnName = Field( + default="log_posterior", + description="The name of the log posterior column, if it exists", + ) + walkers: int = Field( + default=1, + ge=1, + description="The number of walkers in the chain", + ) + grid: bool = Field( + default=False, + description="Whether the chain is a sampled grid or not", + ) + num_free_params: int | None = Field( + default=None, + description="The number of free parameters in the chain", + ge=0, + ) + num_eff_data_points: float | None = Field( + default=None, + description="The number of effective data points", + ge=0, + ) + power: float = Field( + default=1.0, + description="Raise the posterior surface to this. Useful for inflating or deflating uncertainty for debugging.", + ) + + @property + def data_columns(self) -> list[str]: + """The columns in the dataframe which are not weights or posteriors.""" + results = [] + for c in self.samples.columns: + if c in {self.weight_column, self.posterior_column}: + continue + if c.lower() in { + "weight", + "weights", + "posterior", + "posteriors", + "log_weights", + "log_posterior", + "log_posteriors", + }: + continue + results.append(c) + return results + + @property + def data_samples(self) -> pd.DataFrame: + """The subsection of the dataframe with data points (ie excluding weights and posterior)""" + return self.samples[self.data_columns] + + @property + def plotting_columns(self) -> list[str]: + """The columns to be plotted, which are the dataframe columns + with the weights, posterior and colour coloumns removed.""" + cols = self.data_columns + if not self.plot_cloud: + return cols + return [c for c in cols if c != self.color_param] + + @property + def skip(self) -> bool: + """If the chain will be skipped in plotting because it has nothing to plot.""" + return self.samples.empty or not (self.plot_contour or self.plot_cloud or self.plot_point) + + @property + def max_posterior_row(self) -> pd.Series | None: + """The row of samples which correspond to the maximum posterior value. + None if the posterior is not supplied.""" + if self.posterior_column not in self.samples.columns: + logging.warning("No posterior column found, cannot find max posterior") + return None + argmax = self.samples[self.posterior_column].argmax() + return self.samples.loc[argmax] # type: ignore + + @property + def weights(self) -> np.ndarray: + """The column of weights in the samples.""" + return self.samples[self.weight_column].to_numpy() + + @property + def log_posterior(self) -> np.ndarray | None: + """The column of log posteriors in the samples. None if not set.""" + if self.posterior_column not in self.samples.columns: + return None + return self.samples[self.posterior_column].to_numpy() + + @property + def color_data(self) -> np.ndarray | None: + """The data from the color column. None if not set.""" + if self.color_param is None: + return None + return self.samples[self.color_param].to_numpy() + + @field_validator("color") + @classmethod + def _validate_color(cls, v: str | np.ndarray | list[float] | None) -> str | None: + if v is None: + return None + return colors.format(v) + + @field_validator("samples") + @classmethod + def _copy_df(cls, v: pd.DataFrame) -> pd.DataFrame: + return v.copy() + + @model_validator(mode="after") + def _validate_model(self) -> Chain: + assert not self.samples.empty, "Your chain is empty. This is not ideal." + + # If weights aren't set, add them all as one + if self.weight_column not in self.samples: + assert ( + self.weight_column == "weight" + ), f"weight column has been changed to {self.weight_column}, but its not in the dataframe" + + self.samples[self.weight_column] = 1.0 + else: + assert np.all(self.weights > 0), "Weights must be positive and non-zero" + + for column in self.samples.columns: + assert isinstance(column, str), f"Column {column} is not a string" + assert np.all(np.isfinite(self.samples[column])), f"Column {column} has NaN or inf in it" + + # Apply the mean shift if it is set to true + if self.shift_params: + for param in self.samples: + self.samples[param] -= np.average(self.samples[param], weights=self.weights) # type: ignore + + # Check the walkers + assert self.samples.shape[0] % self.walkers == 0, ( + f"Chain {self.name} has {self.samples.shape[0]} steps, " + "which is not divisible by {self.walkers} walkers. This is not good." + ) + + # And if the color_params are set, ensure they're in the dataframe + if self.color_param is not None: + assert ( + self.color_param in self.samples.columns + ), f"Chain {self.name} does not have color parameter {self.color_param}" + + # more nan checks + if self.num_eff_data_points is not None: + assert np.isfinite(self.num_eff_data_points), "num_eff_data_points is not finite" + + if self.num_free_params is not None: + assert np.isfinite(self.num_free_params), "num_free_params is not finite" + + return self + + def get_data(self, column: str) -> pd.Series[float]: + """Extracts a single columns from the samples dataframe.""" + return self.samples[column] + + @classmethod + def from_covariance( + cls, + mean: np.ndarray | list[float], + covariance: np.ndarray | list[list[float]], + columns: list[ColumnName], + name: ChainName, + **kwargs: Any, + ) -> Chain: + """Generate samples as per mean and covariance supplied. Useful for Fisher matrix forecasts. + + Args: + mean: The an array of mean values. + covariance: The 2D array describing the covariance. + Dimensions should agree with the `mean` input. + columns: A list of parameter names, one for each column (dimension) in the mean array. + name: The name of the chain. + kwargs: Any other arguments to pass to the Chain constructor. + + Returns: + The generated chain. + """ + rng = np.random.default_rng() + samples = rng.multivariate_normal(mean, covariance, size=1000000) # type: ignore + df = pd.DataFrame(samples, columns=columns) + return cls(samples=df, name=name, **kwargs) # type: ignore + + def divide(self) -> list[Chain]: + """Returns a ChainConsumer instance containing all the walks of a given chain + as individual chains themselves. + + This method might be useful if, for example, your chain was made using + MCMC with 4 walkers. To check the sampling of all 4 walkers agree, you could + call this to get a ChainConsumer instance with one chain for ech of the + four walks. If you then plot, hopefully all four contours + you would see agree. + + Returns: + One chain per walker, split evenly + """ + assert self.walkers > 1, "Cannot divide a chain with only one walker" + assert not self.grid, "Cannot divide a grid chain" + + splits = np.split(self.samples, self.walkers) + chains = [] + for i, split in enumerate(splits): + df = pd.DataFrame(split, columns=self.samples.columns) + options = self.model_dump(exclude={"samples", "name"}) + if "color" in options: + options.pop("color") + chain = Chain(samples=df, name=f"{self.name} Walker {i}", **options) + chains.append(chain) + + return chains + + def get_max_posterior_point(self) -> MaxPosterior | None: + """Returns the maximum posterior point in the chain. If the posterior + + Returns: + MaxPosterior: The maximum posterior point + """ + if self.max_posterior_row is None: + return None + row = self.max_posterior_row.to_dict() + log_posterior = row.pop(self.posterior_column) + row = {k: v for k, v in row.items() if k in self.plotting_columns} + return MaxPosterior(log_posterior=log_posterior, coordinate=row) + + def get_covariance(self, columns: list[str] | None = None) -> Named2DMatrix: + """Returns the covariance matrix of the chain. + + Args: + columns: The columns to use. None means all data columns. + + Returns: + Named2DMatrix: The covariance matrix + """ + if columns is None: + columns = self.data_columns + cov = np.cov(self.samples[columns], rowvar=False, aweights=self.weights) + return Named2DMatrix(columns=columns, matrix=cov) + + def get_correlation(self, columns: list[str] | None = None) -> Named2DMatrix: + """Returns the correlation matrix of the chain. + + Args: + columns: The columns to use. None means all data columns. + + Returns: + Named2DMatrix: The correlation matrix + """ + cov = self.get_covariance(columns) + diag = np.sqrt(np.diag(cov.matrix)) + divisor = diag[None, :] * diag[:, None] + correlations = cov.matrix / divisor + return Named2DMatrix(columns=cov.columns, matrix=correlations) + + +class MaxPosterior(BetterBase): + """A class that bundles the value of the + log posterior with the coordinate you can find it at.""" + + log_posterior: float + coordinate: dict[ColumnName, float] + + @property + def vec_coordinate(self) -> np.ndarray: + """The coordinate as a numpy array, in the order the columns were given.""" + return np.array(list(self.coordinate.values())) diff --git a/src/chainconsumer/chainconsumer.py b/src/chainconsumer/chainconsumer.py new file mode 100644 index 00000000..64d13495 --- /dev/null +++ b/src/chainconsumer/chainconsumer.py @@ -0,0 +1,203 @@ +from typing import Any + +import numpy as np +import pandas as pd + +from .analysis import Analysis +from .chain import Chain, ChainConfig, ChainName, ColumnName +from .color_finder import ColorInput, colors +from .comparisons import Comparison +from .diagnostic import Diagnostic +from .plotter import PlotConfig, Plotter +from .truth import Truth + +__all__ = ["ChainConsumer"] + + +class ChainConsumer: + """A class for consuming chains produced by an MCMC walk. Or grid searches. To make plots, + figures, tables, diagnostics, you name it.""" + + def __init__(self) -> None: + self._chains: dict[ChainName, Chain] = {} + self._truths: list[Truth] = [] + self._global_chain_override: ChainConfig | None = None + + self.plotter = Plotter(self) + """Use this to access all the plotting functions""" + self.diagnostic = Diagnostic(self) + """Use this to access your diagnostics to see if chains have converged.""" + self.comparison = Comparison(self) + """Use this to compare chains to each other, like ranking the AIC, BIC, and DIC.""" + self.analysis = Analysis(self) + """Use this to access the analysis functions, like getting summary statistics from your chains.""" + + @property + def _all_columns(self) -> list[str]: + """All the columns across all chains""" + return list(set([c for chain in self._chains.values() for c in chain.data_columns])) + + def add_truth(self, truth: Truth) -> "ChainConsumer": + """Add a truth to ChainConsumer. + + Args: + truth: The truth to add. + + Returns: + Itself, to allow chaining calls. + """ + self._truths.append(truth) + return self + + def add_chain(self, chain: Chain) -> "ChainConsumer": + """Add a chain to ChainConsumer. + + Args: + chain: The chain to add. + + Returns: + Itself, to allow chaining calls. + """ + key = chain.name + assert key not in self._chains, f"Chain with name {key} already exists!" + self._chains[key] = chain + return self + + def set_plot_config(self, plot_config: PlotConfig) -> "ChainConsumer": + """Set the plot config for ChainConsumer. + + Args: + plot_config: The plot config to use. + + Returns: + Itself, to allow chaining calls. + """ + self.plotter.set_config(plot_config) + return self + + def add_marker( + self, + location: dict[ColumnName, float], + name: str, + color: ColorInput | None = None, + marker_size: float = 20.0, + marker_style: str = ".", + marker_alpha: float = 1.0, + ) -> "ChainConsumer": + r"""Add a marker to the plot at the given location. + + Args: + location: The location of the marker. + name: The name of the marker. + color: The colour of the marker. Defaults to None. + marker_size: The size of the marker. Defaults to 20.0. + marker_style: The style of the marker. Defaults to ".". + marker_alpha: The alpha of the marker. Defaults to 1.0. + + + Returns: + Itself, to allow chaining calls. + """ + + samples = pd.DataFrame(location, index=[0]) + samples["weight"] = 1.0 + samples["log_posterior"] = 1.0 + kwargs = {} + if color is not None: + kwargs["color"] = color + chain = Chain( + samples=samples, + name=name, + marker_size=marker_size, + marker_style=marker_style, + marker_alpha=marker_alpha, + plot_contour=False, + plot_point=True, + **kwargs, + ) + return self.add_chain(chain) + + def remove_chain(self, remove: str | Chain) -> "ChainConsumer": + r"""Removes a chain from ChainConsumer. + + Args: + remove: The name of the chain to remove, or the chain itself. + + Returns: + Itself, to allow chaining calls. + """ + if isinstance(remove, Chain): + remove = remove.name + + assert remove in self._chains, f"Chain with name {remove} does not exist!" + self._chains.pop(remove) + return self + + def set_override( + self, + override: ChainConfig, + ) -> "ChainConsumer": + """Apply a custom override config + + Args: + override: The override config. Defaults to None. + + Returns: + Itself, to allow chaining calls. + """ + self._global_chain_override = override + return self + + def _get_final_chains(self) -> dict[ChainName, Chain]: + # Copy the original chain list + final_chains = {k: v.model_copy() for k, v in self._chains.items()} + num_chains = len(self._chains) + + # Note we only have to override things without a default + # and things which should change as the number of chains change + global_config: dict[str, Any] = {} + global_config["bar_shade"] = num_chains < 5 + global_config["sigmas"] = [0, 1, 2] + global_config["shade"] = num_chains < 5 + global_config["shade_alpha"] = 1.0 / np.sqrt(num_chains) + + color_iter = colors.next_colour() + + for _, chain in final_chains.items(): + # copy global config into local config + local_config = global_config.copy() + + # Reduce shade alpha if we're showing contour labels + if chain.show_contour_labels: + local_config["shade_alpha"] *= 0.5 + + # Check to see if the color is set + if chain.color is None: + local_config["color"] = next(color_iter) + + chain._apply_if_not_user_set(**local_config) + + # Apply user overrides + if self._global_chain_override is not None: + chain._apply(**self._global_chain_override.get_user_specified_dump()) + + return final_chains + + def get_chain(self, name: str) -> Chain: + """Get a chain by name. + + Args: + name: The name of the chain. + + Returns: + The chain. + """ + assert name in self._chains, f"Chain with name {name} does not exist!" + return self._chains[name] + + def get_names(self) -> list[str]: + """Get the names of all chains. + + Returns: + The names of all chains.""" + return list(self._chains.keys()) diff --git a/src/chainconsumer/color_finder.py b/src/chainconsumer/color_finder.py new file mode 100644 index 00000000..1020a606 --- /dev/null +++ b/src/chainconsumer/color_finder.py @@ -0,0 +1,128 @@ +from collections.abc import Generator, Iterable + +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.colors import rgb2hex + +# Colours drawn from tailwind: https://tailwindcss.com/docs/customizing-colors + +ColorInput = str | np.ndarray | list[float] + +ALL_COLOURS = { + "rose": ["#ffe4e6", "#fecdd3", "#fda4af", "#fb7185", "#f43f5e", "#e11d48", "#be123c", "#9f1239", "#881337"], + "pink": ["#fce7f3", "#fbcfe8", "#f9a8d4", "#f472b6", "#ec4899", "#db2777", "#be185d", "#9d174d", "#831843"], + "fuchsia": ["#fae8ff", "#f5d0fe", "#f0abfc", "#e879f9", "#d946ef", "#c026d3", "#a21caf", "#86198f", "#701a75"], + "purple": ["#f3e8ff", "#e9d5ff", "#d8b4fe", "#c084fc", "#a855f7", "#9333ea", "#7e22ce", "#6b21a8", "#581c87"], + "violet": ["#ede9fe", "#ddd6fe", "#c4b5fd", "#a78bfa", "#8b5cf6", "#7c3aed", "#6d28d9", "#5b21b6", "#4c1d95"], + "indigo": ["#e0e7ff", "#c7d2fe", "#a5b4fc", "#818cf8", "#6366f1", "#4f46e5", "#4338ca", "#3730a3", "#312e81"], + "blue": ["#dbeafe", "#bfdbfe", "#93c5fd", "#60a5fa", "#3b82f6", "#2563eb", "#1d4ed8", "#1e40af", "#1e3a8a"], + "sky": ["#e0f2fe", "#bae6fd", "#7dd3fc", "#38bdf8", "#0ea5e9", "#0284c7", "#0369a1", "#075985", "#0c4a6e"], + "cyan": ["#cffafe", "#a5f3fc", "#67e8f9", "#22d3ee", "#06b6d4", "#0891b2", "#0e7490", "#155e75", "#164e63"], + "teal": ["#ccfbf1", "#99f6e4", "#5eead4", "#2dd4bf", "#14b8a6", "#0d9488", "#0f766e", "#115e59", "#134e4a"], + "emerald": ["#d1fae5", "#a7f3d0", "#6ee7b7", "#34d399", "#10b981", "#059669", "#047857", "#065f46", "#064e3b"], + "green": ["#dcfce7", "#bbf7d0", "#86efac", "#4ade80", "#22c55e", "#16a34a", "#15803d", "#166534", "#14532d"], + "lime": ["#ecfccb", "#d9f99d", "#bef264", "#a3e635", "#84cc16", "#65a30d", "#4d7c0f", "#3f6212", "#365314"], + "yellow": ["#fef9c3", "#fef08a", "#fde047", "#facc15", "#eab308", "#ca8a04", "#a16207", "#854d0e", "#713f12"], + "amber": ["#fef3c7", "#fde68a", "#fcd34d", "#fbbf24", "#f59e0b", "#d97706", "#b45309", "#92400e", "#78350f"], + "orange": ["#ffedd5", "#fed7aa", "#fdba74", "#fb923c", "#f97316", "#ea580c", "#c2410c", "#9a3412", "#7c2d12"], + "red": ["#fee2e2", "#fecaca", "#fca5a5", "#f87171", "#ef4444", "#dc2626", "#b91c1c", "#991b1b", "#7f1d1d"], + "warmGray": ["#f5f5f4", "#e7e5e4", "#d6d3d1", "#a8a29e", "#78716c", "#57534e", "#44403c", "#292524", "#1c1917"], + "trueGray": ["#f5f5f5", "#e5e5e5", "#d4d4d4", "#a3a3a3", "#737373", "#525252", "#404040", "#262626", "#171717"], + "gray": ["#f4f4f5", "#e4e4e7", "#d4d4d8", "#a1a1aa", "#71717a", "#52525b", "#3f3f46", "#27272a", "#18181b"], + "coolGray": ["#f3f4f6", "#e5e7eb", "#d1d5db", "#9ca3af", "#6b7280", "#4b5563", "#374151", "#1f2937", "#111827"], + "blueGray": ["#f1f5f9", "#e2e8f0", "#cbd5e1", "#94a3b8", "#64748b", "#475569", "#334155", "#1e293b", "#0f172a"], +} + + +class Colors: + def __init__(self) -> None: + self.aliases: dict[str, str] = { + "b": "blue", + "r": "red", + "g": "green", + "k": "gray", + "f": "gray", + "m": "rose", + "c": "cyan", + "o": "orange", + "y": "yellow", + "a": "amber", + "p": "purple", + "e": "grey", + "lg": "lime", + "lb": "sky", + "black": "gray", + "white": "gray", + } + self.default_colors: tuple[str, ...] = ( + "blue", + "emerald", + "red", + "purple", + "amber", + "gray", + "cyan", + "teal", + "green", + "orange", + "indigo", + ) + self.iter = self.next_colour() + + def next_colour(self) -> Generator[str, None, None]: + """A generator to return a sequence of colors""" + for index in [4, 7, 2, 8, 3, 6, 1, 5]: + for color in self.default_colors: + yield ALL_COLOURS[color][index] + + def format(self, color: ColorInput | None) -> str: + if color is None: + return next(iter(self.next_colour())) + elif isinstance(color, np.ndarray | list): + color = rgb2hex(color) # type: ignore + if color[0] == "#": + return color + elif color in ALL_COLOURS: + return ALL_COLOURS[color][4] + elif color in self.aliases: + alias = self.aliases[color] + index = 4 + if color.lower() in ["k", "black"]: + index = -1 + elif color.lower() in ["f", "white"]: + index = 0 + return ALL_COLOURS[alias][index] + else: + raise ValueError(f"Color {color} is not mapped. Please give a hex code") + + def get_formatted(self, list_colors: Iterable[ColorInput]) -> list[str]: + return [self.format(c) for c in list_colors] + + def get_default(self) -> list[str]: + return self.get_formatted(self.default_colors) + + def get_colormap(self, num: int, cmap_name: str, scale: float = 0.7) -> list[str]: # pragma: no cover + color_list = self.get_formatted(plt.get_cmap(cmap_name)(np.linspace(0.05, 0.9, num))) + scales = scale + (1 - scale) * np.abs(1 - np.linspace(0, 2, num)) + scaled = [self.scale_colour(c, s) for c, s in zip(color_list, scales)] + return scaled + + def scale_colour(self, color: ColorInput, scalefactor: float) -> str: # pragma: no cover + hexx = self.format(color).strip("#") + if scalefactor < 0 or len(hexx) != 6: + return hexx + r, g, b = int(hexx[:2], 16), int(hexx[2:4], 16), int(hexx[4:], 16) + r = self._clamp(int(r * scalefactor)) + g = self._clamp(int(g * scalefactor)) + b = self._clamp(int(b * scalefactor)) + return f"#{r:02x}{g:02x}{b:02x}" + + def _clamp(self, val: int, minimum: int = 0, maximum: int = 255) -> int: + if val < minimum: + return minimum + if val > maximum: + return maximum + return val + + +colors = Colors() diff --git a/src/chainconsumer/comparisons.py b/src/chainconsumer/comparisons.py new file mode 100644 index 00000000..876162de --- /dev/null +++ b/src/chainconsumer/comparisons.py @@ -0,0 +1,221 @@ +from typing import Literal + +import numpy as np +import pandas as pd +from scipy.interpolate import griddata + +from .helpers import get_latex_table_frame +from .log import logger + + +class Comparison: + def __init__(self, parent: "ChainConsumer"): + self.parent = parent + + def dic(self) -> dict[str, float]: + r"""Returns the corrected Deviance Information Criterion (DIC) for all chains loaded into ChainConsumer. + + If a chain does not have a posterior, this method will return `None` for that chain. **Note that + the DIC metric is only valid on posterior surfaces which closely resemble multivariate normals!** + Formally, we follow Liddle (2007) and first define *Bayesian complexity* as + + .. math:: + p_D = \bar{D}(\theta) - D(\bar{\theta}), + + where :math:`D(\theta) = -2\ln(P(\theta)) + C` is the deviance, where :math:`P` is the posterior + and :math:`C` a constant. From here the DIC is defined as + + .. math:: + DIC \equiv D(\bar{\theta}) + 2p_D = \bar{D}(\theta) + p_D. + + Returns: + dict[str, float]: A dict of chain name to DIC value. + + References + ---------- + [1] Andrew R. Liddle, "Information criteria for astrophysical model selection", MNRAS (2007) + """ + dics = {} + for name, chain in self.parent._chains.items(): + p = chain.log_posterior + if p is None: + logger.warning("You need to set the posterior for chain %s to get the DIC" % chain.name) + else: + means = np.array([np.average(chain.samples[c], weights=chain.weights) for c in chain.data_columns]) + d = -2 * p + d_of_mean = griddata(chain.data_samples, d, means, method="nearest")[0] + mean_d = np.average(d, weights=chain.weights) + p_d = mean_d - d_of_mean + dic = mean_d + p_d + dics[name] = dic + if dics: + min_dic = np.min(list(dics.values())) + for name in dics: + dics[name] -= min_dic + return dics + + def bic(self) -> dict[str, float]: + r"""Returns the corrected Bayesian Information Criterion (BIC) for all chains loaded into ChainConsumer. + + If a chain does not have a posterior, number of data points, and number of free parameters + loaded, this method will return `None` for that chain. Formally, the BIC is defined as + + .. math:: + BIC \equiv -2\ln(P) + k \ln(N), + + where :math:`P` represents the posterior, :math:`k` the number of model parameters and :math:`N` + the number of independent data points used in the model fitting. + + Returns: + A dict of chain name to BIC value. + + """ + bics = {} + for name, chain in self.parent._chains.items(): + p, n_data, n_free = chain.log_posterior, chain.num_eff_data_points, chain.num_free_params + if p is None or n_data is None or n_free is None: + missing = "" + if p is None: + missing += "posterior, " + if n_data is None: + missing += "num_eff_data_points, " + if n_free is None: + missing += "num_free_params, " + + logger.warning(f"You need to set {missing[:-2]} for chain {name} to get the BIC") + else: + bics[name] = n_free * np.log(n_data) - 2 * np.max(p) + if bics: + min_bic = np.min(list(bics.values())) + for name in bics: + bics[name] -= min_bic + + return bics + + def aic(self) -> dict[str, float]: + r"""Returns the corrected Akaike Information Criterion (AICc) for all chains loaded into ChainConsumer. + + If a chain does not have a posterior, number of data points, and number of free parameters + loaded, this method will return `None` for that chain. Formally, the AIC is defined as + + .. math:: + AIC \equiv -2\ln(P) + 2k, + + where :math:`P` represents the posterior, and :math:`k` the number of model parameters. The AICc + is then defined as + + .. math:: + AIC_c \equiv AIC + \frac{2k(k+1)}{N-k-1}, + + where :math:`N` represents the number of independent data points used in the model fitting. + The AICc is a correction for the AIC to take into account finite chain sizes. + + Returns: + dict[str, float]: A dict of chain name to AIC value. + + """ + aics = {} + for name, chain in self.parent._chains.items(): + p, n_data, n_free = chain.log_posterior, chain.num_eff_data_points, chain.num_free_params + if p is None or n_data is None or n_free is None: + missing = "" + if p is None: + missing += "posterior, " + if n_data is None: + missing += "num_eff_data_points, " + if n_free is None: + missing += "num_free_params, " + + logger.warning(f"You need to set {missing[:-2]} for chain {chain.name} to get the AIC") + else: + c_cor = 1.0 * n_free * (n_free + 1) / (n_data - n_free - 1) + aics[name] = 2.0 * (n_free + c_cor - np.max(p)) + if aics: + min_aic = np.min(list(aics.values())) + for name in aics: + aics[name] -= min_aic + return aics + + def comparison_table( + self, + caption: str | None = None, + label: str = "tab:model_comp", + hlines: bool = True, + aic: bool = True, + bic: bool = True, + dic: bool = True, + sort: Literal["bic", "aic", "dic"] = "bic", + descending: bool = True, + ) -> str: # pragma: no cover + """ + Return a LaTeX ready table of model comparisons. + + Args: + caption (str, optional): The table caption to insert. Defaults to None. + label (str, optional): The table label to insert. Defaults to "tab:model_comp". + hlines (bool, optional): Whether to insert hlines in the table or not. Defaults to True. + aic (bool, optional): Whether to include a column for AICc or not. Defaults to True. + bic (bool, optional): Whether to include a column for BIC or not. Defaults to True. + dic (bool, optional): Whether to include a column for DIC or not. Defaults to True. + sort (str, optional): How to sort the models. Should be one of "bic", "aic" or "dic". Defaults to "bic". + descending (bool, optional): The sort order. Defaults to True. + + Returns: + str: A LaTeX table to be copied into your document. + """ + + if sort == "bic": + assert bic, "You cannot sort by BIC if you turn it off" + if sort == "aic": + assert aic, "You cannot sort by AIC if you turn it off" + if sort == "dic": + assert dic, "You cannot sort by DIC if you turn it off" + assert sort in ["bic", "aic", "dic"], f"sort {sort} not recognised, must be dic, aic or dic" + + if caption is None: + caption = "" + if label is None: + label = "" + + base_string = get_latex_table_frame(caption, label) + end_text = " \\\\ \n" + num_cols = 1 + (1 if aic else 0) + (1 if bic else 0) + column_text = "c" * (num_cols + 1) + center_text = "" + hline_text = "\\hline\n" + if hlines: + center_text += hline_text + center_text += ( + "\tModel" + (" & AIC" if aic else "") + (" & BIC " if bic else "") + (" & DIC " if dic else "") + end_text + ) + if hlines: + center_text += "\t" + hline_text + + series = {} + if aic: + series["aic"] = self.aic() + if bic: + series["bic"] = self.bic() + if dic: + series["dic"] = self.dic() + + df = pd.DataFrame(series).sort_values(by=sort, ascending=not descending) + for name, row in df.iterrows(): + chain_name: str = str(name) + line = "\t" + chain_name + if aic: + line += f" & {row['aic']:5.1f} " + if bic: + line += f" & {row['bic']:5.1f} " + if dic: + line += f" & {row['dic']:5.1f} " + line += end_text + center_text += line + if hlines: + center_text += "\t" + hline_text + + return base_string % (column_text, center_text) + + +if __name__ == "__main__": + from .chainconsumer import ChainConsumer diff --git a/src/chainconsumer/diagnostic.py b/src/chainconsumer/diagnostic.py new file mode 100644 index 00000000..16765776 --- /dev/null +++ b/src/chainconsumer/diagnostic.py @@ -0,0 +1,159 @@ +import numpy as np +from pydantic import Field +from scipy.stats import normaltest + +from .base import BetterBase +from .chain import Chain, ChainName +from .log import logger + + +class TestResult(BetterBase): + passed: bool = Field(default=..., description="Whether or not the test passed in general") + results: dict[ChainName, bool] = Field( + default=..., description="For each chain, whether the test passed and its numerical value" + ) + + def __bool__(self) -> bool: + return self.passed + + +def _sanitise_chains( + chains: list[Chain | ChainName] | Chain | ChainName | None, parent: "ChainConsumer" +) -> list[Chain]: + if chains is None: + return list(parent._chains.values()) + elif isinstance(chains, list): + return [c if isinstance(c, Chain) else parent.get_chain(c) for c in chains] + return [parent.get_chain(chains) if isinstance(chains, str) else chains] + + +class Diagnostic: + def __init__(self, parent: "ChainConsumer"): + self.parent: "ChainConsumer" = parent + + def gelman_rubin( + self, chains: list[Chain | ChainName] | Chain | ChainName | None = None, threshold: float = 0.05 + ) -> TestResult: + r"""Runs the Gelman Rubin diagnostic on the supplied chains. + + Args: + chains: Which chain to run the diagnostic on. None will run on all chains + threshold: The maximum deviation permitted from 1 for the final value $\hat{R}$ + + Returns: + The test results + + Notes: + + I follow PyMC in calculating the Gelman-Rubin statistic, where, + having :math:`m` chains of length :math:`n`, we compute + + .. math:: + + B = \frac{n}{m-1} \sum_{j=1}^{m} \left(\bar{\theta}_{.j} - \bar{\theta}_{..}\right)^2 + + W = \frac{1}{m} \sum_{j=1}^{m} \left[ \frac{1}{n-1} \sum_{i=1}^{n} \left( \theta_{ij} - \bar{\theta_{.j}}\right)^2 \right] + + where :math:`\theta` represents each model parameter. We then compute + :math:`\hat{V} = \frac{n_1}{n}W + \frac{1}{n}B`, and have our convergence ratio + :math:`\hat{R} = \sqrt{\frac{\hat{V}}{W}}`. We check that for all parameters, + this ratio deviates from unity by less than the supplied threshold. + """ # noqa: E501 + final_chains = _sanitise_chains(chains, parent=self.parent) + if len(final_chains) > 1: + results = [self.gelman_rubin(c, threshold=threshold) for c in final_chains] + passed = all([r.passed for r in results]) + combined_dict: dict[ChainName, bool] = {} + for result in results: + combined_dict |= result.results + return TestResult(passed=passed, results=combined_dict) + chain = final_chains[0] + + num_walkers = chain.walkers + parameters = chain.data_columns + name = chain.name + data = chain.data_samples + split_samples = np.split(data, num_walkers) + assert num_walkers > 1, "Cannot run Gelman-Rubin statistic with only one walker" + m = 1.0 * len(split_samples) + n = 1.0 * split_samples[0].shape[0] + all_mean = np.mean(data.to_numpy(), axis=0) + chain_means = np.array([np.mean(c, axis=0) for c in split_samples]) + chain_var = np.array([np.var(c, axis=0, ddof=1) for c in split_samples]) + b = n / (m - 1) * ((chain_means - all_mean) ** 2).sum(axis=0) + w = (1 / m) * chain_var.sum(axis=0) + var = (n - 1) * w / n + b / n + v = var + b / (n * m) + r: float = np.sqrt(v / w) + + passed = np.abs(r - 1) < threshold + logger.info("Gelman-Rubin Statistic values for chain %s" % name) + for p, v, pas in zip(parameters, r, passed): + param = "Param %d" % p if isinstance(p, int) else p + logger.info(f"{param}: {v:7.5f} ({'Passed' if pas else 'Failed'})") + all_passed: bool = np.all(passed) + return TestResult(passed=all_passed, results={chain.name: all_passed}) + + def geweke( + self, + chains: list[Chain | ChainName] | Chain | ChainName | None = None, + first: float = 0.1, + last: float = 0.5, + threshold: float = 0.05, + ) -> TestResult: + """Runs the Geweke diagnostic on the supplied chains. + + Args: + chains: Which chain to run the diagnostic on. None will run on all chains + first: The amount of the start of the chain to use + last: The end amount of the chain to use + threshold: The p-value to use when testing for normality. + + Returns: + The test results + """ + final_chains = _sanitise_chains(chains, parent=self.parent) + if len(final_chains) > 1: + results = [self.geweke(c, first=first, last=last, threshold=threshold) for c in final_chains] + passed = all([r.passed for r in results]) + combined_dict = {} + for r in results: + combined_dict |= r.results + return TestResult(passed=passed, results=combined_dict) + chain = final_chains[0] + + num_walkers = chain.walkers + assert ( + num_walkers is not None and num_walkers > 0 + ), "You need to specify the number of walkers to use the Geweke diagnostic." + name = chain.name + data = chain.data_samples + split_samples = np.split(data.to_numpy(), num_walkers) + n = 1.0 * split_samples[0].shape[0] + n_start = int(np.floor(first * n)) + n_end = int(np.floor((1 - last) * n)) + mean_start = np.array([np.mean(c[:n_start, i]) for c in split_samples for i in range(c.shape[1])]) + var_start = np.array( + [self._spec(c[:n_start, i]) / c[:n_start, i].size for c in split_samples for i in range(c.shape[1])] + ) + mean_end = np.array([np.mean(c[n_end:, i]) for c in split_samples for i in range(c.shape[1])]) + var_end = np.array( + [self._spec(c[n_end:, i]) / c[n_end:, i].size for c in split_samples for i in range(c.shape[1])] + ) + zs = (mean_start - mean_end) / (np.sqrt(var_start + var_end)) + _, pvalue = normaltest(zs) + logger.info(f"Gweke Statistic for chain {name} has p-value {pvalue:e}") + passed = pvalue > threshold + return TestResult(passed=passed, results={chain.name: passed}) + + # Method of estimating spectral density following PyMC. + # See https://github.com/pymc-devs/pymc/blob/master/pymc/diagnostics.py + def _spec(self, x, order=2): + from statsmodels.regression.linear_model import yule_walker + + beta, sigma = yule_walker(x, order) # type: ignore + return sigma**2 / (1.0 - np.sum(beta)) ** 2 + + +if __name__ == "__main__": + from chainconsumer import ChainConsumer diff --git a/src/chainconsumer/examples.py b/src/chainconsumer/examples.py new file mode 100644 index 00000000..8ef2247a --- /dev/null +++ b/src/chainconsumer/examples.py @@ -0,0 +1,29 @@ +import numpy as np +import pandas as pd +from scipy.stats import multivariate_normal as mv + + +def make_sample( + num_dimensions: int = 2, + seed: int | None = None, + randomise_mean: bool = False, + num_points: int = 1000000, +) -> pd.DataFrame: + gen = np.random.default_rng(seed) + vals = gen.random((num_dimensions, num_dimensions)) - 0.5 + cov = np.dot(vals, vals.T) + diag = np.sqrt(np.diag(cov)) + outer = np.outer(diag, diag) + cor = cov / outer + means = np.arange(num_dimensions) * 1.0 + if randomise_mean: + means += gen.uniform(-1, 1, num_dimensions) + norm = mv(mean=means, cov=cor) + samples = norm.rvs(size=num_points) + # Use the letters of the alphabet as the column names + columns = [chr(65 + i) for i in range(num_dimensions)] + return pd.DataFrame(samples, columns=columns).assign(log_posterior=norm.logpdf(samples)) + + +if __name__ == "__main__": + make_sample() diff --git a/src/chainconsumer/helpers.py b/src/chainconsumer/helpers.py new file mode 100644 index 00000000..aea44c96 --- /dev/null +++ b/src/chainconsumer/helpers.py @@ -0,0 +1,141 @@ +from __future__ import annotations + +import numpy as np +import pandas as pd +from scipy.ndimage import gaussian_filter + +from .chain import Chain +from .kde import MegKDE + + +def get_extents( + data: pd.Series, + weight: np.ndarray, + plot: bool = False, + wide_extents: bool = True, + tiny: bool = False, + pad: bool = False, +) -> tuple[float, float]: + hist, be = np.histogram(data, weights=weight, bins=2000) + bc = 0.5 * (be[1:] + be[:-1]) + cdf = hist.cumsum() + cdf = cdf / cdf.max() + icdf = (1 - cdf)[::-1] + icdf = icdf / icdf.max() + cdf = 1 - icdf[::-1] + threshold = 1e-4 if plot else 1e-5 + if plot and not wide_extents: + threshold = 0.05 + if tiny: + threshold = 0.3 + i1 = np.where(cdf > threshold)[0][0] + i2 = np.where(icdf > threshold)[0][0] + lower = float(bc[i1]) + upper = float(bc[-i2]) + if pad: + width = upper - lower + lower -= 0.2 * width + upper += 0.2 * width + return lower, upper + + +def get_bins(chain: Chain) -> int: + if chain.bins is not None: + return chain.bins + max_v = 35 if chain.smooth > 0 else 100 + return max((max_v, np.floor(1.0 * np.power(chain.samples.shape[0] / chain.samples.shape[1], 0.25)))) + + +def get_smoothed_bins( + smooth: int, + bins: int, + data: pd.Series, + weight: np.ndarray, + plot: bool = False, + pad: bool = False, +) -> tuple[np.ndarray, int]: + """Get the bins for a histogram, with smoothing. + + Args: + smooth (int): The smoothing factor + bins (int): The number of bins + data (pd.Series): The data + weight (np.ndarray): The weights + plot (bool, optional): Whether this is used in plotting. Determines how conservative to be on extents + Defaults to False. + pad (bool, optional): Whether to pad the histogram. Determines how conservative to be on extents + Defaults to False. + """ + minv, maxv = get_extents(data, weight, plot=plot, pad=pad) + if smooth == 0: + return np.linspace(minv, maxv, int(bins)), 0 + else: + return np.linspace(minv, maxv, 2 * smooth * bins), smooth + + +def get_smoothed_histogram2d( + chain: Chain, + col1: str, + col2: str, +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: # pragma: no cover + """Returns a smoothed 2D histogram of two parameters. + + Args: + chain (Chain): The chain to plot + col1 (str): The first parameter + col2 (str): The second parameter + + Returns: + tuple[np.ndarray, np.ndarray, np.ndarray]: The histogram, x bin enters, y bin centers + """ + x = chain.get_data(col1) + y = chain.get_data(col2) + w = chain.weights + + if chain.grid: + binsx = get_grid_bins(x) + binsy = get_grid_bins(y) + hist, x_bins, y_bins = np.histogram2d(x, y, bins=[binsx, binsy], weights=w) + else: + binsx, smooth = get_smoothed_bins(chain.smooth, get_bins(chain), x, w) + binsy, _ = get_smoothed_bins(smooth, get_bins(chain), y, w) + hist, x_bins, y_bins = np.histogram2d(x, y, bins=[binsx, binsy], weights=w) + + if chain.power is not None: + hist = hist**chain.power + + x_centers = 0.5 * (x_bins[:-1] + x_bins[1:]) + y_centers = 0.5 * (y_bins[:-1] + y_bins[1:]) + + if chain.kde: + nn = x_centers.size * 2 # Double samples for KDE because smooth + x_centers = np.linspace(x_bins.min(), x_bins.max(), nn) + y_centers = np.linspace(y_bins.min(), y_bins.max(), nn) + xx, yy = np.meshgrid(x_centers, y_centers, indexing="ij") + coords = np.vstack((xx.flatten(), yy.flatten())).T + data = np.vstack((x, y)).T + hist = MegKDE(data, w, chain.kde).evaluate(coords).reshape((nn, nn)) + if chain.power is not None: + hist = hist**chain.power + elif chain.smooth: + hist = gaussian_filter(hist, chain.smooth, mode="reflect") + + return hist, x_centers, y_centers + + +def get_grid_bins(data: pd.Series[float]) -> np.ndarray: + bin_c = np.sort(np.unique(data)) + delta = 0.5 * (bin_c[1] - bin_c[0]) + bins = np.concatenate((bin_c - delta, [bin_c[-1] + delta])) + return bins + + +def get_latex_table_frame(caption: str, label: str) -> str: # pragma: no cover + base_string = rf"""\begin{{table}} + \centering + \caption{{{caption}}} + \label{{{label}}} + \begin{{tabular}}{{%s}} + %s \end{{tabular}} +\end{{table}}""" + return base_string diff --git a/chainconsumer/kde.py b/src/chainconsumer/kde.py similarity index 61% rename from chainconsumer/kde.py rename to src/chainconsumer/kde.py index c16dddde..b4eebcb3 100644 --- a/chainconsumer/kde.py +++ b/src/chainconsumer/kde.py @@ -1,29 +1,30 @@ -# -*- coding: utf-8 -*- -from scipy import spatial import numpy as np +from scipy import spatial + +class MegKDE: + """Matched Elliptical Gaussian Kernel Density Estimator -class MegKDE(object): - """ Matched Elliptical Gaussian Kernel Density Estimator - Adapted from the algorithm specified in the BAMBIS's model specified Wolf 2017 to support weighted samples. """ - def __init__(self, train, weights=None, truncation=3.0, nmin=4, factor=1.0): + def __init__( + self, + train: np.ndarray, + weights: np.ndarray | None = None, + truncation: float = 3.0, + nmin: int = 4, + factor: float = 1.0, + ): """ - Parameters - ---------- - train : np.ndarray - The training data set. Should be a 1D array of samples or a 2D array of shape (n_samples, n_dim). - weights : np.ndarray, optional - An array of weights. If not specified, equal weights are assumed. - truncation : float, optional - The maximum deviation (in sigma) to use points in the KDE - nmin : int, optional - The minimum number of points required to estimate the density - factor : float, optional - Send bandwidth to this factor of the data estimate + Args: + train (np.ndarray): The training data set. Should be a 1D array of samples or a 2D array + of shape (n_samples, n_dim). + weights (np.ndarray, optional): An array of weights. If not specified, equal weights are assumed. + truncation (float, optional): The maximum deviation (in sigma) to use points in the KDE + nmin (int, optional): The minimum number of points required to estimate the density + factor (float, optional): Send bandwidth to this factor of the data estimate """ self.truncation = truncation @@ -51,20 +52,15 @@ def __init__(self, train, weights=None, truncation=3.0, nmin=4, factor=1.0): # self.norm = np.product(np.diagonal(self.A)) * (2 * np.pi) ** (-0.5 * self.num_dim) # prob norm # self.scaling = np.power(self.norm * self.sigma, -self.num_dim) - def evaluate(self, data): - """ Estimate un-normalised probability density at target points - - Parameters - ---------- - data : np.ndarray - A `(num_targets, num_dim)` array of points to investigate. - - Returns - ------- - np.ndarray - A `(num_targets)` length array of estimates + def evaluate(self, data: np.ndarray) -> np.ndarray: + """Estimate un-normalised probability density at target points + + Args: + data (np.ndarray): 2D array of shape (n_samples, n_dim). + + Returns: + np.ndarray: A `(n_samples)` length array of estimates - Returns array of probability densities """ if len(data.shape) == 1 and self.num_dim == 1: data = np.atleast_2d(data).T @@ -72,7 +68,7 @@ def evaluate(self, data): _d = np.dot(data - self.mean, self.A) # Get all points within range of kernels - neighbors = self.tree.query_ball_point(_d, self.sigma * self.truncation) + neighbors = self.tree.query_ball_point(_d, self.sigma * self.truncation) # type: ignore out = [] for i, n in enumerate(neighbors): if len(n) >= self.nmin: @@ -80,7 +76,7 @@ def evaluate(self, data): distsq = np.sum(diff * diff, axis=1) else: # If too few points get nmin closest - dist, n = self.tree.query(_d[i], k=self.nmin) + dist, n = self.tree.query(_d[i], k=self.nmin) # noqa: PLW2901 # TODO: I assume this isnt an error distsq = dist * dist out.append(np.sum(self.weights[n] * np.exp(self.sigma_fact * distsq))) return np.array(out) # * self.scaling diff --git a/src/chainconsumer/log.py b/src/chainconsumer/log.py new file mode 100644 index 00000000..18340627 --- /dev/null +++ b/src/chainconsumer/log.py @@ -0,0 +1,3 @@ +import logging + +logger = logging.getLogger("chainconsumer") diff --git a/src/chainconsumer/plotter.py b/src/chainconsumer/plotter.py new file mode 100644 index 00000000..2e83d0ff --- /dev/null +++ b/src/chainconsumer/plotter.py @@ -0,0 +1,1297 @@ +from enum import Enum +from pathlib import Path +from typing import Any + +import matplotlib +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from matplotlib.artist import Artist +from matplotlib.axes import Axes +from matplotlib.collections import PathCollection +from matplotlib.figure import Figure +from matplotlib.font_manager import FontProperties +from matplotlib.lines import Line2D +from matplotlib.textpath import TextPath +from matplotlib.ticker import LogLocator, MaxNLocator, ScalarFormatter +from pydantic import Field +from scipy.interpolate import interp1d # type: ignore +from scipy.stats import norm # type: ignore + +from chainconsumer.truth import Truth + +from .base import BetterBase +from .chain import Chain, ChainName, ColumnName +from .color_finder import ColorInput, colors +from .helpers import get_bins, get_extents, get_grid_bins, get_smoothed_bins, get_smoothed_histogram2d + + +class PlottingBase(BetterBase): + chains: list[Chain] + columns: list[ColumnName] + extents: dict[ColumnName, tuple[float, float]] + blind: list[ColumnName] + log_scales: list[ColumnName] + + +class PlotConfig(BetterBase): + labels: dict[ColumnName, str] = Field(default={}, description="Labels for parameters") + max_ticks: int = Field(default=5, ge=0, description="Maximum number of ticks to use on axes") + plot_hists: bool = Field(default=True, description="Whether to plot the 1D histograms") + flip: bool = Field(default=False, description="Whether to flip the 1D histograms") + serif: bool = Field(default=False, description="Whether to use a serif font") + usetex: bool = Field(default=False, description="Whether to use LaTeX for text rendering") + diagonal_tick_labels: bool = Field(default=True, description="Whether to show tick labels on the diagonal") + label_font_size: int = Field(default=12, ge=0, description="Font size for axis labels") + tick_font_size: int = Field(default=10, ge=0, description="Font size for axis ticks") + spacing: float | None = Field(default=None, ge=0, description="Spacing between subplots") + contour_label_font_size: int = Field(default=10, ge=0, description="Font size for contour labels") + show_legend: bool | None = Field( + default=None, + description="Whether to show the legend. None means determine automatically", + ) + legend_kwargs: dict[str, Any] = Field(default={}, description="Kwargs to pass to the legend") + legend_location: tuple[int, int] | None = Field(default=None, description="Which subplot to put the legend in") + legend_artists: bool | None = Field(default=None, description="Whether to show artists in the legend") + legend_color_text: bool = Field(default=True, description="Whether to color the legend text") + watermark: str | None = Field(default=None, description="Watermark text to add to the plot") + watermark_text_kwargs: dict[str, Any] = Field(default={}, description="Kwargs to pass to the watermark text") + summarise: bool = Field(default=True, description="Whether to annotate the plot with summary statistics") + summary_font_size: int = Field(default=12, ge=0, description="Font size for parameter summaries") + sigma2d: bool | None = Field( + default=None, + description=( + "Whether to use 2D sigmas for summary statistics. Ie in 2D a 1sigma contour" + r" does *not* encapsulate 68% of the volume, it covers 39.3% of the volume." + ), + ) + blind: bool | list[str] = Field(default=False, description="Whether to blind some parameters") + log_scales: list[ColumnName] = Field(default=[], description="Whether to use log scales for some parameters") + extents: dict[ColumnName, tuple[float, float]] = Field( + default={}, description="Extents for parameters. Any you don't specify are determined automatically" + ) + dpi: int = Field(default=300, ge=0, description="DPI for the figure") + + @property + def legend_kwargs_final(self) -> dict[str, Any]: + default = { + "labelspacing": 0.3, + "loc": "upper right", + "frameon": False, + "fontsize": self.label_font_size, + "handlelength": 1, + "handletextpad": 0.2, + "borderaxespad": 0.0, + } + return default | self.legend_kwargs + + @property + def watermark_text_kwargs_final(self) -> dict[str, Any]: + default = { + "color": "#333333", + "alpha": 0.7, + "verticalalignment": "center", + "horizontalalignment": "center", + "weight": "bold", + } + return default | self.watermark_text_kwargs + + def get_label(self, column: ColumnName) -> str: + return self.labels.get(column, column) + + +class FigSize(Enum): + """Enum for figure size options""" + + COLUMN = "COLUMN" + PAGE = "PAGE" + GROW = "GROW" + + @classmethod + def get_size( + cls, input: "FigSize | float | int | tuple[float, float]", num_columns: int, has_cax: bool + ) -> tuple[float, float]: + if input == FigSize.PAGE: + return 10, 10 + if input == FigSize.COLUMN: + return 5 + (1 if has_cax else 0), 5 + grow_factor = 1.0 + if isinstance(input, float): + grow_factor = input + elif isinstance(input, int): + return float(input), float(input) + elif isinstance(input, tuple): + return input + + # Otherwise it must be grow, which is the default + return 3 + grow_factor * 2 * num_columns + (1 if has_cax else 0), 3 + grow_factor * 2 * num_columns + + +def get_artists_from_chains(chains: list[Chain]) -> list[Artist]: + artists: list[Artist] = [] + for chain in chains: + if chain.plot_contour and not chain.plot_point: + artists.append( + Line2D( + (0, 1), + (0, 0), + color=colors.format(chain.color), + ls=chain.linestyle, + lw=chain.linewidth, + label=" " + chain.name, + ) + ) + elif not chain.plot_contour and chain.plot_point: + artists.append( + Line2D( + (0, 1), + (0, 0), + color=colors.format(chain.color), + ls=chain.linestyle, + lw=0, + marker=chain.marker_style, + markersize=np.sqrt(chain.marker_size), + label=" " + chain.name, + ) + ) + else: + artists.append( + Line2D( + (0, 1), + (0, 0), + color=colors.format(chain.color), + ls=chain.linestyle, + lw=chain.linewidth, + marker=chain.marker_style, + markersize=np.sqrt(chain.marker_size), + label=" " + chain.name, + ) + ) + return artists + + +class Plotter: + def __init__(self, parent: "ChainConsumer") -> None: + self.parent: "ChainConsumer" = parent + self._config: PlotConfig | None = None + self._default_config = PlotConfig() + + self.usetex_old = matplotlib.rcParams["text.usetex"] + self.serif_old = matplotlib.rcParams["font.family"] + + def set_config(self, config: PlotConfig) -> None: + self._config = config + + @property + def config(self) -> PlotConfig: + if self._config is None: + return self._default_config + return self._config + + def plot( + self, + chains: list[ChainName | Chain] | None = None, + columns: list[ColumnName] | None = None, + filename: list[str | Path] | str | Path | None = None, + figsize: FigSize | float | int | tuple[float, float] = FigSize.GROW, + ) -> Figure: # pragma: no cover + """Plot the chain! + + Args: + chains: + Used to specify which chain to show if more than one chain is loaded in. + Can be an integer, specifying the + chain index, or a str, specifying the chain name. + columns: + If set, only creates a plot for those specific parameters (if list). If an + integer is given, only plots the fist so many parameters. + filename: + If set, saves the figure to this location + figsize: + The figure size to generate. Accepts a regular two tuple of size in inches, + or one of several key words. The default value of ``COLUMN`` creates a figure + of appropriate size of insertion into an A4 LaTeX document in two-column mode. + ``PAGE`` creates a full page width figure. ``GROW`` creates an image that + scales with parameters (1.5 inches per parameter). String arguments are not + case sensitive. If you pass a float, it will scale the default ``GROW`` by + that amount, so ``2.0`` would result in a plot 3 inches per parameter. + + Returns: + the matplotlib figure + + """ + base = self._sanitise( + chains, columns, self.config.extents, blind=self.config.blind, log_scales=self.config.log_scales + ) + + show_legend = self.config.show_legend + if show_legend is None: + show_legend = len(base.chains) > 1 + + num_cax = len(set([chain.color_param for chain in base.chains if chain.color_param is not None])) + fig_size = FigSize.get_size(figsize, len(base.columns), num_cax > 0) + plot_hists = self.config.plot_hists + flip = len(base.columns) == 2 and plot_hists and self.config.flip + fig, axes, params_x, params_y = self._get_triangle_figure(base, figsize=fig_size) + + axl = axes.ravel().tolist() + summarise = self.config.summarise and len(base.chains) == 1 + + cbar_done = [] + for i, p1 in enumerate(params_x): + for j, p2 in enumerate(params_y): + if i < j: + continue + ax: Axes = axes[i, j] + do_flip = flip and i == len(params_x) - 1 + + # Plot the histograms + if plot_hists and i == j: + for truth in self.parent._truths: + if do_flip: + self._add_truth(ax, truth, px=p1) + else: + self._add_truth(ax, truth, py=p2) + max_val = None + + # Plot each chain + for chain in base.chains: + if not chain.plot_contour or p1 not in chain.samples: + continue + + do_summary = summarise and p1 not in base.blind + max_hist_val = self._plot_bars(ax, p1, chain, flip=do_flip, summary=do_summary) + + if max_val is None or max_hist_val > max_val: + max_val = max_hist_val + + if max_val is not None: + if do_flip: + ax.set_xlim(0, 1.1 * max_val) + else: + ax.set_ylim(0, 1.1 * max_val) + + else: + for chain in base.chains: + if p1 not in chain.samples or p2 not in chain.samples: + continue + + if chain.plot_contour: + h = self._plot_contour(ax, chain, p1, p2) + cp = chain.color_param + if h is not None and cp is not None and cp not in cbar_done: + cbar_done.append(cp) + aspect = fig_size[1] / 0.15 + fraction = 0.85 / fig_size[0] + cbar = fig.colorbar( + h, ax=axl, aspect=aspect, pad=0.03, fraction=fraction, drawedges=False + ) + label = self.config.get_label(cp) + if label == "weight": + label = "Weights" + elif label == "log_weight": + label = "log(Weights)" + elif label == "posterior": + label = "log(Posterior)" + cbar.set_label(label, fontsize=self.config.label_font_size) + if cbar.solids is not None: + cbar.solids.set(alpha=1) + + if chain.plot_point: + self._plot_point(ax, chain, p2, p1) + + for truth in self.parent._truths: + self._add_truth(ax, truth, px=p1, py=p2) + + legend_location = self.config.legend_location + if legend_location is None: + legend_location = (0, -1) if not flip or len(base.columns) > 2 else (-1, 0) + legend_outside = legend_location[0] >= legend_location[1] + + if show_legend: + ax = axes[legend_location[0], legend_location[1]] + legend_kwargs = self.config.legend_kwargs_final.copy() + if "markerfirst" not in legend_kwargs: + legend_kwargs["markerfirst"] = legend_outside or not self.config.legend_artists + + artists = get_artists_from_chains(base.chains) + leg = ax.legend(handles=artists, **legend_kwargs) + if self.config.legend_color_text: + for text, chain in zip(leg.get_texts(), base.chains): + text.set_fontweight("medium") + text.set_color(colors.format(chain.color)) + fig.canvas.draw() + for ax in axes[-1, :]: + offset = ax.get_xaxis().get_offset_text() + ax.set_xlabel("{} {}".format(ax.get_xlabel(), f"[{offset.get_text()}]" if offset.get_text() else "")) + offset.set_visible(False) + for ax in axes[:, 0]: + offset = ax.get_yaxis().get_offset_text() + ax.set_ylabel("{} {}".format(ax.get_ylabel(), f"[{offset.get_text()}]" if offset.get_text() else "")) + offset.set_visible(False) + + if self.config.watermark is not None: + ax_watermark = axes[-1, 0] if flip and len(base.columns) == 2 else None + self._add_watermark(fig, ax_watermark, fig_size, self.config.watermark, dpi=self.config.dpi) + + if filename is not None: + if not isinstance(filename, list): + filename = [filename] + for f in filename: + self._save_fig(fig, f, self.config.dpi) + + return fig + + def _save_fig(self, fig: Figure, filename: str | Path, dpi: int) -> None: # pragma: no cover + fig.savefig(filename, bbox_inches="tight", dpi=dpi, transparent=True, pad_inches=0.05) + + def _add_watermark( + self, + fig: Figure, + axes: Axes | None, + fig_size: tuple[float, float], + text: str, + dpi: int = 300, + size_scale: float = 1.0, + ) -> None: # pragma: no cover + # Code based off github repository https://github.com/cpadavis/preliminize + dx, dy = fig_size + dy, dx = dy * dpi, dx * dpi + rotation = 180 / np.pi * np.arctan2(-dy, dx) + property_dict = self.config.watermark_text_kwargs_final + + keys_in_font_dict = ["family", "style", "variant", "weight", "stretch", "size"] + fontdict = {k: property_dict[k] for k in keys_in_font_dict if k in property_dict} + font_prop = FontProperties(**fontdict) + usetex = property_dict.get("usetex", self.config.usetex) + if usetex: + px, py, scale = 0.5, 0.5, 1.0 + else: + px, py, scale = 0.5, 0.5, 0.8 + + bb0 = TextPath((0, 0), text, size=50, prop=font_prop, usetex=usetex).get_extents() + bb1 = TextPath((0, 0), text, size=51, prop=font_prop, usetex=usetex).get_extents() + dw = (bb1.width - bb0.width) * (dpi / 100) + dh = (bb1.height - bb0.height) * (dpi / 100) + size = np.sqrt(dy**2 + dx**2) / (dh * abs(dy / dx) + dw) * 0.7 * scale * size_scale + if axes is not None: + if usetex: + size *= 0.7 + else: + size *= 0.8 + size = int(size) + if axes is None: + fig.text(px, py, text, fontdict=property_dict, rotation=rotation, fontsize=size) + else: + axes.text(px, py, text, transform=axes.transAxes, fontdict=property_dict, rotation=rotation, fontsize=size) + + def plot_walks( + self, + chains: list[ChainName | Chain] | None = None, + columns: list[ColumnName] | None = None, + filename: list[str | Path] | str | Path | None = None, + figsize: float | tuple[float, float] | None = None, + convolve: int | None = None, + plot_weights: bool = True, + plot_posterior: bool = True, + log_weight: bool = False, + ) -> Figure: # pragma: no cover + """Plots the chain walk; the parameter values as a function of step index. + + This plot is more for a sanity or consistency check than for use with final results. + Plotting this before plotting with :func:`plot` allows you to quickly see if the + chains are well behaved, or if certain parameters are suspect + or require a greater burn in period. + + The desired outcome is to see an unchanging distribution along the x-axis of the plot. + If there are obvious tails or features in the parameters, you probably want + to investigate. + + Args: + chains: + Used to specify which chain to show if more than one chain is loaded in. + Can be an integer, specifying the + chain index, or a str, specifying the chain name. + columns: + If set, only creates a plot for those specific parameters (if list). If an + integer is given, only plots the fist so many parameters. + filename: + If set, saves the figure to this location + figsize: + Scale horizontal and vertical figure size. + col_wrap: + How many columns to plot before wrapping. + convolve: + If set, overplots a smoothed version of the steps using ``convolve`` as + the width of the smoothing filter. + plot_weights: + If true, plots the weight if they are available + plot_posterior: + If true, plots the log posterior if they are available + log_weight: + Whether to display weights in log space or not. If None, the value is + inferred by the mean weights of the plotted chains. + + Returns: + the matplotlib figure created + + """ + + base = self._sanitise( + chains, + columns, + self.config.extents, + blind=self.config.blind, + log_scales=self.config.log_scales, + ) + + n = len(base.columns) + extra = 0 + + plot_posterior = plot_posterior and any([c.log_posterior is not None for c in base.chains]) + if plot_weights: + extra += 1 + if plot_posterior: + extra += 1 + + if figsize is None: + figsize = (8, 0.75 + (n + extra)) + + fig, axes = plt.subplots(figsize=figsize, nrows=n + extra, squeeze=False, sharex=True) + max_points = 100000 + for i, axes_row in enumerate(axes): + ax = axes_row[0] + if i >= extra: + p = base.columns[i - extra] + for chain in base.chains: + if p in chain.data_columns: + chain_row = chain.get_data(p) + if len(chain_row) > max_points: + chain_row = chain_row[:: int(len(chain_row) / max_points)] + log = p in base.log_scales + self._plot_walk( + ax, + p, + chain_row, + extents=base.extents.get(p), + convolve=convolve, + color=colors.format(chain.color), + log_scale=log, + ) + for truth in self.parent._truths: + if p in truth.location: + self._plot_walk_truth(ax, truth, p) + + if p in base.blind: + ax.set_yticks([]) + else: # noqa: PLR5501 + if i == 0 and plot_posterior: + for chain in base.chains: + if chain.log_posterior is not None: + posterior = chain.log_posterior - chain.log_posterior.max() + if len(posterior) > max_points: + posterior = posterior[:: int(len(posterior) / max_points)] + + self._plot_walk( + ax, + r"$\log(P)$", + posterior, + convolve=convolve, + color=colors.format(chain.color), + ) + else: + label = r"$\log_{10}$Weight" if log_weight else "Weight" + + for chain in base.chains: + if chain.weights is not None: + weights = chain.weights + if len(weights) > max_points: + weights = weights[:: int(len(weights) / max_points)] + self._plot_walk( + ax, + label, + np.log10(weights) if log_weight else weights, # type: ignore + convolve=convolve, + color=colors.format(chain.color), + ) + + if filename is not None: + if not isinstance(filename, list): + filename = [filename] + for f in filename: + self._save_fig(fig, f, self.config.dpi) + + return fig + + def plot_distributions( + self, + chains: list[ChainName | Chain] | None = None, + columns: list[ColumnName] | None = None, + filename: list[str | Path] | str | Path | None = None, + col_wrap: int = 4, + figsize: float | tuple[float, float] | None = None, + ) -> Figure: # pragma: no cover + """Plots the 1D parameter distributions for verification purposes. + + This plot is more for a sanity or consistency check than for use with final results. + Plotting this before plotting with :func:`plot` allows you to quickly see if the + chains give well behaved distributions, or if certain parameters are suspect + or require a greater burn in period. + + Args: + chains: + Used to specify which chain to show if more than one chain is loaded in. + Can be an integer, specifying the + chain index, or a str, specifying the chain name. + columns: + If set, only creates a plot for those specific parameters (if list). If an + integer is given, only plots the fist so many parameters. + filename: + If set, saves the figure to this location + figsize: + Scale horizontal and vertical figure size. + col_wrap: + How many columns to plot before wrapping. + + Returns: + the matplotlib figure created + + """ + base = self._sanitise( + chains, + columns, + self.config.extents, + blind=self.config.blind, + log_scales=self.config.log_scales, + ) + + n = len(base.columns) + num_cols = min(n, col_wrap) + num_rows = int(np.ceil(1.0 * n / col_wrap)) + + if figsize is None: + figsize = 1.0 + if isinstance(figsize, float): + figsize_float = figsize + figsize = (num_cols * 2.5 * figsize, num_rows * 2.5 * figsize) + else: + figsize_float = 1.0 + + summary = self.config.summarise and len(base.chains) == 1 + hspace = (0.8 if summary else 0.5) / figsize_float + fig, axes = plt.subplots(nrows=num_rows, ncols=num_cols, figsize=figsize, squeeze=False) + fig.subplots_adjust(left=0.1, right=0.95, top=0.95, bottom=0.1, wspace=0.05, hspace=hspace) + + formatter = ScalarFormatter(useOffset=False) + formatter.set_powerlimits((-3, 4)) + + for i, ax in enumerate(axes.flatten()): + if i >= n: + ax.set_axis_off() + continue + p = base.columns[i] + + ax.set_yticks([]) + if p in base.log_scales: + ax.set_xscale("log") + if p in base.blind: + ax.set_xticks([]) + else: + if self.config.diagonal_tick_labels: + _ = [label.set_rotation(45) for label in ax.get_xticklabels()] + _ = [label.set_fontsize(self.config.tick_font_size) for label in ax.get_xticklabels()] + + if p in base.log_scales: + ax.xaxis.set_major_locator(LogLocator(numticks=self.config.max_ticks)) + else: + ax.xaxis.set_major_locator(MaxNLocator(self.config.max_ticks, prune="lower")) + ax.xaxis.set_major_formatter(formatter) + ax.set_xlim(base.extents.get(p) or self._get_parameter_extents(p, base.chains)) + + max_val = -np.inf + for chain in base.chains: + if not chain.plot_contour: + continue + if p in chain.plotting_columns: + param_summary = summary and p not in base.blind + m = self._plot_bars(ax, p, chain, summary=param_summary) + if max_val is None or m > max_val: + max_val = m + for truth in self.parent._truths: + self._add_truth(ax, truth, py=p) + ax.set_ylim(0, 1.1 * max_val) + ax.set_xlabel(p, fontsize=self.config.label_font_size) + + if filename is not None: + if not isinstance(filename, list): + filename = [filename] + for f in filename: + self._save_fig(fig, f, self.config.dpi) + fig.tight_layout() + return fig + + def plot_summary( + self, + chains: list[ChainName | Chain] | None = None, + columns: list[ColumnName] | None = None, + filename: list[str | Path] | str | Path | None = None, + figsize: float = 1.0, + errorbar: bool = False, + extra_parameter_spacing: float = 1.0, + vertical_spacing_ratio: float = 1.0, + ) -> Figure: # pragma: no cover + """Plots parameter summaries + + This plot is more for a sanity or consistency check than for use with final results. + Plotting this before plotting with :func:`plot` allows you to quickly see if the + chains give well behaved distributions, or if certain parameters are suspect + or require a greater burn in period. + + Args: + chains: + Used to specify which chain to show if more than one chain is loaded in. + Can be an integer, specifying the + chain index, or a str, specifying the chain name. + columns: + If set, only creates a plot for those specific parameters (if list). If an + integer is given, only plots the fist so many parameters. + filename: + If set, saves the figure to this location + figsize: + Scale horizontal and vertical figure size. + errorbar: + Whether to onle plot an error bar, instead of the marginalised distribution. + include_truth_chain: + If you specify another chain as the truth chain, determine if it should still + be plotted. + extra_parameter_spacing: + Increase horizontal space for parameter values + vertical_spacing_ratio: + Increase vertical space for each model + Returns: + the matplotlib figure created + + """ + wide_extents = not errorbar + base = self._sanitise( + chains, + columns, + self.config.extents, + blind=self.config.blind, + log_scales=self.config.log_scales, + wide_extents=wide_extents, + ) + + # We have a bit of fun to go from chain names to the width of the + # subplot used to display said names + max_param = self._get_size_of_texts(base.columns) + fid_dpi = 65 # Seriously I have no idea what value this should be + param_width = extra_parameter_spacing + max(0.5, max_param / fid_dpi) + max_model_name = self._get_size_of_texts([chain.name for chain in base.chains]) + model_width = 0.25 + (max_model_name / fid_dpi) + gridspec_kw = { + "width_ratios": [model_width] + [param_width] * len(base.columns), + "height_ratios": [1] * len(base.chains), + } + ncols = 1 + len(base.columns) + top_spacing = 0.3 + bottom_spacing = 0.2 + row_height = (0.5 if errorbar else 0.8) * vertical_spacing_ratio + width = param_width * len(base.columns) + model_width + height = top_spacing + bottom_spacing + row_height * len(base.chains) + top_ratio = 1 - (top_spacing / height) + bottom_ratio = bottom_spacing / height + + fig_size = (width * figsize, height * figsize) + fig, axes = plt.subplots( + nrows=len(base.chains), ncols=ncols, figsize=fig_size, squeeze=False, gridspec_kw=gridspec_kw + ) + fig.subplots_adjust(left=0.05, right=0.95, top=top_ratio, bottom=bottom_ratio, wspace=0.0, hspace=0.0) + label_font_size = self.config.label_font_size + legend_color_text = self.config.legend_color_text + + max_vals: dict[ColumnName, float] = {} + num_chains = len(base.chains) + for i, axes_row in enumerate(axes): + chain = base.chains[i] + colour = colors.format(chain.color) + + # First one put name of model + ax_first = axes_row[0] + ax_first.set_axis_off() + text_colour = "k" if not legend_color_text else colour + ax_first.text( + 0, + 0.5, + chain.name, + transform=ax_first.transAxes, + fontsize=label_font_size, + verticalalignment="center", + color=text_colour, + weight="medium", + ) + axes_for_summaries = axes_row[1:] + + for ax, p in zip(axes_for_summaries, base.columns): + # Set up the frames + if i > 0: + ax.spines["top"].set_visible(False) + if i < (num_chains - 1): + ax.spines["bottom"].set_visible(False) + if i < (num_chains - 1) or p in base.blind: + ax.set_xticks([]) + ax.set_yticks([]) + ax.set_xlim(base.extents[p]) + if p in base.log_scales: + ax.set_xscale("log") + + # Put title in + if i == 0: + ax.set_title(self.config.get_label(p), fontsize=label_font_size) + + # Add truth values + for truth in self.parent._truths: + truth_value = truth.location.get(p) + if truth_value is not None: + ax.axvline(truth_value, **truth._kwargs) + + # Skip if this chain doesnt have the parameter + if p not in chain.data_columns: + continue + + # Plot the good stuff + if errorbar: + fv = self.parent.analysis.get_parameter_summary(chain, p) + if fv is None or fv.all_none: + continue + if fv.lower is not None and fv.upper is not None: + diff = np.abs(np.diff(fv.array)) + ax.errorbar([fv.center], 0, xerr=[[diff[0]], [diff[1]]], fmt="o", color=colour) + else: + m = self._plot_bars(ax, p, chain) + if max_vals.get(p) is None or m > max_vals[p]: + max_vals[p] = m + + for i, axes_row in enumerate(axes): + for ax, p in zip(axes_row, base.columns): + if not errorbar: + ax.set_ylim(0, 1.1 * max_vals[p]) + + if self.config.watermark: + ax = None + self._add_watermark(fig, ax, fig_size, self.config.watermark, dpi=self.config.dpi, size_scale=0.8) + + if filename is not None: + if not isinstance(filename, list): + filename = [filename] + for f in filename: + self._save_fig(fig, f, self.config.dpi) + + return fig + + def _get_size_of_texts(self, texts: list[str]) -> float: # pragma: no cover + usetex = self.config.usetex + size = self.config.label_font_size + widths = [TextPath((0, 0), text, usetex=usetex, size=size).get_extents().width for text in texts] + return max(widths) + + def _sanitise_columns(self, columns: list[ColumnName] | None, chains: list[Chain]) -> list[ColumnName]: + if columns is None: + res = [] # Doing it without set to preserve order + for chain in chains: + for column in chain.plotting_columns: + if column not in res: + res.append(column) + return res + return columns + + def _sanitise_logscale(self, log_scales: list[ColumnName] | None) -> list[ColumnName]: + # We could at some point determine if something should be a log scale by analyising + # its distribution, but for now assume its all linear + if log_scales is None: + return [] + return log_scales + + def _sanitise_blinds(self, blind: bool | list[ColumnName] | None, columns: list[ColumnName]) -> list[ColumnName]: + if blind is None or blind is False: + return [] + elif blind is True: + return columns + return blind + + def _sanitise( + self, + chains: list[ChainName | Chain] | None, + columns: list[ColumnName] | None, + extents: dict[str, tuple[float, float]] | None, + blind: bool | list[ColumnName] | None = None, + log_scales: list[ColumnName] | None = None, + wide_extents: bool = True, + ) -> PlottingBase: + final_chains = self._sanitise_chains(chains) + final_columns = self._sanitise_columns(columns, final_chains) + extents = self._get_custom_extents(final_columns, final_chains, extents, wide_extents=wide_extents) + self.set_rc_params() + + return PlottingBase( + chains=final_chains, + columns=final_columns, + extents=extents, + log_scales=self._sanitise_logscale(log_scales), + blind=self._sanitise_blinds(blind, final_columns), + ) + + def set_rc_params(self) -> None: + if self.config.usetex: + plt.rc("text", usetex=True) + else: + plt.rc("text", usetex=False) + if self.config.serif: + plt.rc("font", family="serif") + else: + plt.rc("font", family="sans-serif") + + def restore_rc_params(self): + """Restores the matplotlib rc parameters modified by usetex and serif. + + Unfortunately this cannot be automated because you cannot invoke it whilst you have + an active figure object or matplotlib will destroy you. So do all your plotting, close + the plots, and then you can call this. + """ + plt.rc("text", usetex=self.usetex_old) + plt.rc("font", family=self.serif_old) + + def _get_custom_extents( + self, + columns: list[ColumnName], + chains: list[Chain], + initial_extents: dict[ColumnName, tuple[float, float]] | None, + wide_extents: bool = True, + ) -> dict[ColumnName, tuple[float, float]]: # pragma: no cover + if initial_extents is None: + initial_extents = {} + extents = {} | initial_extents + for p in columns: + if p not in initial_extents: + extents[p] = self._get_parameter_extents(p, chains, wide_extents=wide_extents) + return extents + + def _get_triangle_figure( + self, base: PlottingBase, figsize: tuple[float, float] + ) -> tuple[Figure, np.ndarray, list[ColumnName], list[ColumnName]]: + n = len(base.columns) + if not self.config.plot_hists: + n -= 1 + + spacing = self.config.spacing + if spacing is None: + spacing = 1.0 if n < 6 else 0.0 + + gridspec_kw = {} + if n == 2 and self.config.plot_hists and self.config.flip: + gridspec_kw = {"width_ratios": [3, 1], "height_ratios": [1, 3]} + + fig, axes = plt.subplots(n, n, figsize=figsize, squeeze=False, gridspec_kw=gridspec_kw) + min_left_for_axes = min(max(0.85 / figsize[0], 0.1), 0.3) + min_bottom_for_axes = min(max(0.85 / figsize[1], 0.1), 0.3) + fig.subplots_adjust( + left=min_left_for_axes, + right=0.95, + top=0.9, + bottom=min_bottom_for_axes, + wspace=0.05 * spacing, + hspace=0.05 * spacing, + ) + + if self.config.plot_hists: + params_x = base.columns + params_y = base.columns + else: + params_x = base.columns[1:] + params_y = base.columns[:-1] + for i, p1 in enumerate(params_x): + for j, p2 in enumerate(params_y): + ax = axes[i, j] + formatter_x = ScalarFormatter(useOffset=True) + formatter_x.set_powerlimits((-3, 4)) + formatter_y = ScalarFormatter(useOffset=True) + formatter_y.set_powerlimits((-3, 4)) + + display_x_ticks = False + display_y_ticks = False + if i < j: + ax.set_frame_on(False) + ax.set_xticks([]) + ax.set_yticks([]) + else: + logx = False + logy = False + if p1 == p2: + if p1 in base.log_scales: + if self.config.flip and j == n - 1: + ax.set_yscale("log") + logy = True + else: + ax.set_xscale("log") + logx = True + else: + if p1 in base.log_scales: + ax.set_yscale("log") + logy = True + if p2 in base.log_scales: + ax.set_xscale("log") + logx = True + if i != n - 1 or (self.config.flip and j == n - 1): + ax.set_xticks([]) + else: + if p2 in base.blind: + ax.set_xticks([]) + else: + display_x_ticks = True + if isinstance(p2, str): + ax.set_xlabel(self.config.get_label(p2), fontsize=self.config.label_font_size) + if j != 0 or (self.config.plot_hists and i == 0): + ax.set_yticks([]) + else: + if p1 in base.blind: + ax.set_yticks([]) + else: + display_y_ticks = True + if isinstance(p1, str): + ax.set_ylabel(self.config.get_label(p1), fontsize=self.config.label_font_size) + if display_x_ticks: + if self.config.diagonal_tick_labels: + _ = [label.set_rotation(45) for label in ax.get_xticklabels()] + _ = [label.set_fontsize(self.config.tick_font_size) for label in ax.get_xticklabels()] + if not logx: + ax.xaxis.set_major_locator(MaxNLocator(self.config.max_ticks, prune="lower")) + ax.xaxis.set_major_formatter(formatter_x) + else: + ax.xaxis.set_major_locator(LogLocator(numticks=self.config.max_ticks)) + else: + ax.set_xticks([]) + if display_y_ticks: + if self.config.diagonal_tick_labels: + _ = [label.set_rotation(45) for label in ax.get_yticklabels()] + _ = [label.set_fontsize(self.config.tick_font_size) for label in ax.get_yticklabels()] + if not logy: + ax.yaxis.set_major_locator(MaxNLocator(self.config.max_ticks, prune="lower")) + ax.yaxis.set_major_formatter(formatter_y) + else: + ax.yaxis.set_major_locator(LogLocator(numticks=self.config.max_ticks)) + else: + ax.set_yticks([]) + if (i != j or not self.config.plot_hists) or (self.config.flip and i == 1): + ax.set_ylim(base.extents[p1]) + ax.set_xlim(base.extents[p2]) + + return fig, axes, params_x, params_y + + def _get_parameter_extents( + self, column: ColumnName, chains: list[Chain], wide_extents: bool = True + ) -> tuple[float, float]: + min_val, max_val = np.inf, -np.inf + for chain in chains: + if column not in chain.samples: + continue # pragma: no cover + + data = chain.get_data(column) + min_prop, max_prop = np.inf, -np.inf + if chain.plot_contour or chain.plot_cloud: + if chain.grid: + min_prop = data.min() + max_prop = data.max() + else: + min_prop, max_prop = get_extents(data, chain.weights, plot=True, wide_extents=wide_extents) + + else: + point = chain.get_max_posterior_point() + if point is not None and column in point.coordinate: + min_prop = point.coordinate[column] + max_prop = min_prop + + if min_prop < min_val: + min_val = min_prop + if max_prop > max_val: + max_val = max_prop + + return min_val, max_val + + def _get_levels(self, sigmas: list[float]) -> np.ndarray: + sigma2d = self.config.sigma2d + if sigma2d: + levels: np.ndarray = 1.0 - np.exp(-0.5 * np.array(sigmas) ** 2) + else: + levels: np.ndarray = 2 * norm.cdf(sigmas) - 1.0 + return levels + + def _plot_point(self, ax: Axes, chain: Chain, px: str, py: str) -> PathCollection | None: # pragma: no cover + point = chain.get_max_posterior_point() + if point is None or px not in point.coordinate or py not in point.coordinate: + return None + # Determine if we need to darken the point + c = colors.format(chain.color) + if chain.plot_contour: + c = colors.scale_colour(colors.format(chain.color), 0.5) + h = ax.scatter( + [point.coordinate[px]], + [point.coordinate[py]], + marker=chain.marker_style, + c=c, + s=chain.marker_size, + alpha=chain.marker_alpha, + zorder=chain.zorder + 1, + ) + return h + + def _sanitise_chains( + self, chains: list[Chain | ChainName] | dict[ChainName, Chain] | None, include_skip: bool = False + ) -> list[Chain]: + overriden_chains = self.parent._get_final_chains() + final_chains = [] + if isinstance(chains, list): + final_chains = [overriden_chains[c if isinstance(c, ChainName) else c.name] for c in chains] + elif isinstance(chains, dict): + final_chains = [overriden_chains[c.name] for c in chains.values()] + else: + final_chains = list(overriden_chains.values()) + return [c for c in final_chains if include_skip or not c.skip] + + def plot_contour( + self, + ax: Axes, + column_x: str, + column_y: str, + chains: list[Chain | ChainName] | dict[ChainName, Chain] | None = None, + ) -> None: + """A lightweight method to plot contours in an external axis given two specified parameters + + Args: + ax (Axes): The axis to plot on + column_x (str): The parameter to plot on the x axis + column_y (str): The parameter to plot on the y axis + chains (list[Chain | ChainName] | dict[ChainName, str], optional): The chains to plot. Defaults to None. + """ + + final_chains = self._sanitise_chains(chains) + for chain in final_chains: + self._plot_contour(ax, chain, column_y, column_x) + + def _plot_scatter(self, ax: Axes, chain: Chain, color: str, x: pd.Series, y: pd.Series) -> PathCollection | None: + skip = max(1, int(x.size / chain.num_cloud)) + if chain.color_data is not None: + kwargs = {"c": chain.color_data[::skip], "cmap": chain.cmap} + else: + kwargs = {"c": color, "alpha": 0.3} + + h = ax.scatter( + x[::skip], + y[::skip], + s=10, + marker=".", + edgecolors="none", + zorder=chain.zorder - 5, + **kwargs, # type: ignore + ) + if chain.color_data is not None: + return h + else: + return None + + def _plot_contour(self, ax: Axes, chain: Chain, px: str, py: str) -> PathCollection | None: # pragma: no cover + levels = self._get_levels(chain.sigmas) + x = chain.get_data(py) + y = chain.get_data(px) + + contour_colours = self._scale_colours(colors.format(chain.color), len(levels), chain.shade_gradient) + sub = max(0.1, 1 - 0.2 * chain.shade_gradient) + paths = None + + if chain.plot_cloud: + paths = self._plot_scatter(ax, chain, contour_colours[1], x, y) + + # TODO: Figure out whats going on here + if chain.shade: + sub *= 0.9 + colours2 = [colors.scale_colour(contour_colours[0], sub)] + [ + colors.scale_colour(c, sub) for c in contour_colours[:-1] + ] + + hist, x_centers, y_centers = get_smoothed_histogram2d(chain, py, px) + hist[hist == 0] = 1e-16 + vals = self._convert_to_stdev(hist.T) + + if chain.shade and chain.shade_alpha > 0: + ax.contourf( + x_centers, + y_centers, + vals, + levels=levels, + colors=contour_colours, + alpha=chain.shade_alpha, + zorder=chain.zorder - 2, + ) + con = ax.contour( + x_centers, + y_centers, + vals, + levels=levels, + colors=colours2, + linestyles=chain.linestyle, + linewidths=chain.linewidth, + zorder=chain.zorder, + ) + + if chain.show_contour_labels: + lvls = [lvl for lvl in con.levels if lvl != 0.0] + fmt = {lvl: f" {lvl:0.0%} " if lvl < 0.991 else f" {lvl:0.1%} " for lvl in lvls} + texts = ax.clabel(con, lvls, inline=True, fmt=fmt, fontsize=self.config.contour_label_font_size) + for text in texts: + text.set_fontweight("semibold") + + return paths + + def _add_truth( + self, ax: Axes, truth: Truth, px: str | None = None, py: str | None = None + ) -> None: # pragma: no cover + if px is not None: + val_x = truth.location.get(px) + if val_x is not None: + ax.axhline(val_x, **truth._kwargs) + if py is not None: + val_y = truth.location.get(py) + if val_y is not None: + ax.axvline(val_y, **truth._kwargs) + + def _plot_bars( + self, ax: Axes, column: str, chain: Chain, flip: bool = False, summary: bool = False + ) -> float: # pragma: no cover + # Get values from config + data = chain.get_data(column) + if chain.smooth or chain.kde: + xs, ys, _ = self.parent.analysis._get_smoothed_histogram(chain, column, pad=True) + if flip: + ax.plot(ys, xs, color=chain.color, ls=chain.linestyle, lw=chain.linewidth, zorder=chain.zorder) + else: + ax.plot(xs, ys, color=chain.color, ls=chain.linestyle, lw=chain.linewidth, zorder=chain.zorder) + else: + if chain.grid: + bins = get_grid_bins(data) + else: + bins, _ = get_smoothed_bins(chain.smooth, get_bins(chain), data, chain.weights) + hist, edges = np.histogram(data, bins=bins, density=True, weights=chain.weights) + if chain.power is not None: + hist = hist**chain.power + edge_center = 0.5 * (edges[:-1] + edges[1:]) + xs, ys = edge_center, hist + ax.hist( + xs, + weights=ys, + bins=bins, # type: ignore + histtype="step", + color=chain.color, # type: ignore + orientation="horizontal" if flip else "vertical", + ls=chain.linestyle, + lw=chain.linewidth, + zorder=chain.zorder, + ) + interp_type = "linear" if chain.smooth else "nearest" + interpolator = interp1d(xs, ys, kind=interp_type) + + if chain.bar_shade: + fit_values = self.parent.analysis.get_parameter_summary(chain, column) + if fit_values is not None: + lower = fit_values.lower + upper = fit_values.upper + if lower is not None and upper is not None: + if lower < xs.min(): + lower = xs.min() + if upper > xs.max(): + upper = xs.max() + x = np.linspace(lower, upper, 1000) # type: ignore + if flip: + ax.fill_betweenx( + x, + np.zeros(x.shape), + interpolator(x), + color=chain.color, + alpha=0.2, + zorder=chain.zorder, + ) + else: + ax.fill_between( + x, + np.zeros(x.shape), + interpolator(x), + color=chain.color, + alpha=0.2, + zorder=chain.zorder, + ) + if summary: + t = self.parent.analysis.get_parameter_text(fit_values) + if isinstance(column, str): + ax.set_title( + r"${} = {}$".format(column.strip("$"), t), fontsize=self.config.summary_font_size + ) + else: + ax.set_title(r"$%s$" % t, fontsize=self.config.summary_font_size) + return float(ys.max()) + + def _plot_walk( + self, + ax: Axes, + column: ColumnName, + data: pd.Series, + extents: tuple[float, float] | None = None, + convolve: int | None = None, + color: str | None = None, + log_scale: bool = False, + ) -> None: # pragma: no cover + if extents is not None: + ax.set_ylim(extents) + assert convolve is None or isinstance(convolve, int), "Convolve must be an integer pixel window width" + x = np.arange(data.size) + ax.set_xlim(0, x[-1]) + ax.set_ylabel(self.config.get_label(column)) + if color is None: + color = "#0345A1" + ax.scatter(x, data, c=color, s=2, marker=".", edgecolors="none", alpha=0.5) + max_ticks = self.config.max_ticks + if log_scale: + ax.set_yscale("log") + ax.yaxis.set_major_locator(LogLocator(numticks=max_ticks)) + else: + ax.yaxis.set_major_locator(MaxNLocator(max_ticks, prune="lower")) + + if convolve is not None: + trim = int(0.5 * convolve) + color2 = colors.scale_colour(color, 0.5) + filt = np.ones(convolve) / convolve + filtered = np.convolve(data, filt, mode="same") + ax.plot(x[trim:-trim], filtered[trim:-trim], color=color2, alpha=1) + + def _plot_walk_truth(self, ax: Axes, truth: Truth, col: str) -> None: + ax.axhline(truth.location[col], **truth._kwargs) + + def _convert_to_stdev(self, sigma: np.ndarray) -> np.ndarray: # pragma: no cover + # From astroML + shape = sigma.shape + sigma = sigma.ravel() + i_sort = np.argsort(sigma)[::-1] + i_unsort = np.argsort(i_sort) + + sigma_cumsum = 1.0 * sigma[i_sort].cumsum() + sigma_cumsum /= sigma_cumsum[-1] + + return sigma_cumsum[i_unsort].reshape(shape) + + def _scale_colours(self, colour: ColorInput, num: int, shade_gradient: float) -> list[str]: # pragma: no cover + # http://thadeusb.com/weblog/2010/10/10/python_scale_hex_color + minv, maxv = 1 - 0.1 * shade_gradient, 1 + 0.5 * shade_gradient + scales = np.logspace(np.log(minv), np.log(maxv), num) + colours = [colors.scale_colour(colour, scale) for scale in scales] + return colours + + +if __name__ == "__main__": + from .chainconsumer import ChainConsumer diff --git a/src/chainconsumer/py.typed b/src/chainconsumer/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/src/chainconsumer/statistics.py b/src/chainconsumer/statistics.py new file mode 100644 index 00000000..e3c83a71 --- /dev/null +++ b/src/chainconsumer/statistics.py @@ -0,0 +1,20 @@ +from enum import Enum + + +class SummaryStatistic(Enum): + MAX = "max" + """The max value summary statistic is the default. The central point is set + to your maxmimum likelihood, and the upper and lower bounds are determined by + finding an iso-likelihood surface which encapsulates the required volume.""" + + MAX_CENTRAL = "max_central" + """As per the MAX method, this has the centre point at the maximum likelihood. + However the lower and upper values come from the CDF, like the cumulative method.""" + + CUMULATIVE = "cumulative" + """The lower, central, and upper bound are determined by finding where on the marginalised + sample CDF the points lie. This means the central point is the median.""" + + MEAN = "mean" + """As per the cumulative method, except the central value is placed in the midpoint between + the upper and lower boundary. Not recommended, but was requested.""" diff --git a/src/chainconsumer/truth.py b/src/chainconsumer/truth.py new file mode 100644 index 00000000..4d95151b --- /dev/null +++ b/src/chainconsumer/truth.py @@ -0,0 +1,39 @@ +from typing import Any + +import pandas as pd +from pydantic import Field, ValidationError, field_validator + +from .base import BetterBase +from .color_finder import ColorInput + + +class Truth(BetterBase): + location: dict[str, float] = Field( + default=..., + description="The truth value, either as dictionary or pandas series which will be converted to a dict)", + ) + name: str | None = Field(default=None, description="The name of the truth line") + color: ColorInput = Field(default="black", description="The color of the truth line") + line_width: float = Field(default=1.0, description="The width of the truth line") + line_style: str = Field(default="--", description="The style of the truth line") + alpha: float = Field(default=1.0, description="The alpha of the truth line") + zorder: int = Field(default=100, description="The zorder of the truth line") + + @field_validator("location") + @classmethod + def _ensure_dict(cls, v): + if isinstance(v, dict): + return v + elif isinstance(v, pd.Series): + return v.to_dict() + raise ValidationError("Truth must be a dict or a pandas Series") + + @property + def _kwargs(self) -> dict[str, Any]: + return { + "ls": self.line_style, + "c": self.color, + "lw": self.line_width, + "alpha": self.alpha, + "zorder": self.zorder, + } diff --git a/tests/test_analysis.py b/tests/test_analysis.py index 33e7bae7..86051e4b 100644 --- a/tests/test_analysis.py +++ b/tests/test_analysis.py @@ -1,29 +1,29 @@ -import os -import tempfile - import numpy as np -from scipy.interpolate import interp1d -from scipy.stats import skewnorm, norm -import pytest +import pandas as pd +from scipy.stats import skewnorm -from chainconsumer import ChainConsumer +from chainconsumer import Bound, Chain, ChainConfig, ChainConsumer -class TestChain(object): - np.random.seed(1) - n = 2000000 - data = np.random.normal(loc=5.0, scale=1.5, size=n) - data2 = np.random.normal(loc=3, scale=1.0, size=n) +class TestChain: + rng = np.random.default_rng(1) + n = 3000000 + data = rng.normal(loc=5.0, scale=1.5, size=n) + data2 = rng.normal(loc=3, scale=1.0, size=n) data_combined = np.vstack((data, data2)).T data_skew = skewnorm.rvs(5, loc=1, scale=1.5, size=n) + chain = Chain(samples=pd.DataFrame(data, columns=["x"]), name="a") + chain2 = Chain(samples=pd.DataFrame(data2, columns=["x"]), name="b") + chain_combined = Chain(samples=pd.DataFrame(data_combined, columns=["a", "b"]), name="combined") + chain_skew = Chain(samples=pd.DataFrame(data_skew, columns=["x"]), name="skew") # type: ignore + def test_summary(self): tolerance = 4e-2 consumer = ChainConsumer() - consumer.add_chain(self.data[::10]) - consumer.configure(kde=True) + consumer.add_chain(Chain(samples=pd.DataFrame(self.data[::10], columns=["x"]), name="a", kde=True)) summary = consumer.analysis.get_summary() - actual = np.array(list(summary.values())[0]) + actual = summary["a"]["x"].array expected = np.array([3.5, 5.0, 6.5]) diff = np.abs(expected - actual) assert np.all(diff < tolerance) @@ -31,10 +31,10 @@ def test_summary(self): def test_summary_no_smooth(self): tolerance = 5e-2 consumer = ChainConsumer() - consumer.add_chain(self.data) - consumer.configure(smooth=0, bins=2.4) + consumer.add_chain(self.chain) + consumer.set_override(ChainConfig(smooth=0, bins=100)) summary = consumer.analysis.get_summary() - actual = np.array(list(summary.values())[0]) + actual = summary["a"]["x"].array expected = np.array([3.5, 5.0, 6.5]) diff = np.abs(expected - actual) assert np.all(diff < tolerance) @@ -42,65 +42,39 @@ def test_summary_no_smooth(self): def test_summary2(self): tolerance = 5e-2 consumer = ChainConsumer() - consumer.add_chain(self.data_combined, parameters=["a", "b"], name="chain1") - consumer.add_chain(self.data_combined, name="chain2") + consumer.add_chain(self.chain_combined) + c2 = self.chain_combined.model_copy() + c2.name = "chain2" + consumer.add_chain(c2) summary = consumer.analysis.get_summary() - k1 = list(summary[0].keys()) - k2 = list(summary[1].keys()) - assert len(k1) == 2 - assert "a" in k1 - assert "b" in k1 - assert len(k2) == 2 - assert "a" in k2 - assert "b" in k2 + + assert len(summary) == 2 + assert "combined" in summary + assert "chain2" in summary + assert "a" in summary["combined"] + assert "b" in summary["combined"] + assert "a" in summary["chain2"] + assert "b" in summary["chain2"] + expected1 = np.array([3.5, 5.0, 6.5]) expected2 = np.array([2.0, 3.0, 4.0]) - diff1 = np.abs(expected1 - np.array(list(summary[0]["a"]))) - diff2 = np.abs(expected2 - np.array(list(summary[0]["b"]))) + diff1 = np.abs(expected1 - summary["combined"]["a"].array) + diff2 = np.abs(expected2 - summary["chain2"]["b"].array) assert np.all(diff1 < tolerance) assert np.all(diff2 < tolerance) - def test_summary_some_params(self): - consumer = ChainConsumer() - consumer.add_chain(self.data_combined, parameters=["a", "b"], name="chain1") - summary = consumer.analysis.get_summary(parameters=["a"], squeeze=False) - k1 = list(summary[0].keys()) - assert len(k1) == 1 - assert "a" in k1 - assert "b" not in k1 - - def test_summary1(self): - tolerance = 5e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data) - consumer.configure(bins=0.8) - summary = consumer.analysis.get_summary() - actual = np.array(list(summary.values())[0]) - expected = np.array([3.5, 5.0, 6.5]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) - - def test_summary_specific(self): - tolerance = 5e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data, name="A") - consumer.configure(bins=0.8) - summary = consumer.analysis.get_summary(chains="A") - actual = np.array(list(summary.values())[0]) - expected = np.array([3.5, 5.0, 6.5]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) - def test_summary_disjoint(self): tolerance = 5e-2 consumer = ChainConsumer() - consumer.add_chain(self.data, parameters="A") - consumer.add_chain(self.data, parameters="B") - consumer.configure(bins=0.8) - summary = consumer.analysis.get_summary(parameters="A") + consumer.add_chain(self.chain) + c2 = self.chain.model_copy() + c2.name = "chain2" + c2.samples = c2.samples.rename(columns={"x": "y"}) + consumer.add_chain(c2) + summary = consumer.analysis.get_summary(columns=["x"]) assert len(summary) == 2 # Two chains - assert summary[1] == {} # Second chain doesnt have param A - actual = summary[0]["A"] + assert not summary["chain2"] # but this one has no cols + actual = summary["a"]["x"].array expected = np.array([3.5, 5.0, 6.5]) diff = np.abs(expected - actual) assert np.all(diff < tolerance) @@ -108,74 +82,73 @@ def test_summary_disjoint(self): def test_summary_power(self): tolerance = 5e-2 consumer = ChainConsumer() - data = np.random.normal(loc=0, scale=np.sqrt(2), size=1000000) - consumer.add_chain(data, power=2.0) + data = self.rng.normal(loc=0, scale=np.sqrt(2), size=1000000) + consumer.add_chain(Chain(samples=pd.DataFrame(data, columns=["x"]), name="A", power=2.0)) summary = consumer.analysis.get_summary() - actual = np.array(list(summary.values())[0]) + actual = summary["A"]["x"].array expected = np.array([-1.0, 0.0, 1.0]) diff = np.abs(expected - actual) assert np.all(diff < tolerance) def test_output_text(self): consumer = ChainConsumer() - consumer.add_chain(self.data, parameters=["a"]) - consumer.configure(bins=0.8) + consumer.add_chain(self.chain) vals = consumer.analysis.get_summary()["a"] - text = consumer.analysis.get_parameter_text(*vals) + text = consumer.analysis.get_parameter_text(vals["x"]) assert text == r"5.0\pm 1.5" def test_output_text_asymmetric(self): - p1 = [1.0, 2.0, 3.5] + bound = Bound.from_array([1.0, 2.0, 3.5]) consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(bound) assert text == r"2.0^{+1.5}_{-1.0}" def test_output_format1(self): p1 = [1.0e-1, 2.0e-1, 3.5e-1] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"0.20^{+0.15}_{-0.10}" def test_output_format2(self): p1 = [1.0e-2, 2.0e-2, 3.5e-2] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"0.020^{+0.015}_{-0.010}" def test_output_format3(self): p1 = [1.0e-3, 2.0e-3, 3.5e-3] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"\left( 2.0^{+1.5}_{-1.0} \right) \times 10^{-3}" def test_output_format4(self): p1 = [1.0e3, 2.0e3, 3.5e3] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"\left( 2.0^{+1.5}_{-1.0} \right) \times 10^{3}" def test_output_format5(self): p1 = [1.1e6, 2.2e6, 3.3e6] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"\left( 2.2\pm 1.1 \right) \times 10^{6}" def test_output_format6(self): p1 = [1.0e-2, 2.0e-2, 3.5e-2] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1, wrap=True) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1), wrap=True) assert text == r"$0.020^{+0.015}_{-0.010}$" def test_output_format7(self): p1 = [None, 2.0e-2, 3.5e-2] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == "" def test_output_format8(self): p1 = [-1, -0.0, 1] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"0.0\pm 1.0" def test_output_format9(self): @@ -183,7 +156,7 @@ def test_output_format9(self): d = 123.321 p1 = [x - d, x, x + d] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"123460\pm 120" def test_output_format10(self): @@ -191,7 +164,7 @@ def test_output_format10(self): d = 1234.321 p1 = [x - d, x, x + d] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"\left( 123.5\pm 1.2 \right) \times 10^{3}" def test_output_format11(self): @@ -199,7 +172,7 @@ def test_output_format11(self): d = 111.111 p1 = [x - d, x, x + d] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"220\pm 110" def test_output_format12(self): @@ -207,7 +180,7 @@ def test_output_format12(self): d = 11.111 p1 = [x - d, x, x + d] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"222\pm 11" def test_output_format13(self): @@ -215,7 +188,7 @@ def test_output_format13(self): d = 11.111 p1 = [x - d, x, x + d] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"2222\pm 11" def test_output_format14(self): @@ -223,7 +196,7 @@ def test_output_format14(self): d = 1.111 p1 = [x - d, x, x + d] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"222.2\pm 1.1" def test_output_format15(self): @@ -231,7 +204,7 @@ def test_output_format15(self): d = 0.111 p1 = [x - d, x, x + d] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"222.22\pm 0.11" def test_output_format16(self): @@ -239,746 +212,611 @@ def test_output_format16(self): d = 0.0111 p1 = [x - d, x, x + d] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"222.222\pm 0.011" def test_output_format17(self): p1 = [1.0, 1.0, 2.0] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"1.0^{+1.0}_{-0.0}" def test_output_format18(self): p1 = [10000.0, 10000.0, 10000.0] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"\left( 1.0\pm 0.0 \right) \times 10^{4}" def test_output_format19(self): p1 = [1.0, 2.0, 2.0] consumer = ChainConsumer() - text = consumer.analysis.get_parameter_text(*p1) + text = consumer.analysis.get_parameter_text(Bound.from_array(p1)) assert text == r"2.0^{+0.0}_{-1.0}" - def test_file_loading1(self): - data = self.data[:1000] - directory = tempfile._get_default_tempdir() - filename = next(tempfile._get_candidate_names()) - filename = directory + os.sep + filename + ".txt" - np.savetxt(filename, data) - consumer = ChainConsumer() - consumer.add_chain(filename) - summary = consumer.analysis.get_summary() - actual = np.array(list(summary.values())[0]) - assert np.abs(actual[1] - 5.0) < 0.5 - - def test_file_loading2(self): - data = self.data[:1000] - directory = tempfile._get_default_tempdir() - filename = next(tempfile._get_candidate_names()) - filename = directory + os.sep + filename + ".npy" - np.save(filename, data) - consumer = ChainConsumer() - consumer.add_chain(filename) - summary = consumer.analysis.get_summary() - actual = np.array(list(summary.values())[0]) - assert np.abs(actual[1] - 5.0) < 0.5 - - def test_using_list(self): - data = self.data.tolist() - c = ChainConsumer() - c.add_chain(data) - summary = c.analysis.get_summary() - actual = np.array(list(summary.values())[0]) - assert np.abs(actual[1] - 5.0) < 0.1 - - def test_using_dict(self): - data = {"x": self.data, "y": self.data2} - c = ChainConsumer() - c.add_chain(data) - summary = c.analysis.get_summary() - deviations = np.abs([summary["x"][1] - 5, summary["y"][1] - 3]) - assert np.all(deviations < 0.1) - - def test_summary_when_no_parameter_names(self): - c = ChainConsumer() - c.add_chain(self.data) - summary = c.analysis.get_summary() - assert list(summary.keys()) == ['0'] - - def test_squeeze_squeezes(self): - sum = ChainConsumer().add_chain(self.data).analysis.get_summary() - assert isinstance(sum, dict) - - def test_squeeze_doesnt(self): - sum = ChainConsumer().add_chain(self.data).analysis.get_summary(squeeze=False) - assert isinstance(sum, list) - assert len(sum) == 1 - - def test_squeeze_doesnt_squeeze_multi(self): - c = ChainConsumer() - c.add_chain(self.data).add_chain(self.data) - sum = c.analysis.get_summary() - assert isinstance(sum, list) - assert len(sum) == 2 - - def test_dictionary_and_parameters_fail(self): - with pytest.raises(AssertionError): - ChainConsumer().add_chain({"x": self.data}, parameters=["$x$"]) - - def test_convergence_failure(self): - data = np.concatenate((np.random.normal(loc=0.0, size=10000), - np.random.normal(loc=4.0, size=10000))) - consumer = ChainConsumer() - consumer.add_chain(data) - summary = consumer.analysis.get_summary() - actual = np.array(list(summary.values())[0]) - assert actual[0] is None and actual[2] is None - - def test_divide_chains_default(self): - np.random.seed(0) - data = np.concatenate((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))) - consumer = ChainConsumer() - num_walkers = 2 - consumer.add_chain(data, walkers=num_walkers) - - c = consumer.divide_chain() - c.configure(bins=0.7) - means = [0, 1.0] - for i in range(num_walkers): - stats = list(c.analysis.get_summary()[i].values())[0] - assert np.abs(stats[1] - means[i]) < 1e-1 - assert np.abs(c.chains[i].chain[:, 0].mean() - means[i]) < 1e-2 - - def test_divide_chains_index(self): - np.random.seed(0) - data = np.concatenate((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))) - consumer = ChainConsumer() - num_walkers = 2 - consumer.add_chain(data, walkers=num_walkers) - - c = consumer.divide_chain(chain=0) - c.configure(bins=0.7) - means = [0, 1.0] - for i in range(num_walkers): - stats = list(c.analysis.get_summary()[i].values())[0] - assert np.abs(stats[1] - means[i]) < 1e-1 - assert np.abs(c.chains[i].chain[:, 0].mean() - means[i]) < 1e-2 - def test_divide_chains_name(self): - np.random.seed(0) - data = np.concatenate((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))) + data = np.concatenate((self.rng.normal(loc=0.0, size=100000), self.rng.normal(loc=1.0, size=100000))) consumer = ChainConsumer() num_walkers = 2 - consumer.add_chain(data, walkers=num_walkers, name="test") - c = consumer.divide_chain(chain="test") - c.configure(bins=0.7) + chain = Chain(samples=pd.DataFrame(data, columns=["x"]), walkers=num_walkers, name="test") + for c in chain.divide(): + consumer.add_chain(c) means = [0, 1.0] - for i in range(num_walkers): - stats = list(c.analysis.get_summary()[i].values())[0] - assert np.abs(stats[1] - means[i]) < 1e-1 - assert np.abs(c.chains[i].chain[:, 0].mean() - means[i]) < 1e-2 - - def test_divide_chains_fail(self): - np.random.seed(0) - data = np.concatenate((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))) - consumer = ChainConsumer() - consumer.add_chain(data, walkers=2) - with pytest.raises(ValueError): - consumer.divide_chain(chain=2.0) - - def test_divide_chains_name_fail(self): - np.random.seed(0) - data = np.concatenate((np.random.normal(loc=0.0, size=200000), - np.random.normal(loc=1.0, size=200000))) - consumer = ChainConsumer() - consumer.add_chain(data, walkers=2) - with pytest.raises(AssertionError): - c = consumer.divide_chain(chain="notexist") + stats = consumer.analysis.get_summary() - def test_stats_max_normal(self): - tolerance = 5e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data) - consumer.configure(statistics="max") - summary = consumer.analysis.get_summary() - actual = np.array(list(summary.values())[0]) - expected = np.array([3.5, 5.0, 6.5]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) - - def test_stats_max_cliff(self): - tolerance = 5e-2 - n = 100000 - data = np.linspace(0, 10, n) - weights = norm.pdf(data, 1, 2) - consumer = ChainConsumer() - consumer.add_chain(data, weights=weights) - consumer.configure(statistics="max", bins=4.0, smooth=1) - summary = consumer.analysis.get_summary() - actual = np.array(list(summary.values())[0]) - expected = np.array([0.0, 1.0, 2.73]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) - - def test_stats_mean_normal(self): - tolerance = 5e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data) - consumer.configure(statistics="mean") - summary = consumer.analysis.get_summary() - actual = np.array(list(summary.values())[0]) - expected = np.array([3.5, 5.0, 6.5]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) - - def test_stats_cum_normal(self): - tolerance = 5e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data) - consumer.configure(statistics="cumulative") - summary = consumer.analysis.get_summary() - actual = np.array(list(summary.values())[0]) - expected = np.array([3.5, 5.0, 6.5]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) - - def test_reject_bad_satst(self): - consumer = ChainConsumer() - consumer.add_chain(self.data) - with pytest.raises(AssertionError): - consumer.configure(statistics="monkey") - - def test_stats_max_skew(self): - tolerance = 3e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data_skew) - consumer.configure(statistics="max") - summary = consumer.analysis.get_summary() - actual = np.array(list(summary.values())[0]) - expected = np.array([1.01, 1.55, 2.72]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) - - def test_stats_mean_skew(self): - tolerance = 3e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data_skew) - consumer.configure(statistics="mean") - summary = consumer.analysis.get_summary() - actual = np.array(list(summary.values())[0]) - expected = np.array([1.27, 2.19, 3.11]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) - - def test_stats_cum_skew(self): - tolerance = 3e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data_skew) - consumer.configure(statistics="cumulative") - summary = consumer.analysis.get_summary() - actual = np.array(list(summary.values())[0]) - expected = np.array([1.27, 2.01, 3.11]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) - - def test_stats_list_skew(self): - tolerance = 3e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data_skew) - consumer.add_chain(self.data_skew) - consumer.configure(statistics=["cumulative", "mean"]) - summary = consumer.analysis.get_summary() - actual0 = np.array(list(summary[0].values())[0]) - actual1 = np.array(list(summary[1].values())[0]) - expected0 = np.array([1.27, 2.01, 3.11]) - expected1 = np.array([1.27, 2.19, 3.11]) - diff0 = np.abs(expected0 - actual0) - diff1 = np.abs(expected1 - actual1) - assert np.all(diff0 < tolerance) - assert np.all(diff1 < tolerance) - - def test_weights(self): - tolerance = 3e-2 - samples = np.linspace(-4, 4, 200000) - weights = norm.pdf(samples) - c = ChainConsumer() - c.add_chain(samples, weights=weights) - expected = np.array([-1.0, 0.0, 1.0]) - summary = c.analysis.get_summary() - actual = np.array(list(summary.values())[0]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) - - def test_grid_data(self): - x, y = np.linspace(-3, 3, 200), np.linspace(-5, 5, 200) - xx, yy = np.meshgrid(x, y, indexing='ij') - xs, ys = xx.flatten(), yy.flatten() - chain = np.vstack((xs, ys)).T - pdf = (1 / (2 * np.pi)) * np.exp(-0.5 * (xs * xs + ys * ys / 4)) - c = ChainConsumer() - c.add_chain(chain, parameters=['x', 'y'], weights=pdf, grid=True) - summary = c.analysis.get_summary() - x_sum = summary['x'] - y_sum = summary['y'] - expected_x = np.array([-1.0, 0.0, 1.0]) - expected_y = np.array([-2.0, 0.0, 2.0]) - threshold = 0.1 - assert np.all(np.abs(expected_x - x_sum) < threshold) - assert np.all(np.abs(expected_y - y_sum) < threshold) - - def test_grid_list_input(self): - x, y = np.linspace(-3, 3, 200), np.linspace(-5, 5, 200) - xx, yy = np.meshgrid(x, y, indexing='ij') - pdf = (1 / (2 * np.pi)) * np.exp(-0.5 * (xx * xx + yy * yy / 4)) - c = ChainConsumer() - c.add_chain([x, y], parameters=['x', 'y'], weights=pdf, grid=True) - summary = c.analysis.get_summary() - x_sum = summary['x'] - y_sum = summary['y'] - expected_x = np.array([-1.0, 0.0, 1.0]) - expected_y = np.array([-2.0, 0.0, 2.0]) - threshold = 0.05 - assert np.all(np.abs(expected_x - x_sum) < threshold) - assert np.all(np.abs(expected_y - y_sum) < threshold) - - def test_grid_dict_input(self): - x, y = np.linspace(-3, 3, 200), np.linspace(-5, 5, 200) - xx, yy = np.meshgrid(x, y, indexing='ij') - pdf = (1 / (2 * np.pi)) * np.exp(-0.5 * (xx * xx + yy * yy / 4)) - c = ChainConsumer() - with pytest.raises(AssertionError): - c.add_chain({'x': x, 'y': y}, weights=pdf, grid=True) - - def test_grid_dict_input2(self): - x, y = np.linspace(-3, 3, 200), np.linspace(-5, 5, 200) - xx, yy = np.meshgrid(x, y, indexing='ij') - pdf = (1 / (2 * np.pi)) * np.exp(-0.5 * (xx * xx + yy * yy / 4)) - c = ChainConsumer() - c.add_chain({'x': xx.flatten(), 'y': yy.flatten()}, weights=pdf.flatten(), grid=True) - summary = c.analysis.get_summary() - x_sum = summary['x'] - y_sum = summary['y'] - expected_x = np.array([-1.0, 0.0, 1.0]) - expected_y = np.array([-2.0, 0.0, 2.0]) - threshold = 0.05 - assert np.all(np.abs(expected_x - x_sum) < threshold) - assert np.all(np.abs(expected_y - y_sum) < threshold) - - def test_normal_list_input(self): - tolerance = 5e-2 - consumer = ChainConsumer() - consumer.add_chain([self.data, self.data2], parameters=['x', 'y']) - # consumer.configure(bins=1.6) - summary = consumer.analysis.get_summary() - actual1 = summary['x'] - actual2 = summary['y'] - expected1 = np.array([3.5, 5.0, 6.5]) - expected2 = np.array([2.0, 3.0, 4.0]) - diff1 = np.abs(expected1 - actual1) - diff2 = np.abs(expected2 - actual2) - assert np.all(diff1 < tolerance) - assert np.all(diff2 < tolerance) - - def test_grid_3d(self): - x, y, z = np.linspace(-3, 3, 30), np.linspace(-3, 3, 30), np.linspace(-3, 3, 30) - xx, yy, zz = np.meshgrid(x, y, z, indexing='ij') - pdf = (1 / (2 * np.pi)) * np.exp(-0.5 * (xx * xx + yy * yy + zz * zz)) - c = ChainConsumer() - c.add_chain([x, y, z], parameters=['x', 'y', 'z'], weights=pdf, grid=True) - summary = c.analysis.get_summary() - expected = np.array([-1.0, 0.0, 1.0]) - for k in summary: - assert np.all(np.abs(summary[k] - expected) < 0.2) - - def test_correlations_1d(self): - data = np.random.normal(0, 1, size=100000) - parameters = ["x"] - c = ChainConsumer() - c.add_chain(data, parameters=parameters) - p, cor = c.analysis.get_correlations() - assert p[0] == "x" - assert np.isclose(cor[0, 0], 1, atol=1e-2) - assert cor.shape == (1, 1) - - def test_correlations_2d(self): - data = np.random.multivariate_normal([0, 0], [[1, 0], [0, 1]], size=100000) - parameters = ["x", "y"] - c = ChainConsumer() - c.add_chain(data, parameters=parameters) - p, cor = c.analysis.get_correlations() - assert p[0] == "x" - assert p[1] == "y" - assert np.isclose(cor[0, 0], 1, atol=1e-2) - assert np.isclose(cor[1, 1], 1, atol=1e-2) - assert np.abs(cor[0, 1]) < 0.01 - assert cor.shape == (2, 2) - - def test_correlations_3d(self): - data = np.random.multivariate_normal([0, 0, 1], [[1, 0.5, 0.2], [0.5, 1, 0.3], [0.2, 0.3, 1.0]], size=100000) - parameters = ["x", "y", "z"] - c = ChainConsumer() - c.add_chain(data, parameters=parameters, name="chain1") - p, cor = c.analysis.get_correlations(chain="chain1", parameters=["y", "z", "x"]) - assert p[0] == "y" - assert p[1] == "z" - assert p[2] == "x" - assert np.isclose(cor[0, 0], 1, atol=1e-2) - assert np.isclose(cor[1, 1], 1, atol=1e-2) - assert np.isclose(cor[2, 2], 1, atol=1e-2) - assert cor.shape == (3, 3) - assert np.abs(cor[0, 1] - 0.3) < 0.01 - assert np.abs(cor[0, 2] - 0.5) < 0.01 - assert np.abs(cor[1, 2] - 0.2) < 0.01 - - def test_correlations_2d_non_unitary(self): - data = np.random.multivariate_normal([0, 0], [[4, 0], [0, 4]], size=100000) - parameters = ["x", "y"] - c = ChainConsumer() - c.add_chain(data, parameters=parameters) - p, cor = c.analysis.get_correlations() - assert p[0] == "x" - assert p[1] == "y" - assert np.isclose(cor[0, 0], 1, atol=1e-2) - assert np.isclose(cor[1, 1], 1, atol=1e-2) - assert np.abs(cor[0, 1]) < 0.01 - assert cor.shape == (2, 2) - - def test_correlation_latex_table(self): - data = np.random.multivariate_normal([0, 0, 1], [[1, 0.5, 0.2], [0.5, 1, 0.3], [0.2, 0.3, 1.0]], size=1000000) - parameters = ["x", "y", "z"] - c = ChainConsumer() - c.add_chain(data, parameters=parameters) - latex_table = c.analysis.get_correlation_table() - - actual = r"""\begin{table} - \centering - \caption{Parameter Correlations} - \label{tab:parameter_correlations} - \begin{tabular}{c|ccc} - & x & y & z\\ - \hline - x & 1.00 & 0.50 & 0.20 \\ - y & 0.50 & 1.00 & 0.30 \\ - z & 0.20 & 0.30 & 1.00 \\ - \hline - \end{tabular} - \end{table}""" - assert latex_table.replace(" ", "") == actual.replace(" ", "") - - def test_covariance_1d(self): - data = np.random.normal(0, 2, size=2000000) - parameters = ["x"] - c = ChainConsumer() - c.add_chain(data, parameters=parameters) - p, cor = c.analysis.get_covariance() - assert p[0] == "x" - assert np.isclose(cor[0, 0], 4, atol=1e-2) - assert cor.shape == (1, 1) - - def test_covariance_2d(self): - data = np.random.multivariate_normal([0, 0], [[3, 0], [0, 9]], size=2000000) - parameters = ["x", "y"] - c = ChainConsumer() - c.add_chain(data, parameters=parameters) - p, cor = c.analysis.get_covariance() - assert p[0] == "x" - assert p[1] == "y" - assert np.isclose(cor[0, 0], 3, atol=2e-2) - assert np.isclose(cor[1, 1], 9, atol=2e-2) - assert np.isclose(cor[0, 1], 0, atol=2e-2) - assert cor.shape == (2, 2) - - def test_covariance_3d(self): - cov = [[3, 0.5, 0.2], [0.5, 4, 0.3], [0.2, 0.3, 5]] - data = np.random.multivariate_normal([0, 0, 1], cov, size=2000000) - parameters = ["x", "y", "z"] - c = ChainConsumer() - c.add_chain(data, parameters=parameters, name="chain1") - p, cor = c.analysis.get_covariance(chain="chain1", parameters=["y", "z", "x"]) - assert p[0] == "y" - assert p[1] == "z" - assert p[2] == "x" - assert np.isclose(cor[0, 0], 4, atol=2e-2) - assert np.isclose(cor[1, 1], 5, atol=2e-2) - assert np.isclose(cor[2, 2], 3, atol=2e-2) - assert cor.shape == (3, 3) - assert np.abs(cor[0, 1] - 0.3) < 0.01 - assert np.abs(cor[0, 2] - 0.5) < 0.01 - assert np.abs(cor[1, 2] - 0.2) < 0.01 - - def test_covariance_latex_table(self): - cov = [[2, 0.5, 0.2], [0.5, 3, 0.3], [0.2, 0.3, 4.0]] - data = np.random.multivariate_normal([0, 0, 1], cov, size=20000000) - parameters = ["x", "y", "z"] - c = ChainConsumer() - c.add_chain(data, parameters=parameters) - latex_table = c.analysis.get_covariance_table() - - actual = r"""\begin{table} - \centering - \caption{Parameter Covariance} - \label{tab:parameter_covariance} - \begin{tabular}{c|ccc} - & x & y & z\\ - \hline - x & 2.00 & 0.50 & 0.20 \\ - y & 0.50 & 3.00 & 0.30 \\ - z & 0.20 & 0.30 & 4.00 \\ - \hline - \end{tabular} - \end{table}""" - assert latex_table.replace(" ", "") == actual.replace(" ", "") - - def test_fail_if_more_parameters_than_data(self): - with pytest.raises(AssertionError): - ChainConsumer().add_chain(self.data_combined, parameters=["x", "y", "z"]) - - def test_covariant_covariance_calc(self): - data1 = np.random.multivariate_normal([0, 0], [[1, 0], [0, 1]], size=10000) - data2 = np.random.multivariate_normal([0, 0], [[2, 1], [1, 2]], size=10000) - weights = np.concatenate((np.ones(10000), np.zeros(10000))) - data = np.concatenate((data1, data2)) - c = ChainConsumer() - c.add_chain(data, weights=weights, parameters=["x", "y"]) - p, cor = c.analysis.get_covariance() - assert p[0] == "x" - assert p[1] == "y" - assert np.isclose(cor[0, 0], 1, atol=4e-2) - assert np.isclose(cor[1, 1], 1, atol=4e-2) - assert np.isclose(cor[0, 1], 0, atol=4e-2) - assert cor.shape == (2, 2) - - def test_2d_levels(self): - c = ChainConsumer() - c.add_chain(self.data) - c.configure(sigmas=[0, 1, 2], sigma2d=True) - levels = c.plotter._get_levels() - assert np.allclose(levels, [0, 0.39, 0.86], atol=0.01) - - def test_1d_levels(self): - c = ChainConsumer() - c.add_chain(self.data) - c.configure(sigmas=[0, 1, 2], sigma2d=False) - levels = c.plotter._get_levels() - assert np.allclose(levels, [0, 0.68, 0.95], atol=0.01) - - def test_summary_area(self): - c = ChainConsumer() - c.add_chain(self.data) - summary = c.analysis.get_summary()['0'] - expected = [3.5, 5, 6.5] - assert np.all(np.isclose(summary, expected, atol=0.1)) - - def test_summary_area_default(self): - c = ChainConsumer() - c.add_chain(self.data) - c.configure(summary_area=0.6827) - summary = c.analysis.get_summary()['0'] - expected = [3.5, 5, 6.5] - assert np.all(np.isclose(summary, expected, atol=0.1)) - - def test_summary_area_95(self): - c = ChainConsumer() - c.add_chain(self.data) - c.configure(summary_area=0.95) - summary = c.analysis.get_summary()['0'] - expected = [2, 5, 8] - assert np.all(np.isclose(summary, expected, atol=0.1)) - - def test_summary_max_symmetric_1(self): - c = ChainConsumer() - c.add_chain(self.data) - c.configure(statistics="max_symmetric") - summary = c.analysis.get_summary()['0'] - expected = [3.5, 5, 6.5] - assert np.all(np.isclose(summary, expected, atol=0.1)) - assert np.isclose(summary[2] - summary[1], summary[1] - summary[0]) - - def test_summary_max_symmetric_2(self): - c = ChainConsumer() - c.add_chain(self.data_skew) - summary_area = 0.6827 - c.configure(statistics="max_symmetric", bins=1.0, summary_area=summary_area) - summary = c.analysis.get_summary()['0'] - - xs = np.linspace(0, 2, 1000) - pdf = skewnorm.pdf(xs, 5, 1, 1.5) - xmax = xs[pdf.argmax()] - cdf_top = skewnorm.cdf(summary[2], 5, 1, 1.5) - cdf_bottom = skewnorm.cdf(summary[0], 5, 1, 1.5) - area = cdf_top - cdf_bottom - - assert np.isclose(xmax, summary[1], atol=0.05) - assert np.isclose(area, summary_area, atol=0.05) - assert np.isclose(summary[2] - summary[1], summary[1] - summary[0]) - - def test_summary_max_symmetric_3(self): - c = ChainConsumer() - c.add_chain(self.data_skew) - summary_area = 0.95 - c.configure(statistics="max_symmetric", bins=1.0, summary_area=summary_area) - summary = c.analysis.get_summary()['0'] - - xs = np.linspace(0, 2, 1000) - pdf = skewnorm.pdf(xs, 5, 1, 1.5) - xmax = xs[pdf.argmax()] - cdf_top = skewnorm.cdf(summary[2], 5, 1, 1.5) - cdf_bottom = skewnorm.cdf(summary[0], 5, 1, 1.5) - area = cdf_top - cdf_bottom - - assert np.isclose(xmax, summary[1], atol=0.05) - assert np.isclose(area, summary_area, atol=0.05) - assert np.isclose(summary[2] - summary[1], summary[1] - summary[0]) - - def test_summary_max_shortest_1(self): - c = ChainConsumer() - c.add_chain(self.data) - c.configure(statistics="max_shortest") - summary = c.analysis.get_summary()['0'] - expected = [3.5, 5, 6.5] - assert np.all(np.isclose(summary, expected, atol=0.1)) - - def test_summary_max_shortest_2(self): - c = ChainConsumer() - c.add_chain(self.data_skew) - summary_area = 0.6827 - c.configure(statistics="max_shortest", bins=1.0, summary_area=summary_area) - summary = c.analysis.get_summary()['0'] - - xs = np.linspace(-1, 5, 1000) - pdf = skewnorm.pdf(xs, 5, 1, 1.5) - cdf = skewnorm.cdf(xs, 5, 1, 1.5) - x2 = interp1d(cdf, xs, bounds_error=False, fill_value=np.inf)(cdf + summary_area) - dist = x2 - xs - ind = np.argmin(dist) - x0 = xs[ind] - x2 = x2[ind] - xmax = xs[pdf.argmax()] - - assert np.isclose(xmax, summary[1], atol=0.05) - assert np.isclose(x0, summary[0], atol=0.05) - assert np.isclose(x2, summary[2], atol=0.05) - - def test_summary_max_shortest_3(self): - c = ChainConsumer() - c.add_chain(self.data_skew) - summary_area = 0.95 - c.configure(statistics="max_shortest", bins=1.0, summary_area=summary_area) - summary = c.analysis.get_summary()['0'] - - xs = np.linspace(-1, 5, 1000) - pdf = skewnorm.pdf(xs, 5, 1, 1.5) - cdf = skewnorm.cdf(xs, 5, 1, 1.5) - x2 = interp1d(cdf, xs, bounds_error=False, fill_value=np.inf)(cdf + summary_area) - dist = x2 - xs - ind = np.argmin(dist) - x0 = xs[ind] - x2 = x2[ind] - xmax = xs[pdf.argmax()] - - assert np.isclose(xmax, summary[1], atol=0.05) - assert np.isclose(x0, summary[0], atol=0.05) - assert np.isclose(x2, summary[2], atol=0.05) - - def test_summary_max_central_1(self): - c = ChainConsumer() - c.add_chain(self.data) - c.configure(statistics="max_central") - summary = c.analysis.get_summary()['0'] - expected = [3.5, 5, 6.5] - assert np.all(np.isclose(summary, expected, atol=0.1)) - - def test_summary_max_central_2(self): - c = ChainConsumer() - c.add_chain(self.data_skew) - summary_area = 0.6827 - c.configure(statistics="max_central", bins=1.0, summary_area=summary_area) - summary = c.analysis.get_summary()['0'] - - xs = np.linspace(-1, 5, 1000) - pdf = skewnorm.pdf(xs, 5, 1, 1.5) - cdf = skewnorm.cdf(xs, 5, 1, 1.5) - xval = interp1d(cdf, xs)([0.5 - 0.5 * summary_area, 0.5 + 0.5 * summary_area]) - xmax = xs[pdf.argmax()] - - assert np.isclose(xmax, summary[1], atol=0.05) - assert np.isclose(xval[0], summary[0], atol=0.05) - assert np.isclose(xval[1], summary[2], atol=0.05) - - def test_summary_max_central_3(self): - c = ChainConsumer() - c.add_chain(self.data_skew) - summary_area = 0.95 - c.configure(statistics="max_central", bins=1.0, summary_area=summary_area) - summary = c.analysis.get_summary()['0'] - - xs = np.linspace(-1, 5, 1000) - pdf = skewnorm.pdf(xs, 5, 1, 1.5) - cdf = skewnorm.cdf(xs, 5, 1, 1.5) - xval = interp1d(cdf, xs)([0.5 - 0.5 * summary_area, 0.5 + 0.5 * summary_area]) - xmax = xs[pdf.argmax()] - - assert np.isclose(xmax, summary[1], atol=0.05) - assert np.isclose(xval[0], summary[0], atol=0.05) - assert np.isclose(xval[1], summary[2], atol=0.05) - - def test_max_likelihood_1(self): - c = ChainConsumer() - data = np.random.multivariate_normal([0, 0], [[1, 0], [0, 1]], size=10000) - posterior = norm.logpdf(data).sum(axis=1) - data[:, 1] += 1 - c.add_chain(data, parameters=["x", "y"], posterior=posterior, name="A") - result = c.analysis.get_max_posteriors() - x, y = result["x"], result["y"] - assert np.isclose(x, 0, atol=0.05) - assert np.isclose(y, 1, atol=0.05) - - def test_max_likelihood_2(self): - c = ChainConsumer() - data = np.random.multivariate_normal([0, 0], [[1, 0], [0, 1]], size=10000) - posterior = norm.logpdf(data).sum(axis=1) - data[:, 1] += 2 - c.add_chain(data, parameters=["x", "y"], posterior=posterior, name="A") - c.add_chain(data + 3, parameters=["x", "y"], name="B") - result = c.analysis.get_max_posteriors(parameters=["x", "y"], chains="A") - x, y = result["x"], result["y"] - assert np.isclose(x, 0, atol=0.05) - assert np.isclose(y, 2, atol=0.05) - - def test_max_likelihood_3(self): - c = ChainConsumer() - data = np.random.multivariate_normal([0, 0], [[1, 0], [0, 1]], size=10000) - posterior = norm.logpdf(data).sum(axis=1) - data[:, 1] += 3 - c.add_chain(data, parameters=["x", "y"], posterior=posterior, name="A") - c.add_chain(data + 3, parameters=["x", "y"], name="B") - result = c.analysis.get_max_posteriors(chains="A") - x, y = result["x"], result["y"] - assert np.isclose(x, 0, atol=0.05) - assert np.isclose(y, 3, atol=0.05) - - def test_max_likelihood_4(self): - c = ChainConsumer() - data = np.random.multivariate_normal([0, 0], [[1, 0], [0, 1]], size=10000) - posterior = norm.logpdf(data).sum(axis=1) - data[:, 1] += 2 - c.add_chain(data, parameters=["x", "y"], posterior=posterior, name="A") - c.add_chain(data + 3, parameters=["x", "y"], name="B") - result = c.analysis.get_max_posteriors(parameters="x", chains="A", squeeze=False) - assert len(result) == 1 - x = result[0]["x"] - assert np.isclose(x, 0, atol=0.05) - - def test_max_likelihood_5_failure(self): - c = ChainConsumer() - data = np.random.multivariate_normal([0, 0], [[1, 0], [0, 1]], size=10000) - data[:, 1] += 2 - c.add_chain(data, parameters=["x", "y"], name="A") - result = c.analysis.get_max_posteriors(parameters="x", chains="A") - print(result) - assert result is None + for i in range(num_walkers): + name = f"test Walker {i}" + assert name in stats + array = stats[name]["x"] + assert np.all(np.abs(array.center - means[i]) < 1e-1) + assert np.abs(consumer.get_chain(name).get_data("x").mean() - means[i]) < 1e-2 + + # def test_stats_max_cliff(self): + # tolerance = 5e-2 + # n = 100000 + # data = np.linspace(0, 10, n) + # weights = norm.pdf(data, 1, 2) + # consumer = ChainConsumer() + # consumer.add_chain(data, weights=weights) + # consumer.configure_overrides(statistics="max", bins=4.0, smooth=1) + # summary = consumer.analysis.get_summary() + # actual = np.array(next(iter(summary.values()))) + # expected = np.array([0.0, 1.0, 2.73]) + # diff = np.abs(expected - actual) + # assert np.all(diff < tolerance) + + # def test_stats_mean_normal(self): + # tolerance = 5e-2 + # consumer = ChainConsumer() + # consumer.add_chain(self.data) + # consumer.configure_overrides(statistics="mean") + # summary = consumer.analysis.get_summary() + # actual = np.array(next(iter(summary.values()))) + # expected = np.array([3.5, 5.0, 6.5]) + # diff = np.abs(expected - actual) + # assert np.all(diff < tolerance) + + # def test_stats_cum_normal(self): + # tolerance = 5e-2 + # consumer = ChainConsumer() + # consumer.add_chain(self.data) + # consumer.configure_overrides(statistics="cumulative") + # summary = consumer.analysis.get_summary() + # actual = np.array(next(iter(summary.values()))) + # expected = np.array([3.5, 5.0, 6.5]) + # diff = np.abs(expected - actual) + # assert np.all(diff < tolerance) + + # def test_reject_bad_satst(self): + # consumer = ChainConsumer() + # consumer.add_chain(self.data) + # with pytest.raises(AssertionError): + # consumer.configure_overrides(statistics="monkey") + + # def test_stats_max_skew(self): + # tolerance = 3e-2 + # consumer = ChainConsumer() + # consumer.add_chain(self.data_skew) + # consumer.configure_overrides(statistics="max") + # summary = consumer.analysis.get_summary() + # actual = np.array(next(iter(summary.values()))) + # expected = np.array([1.01, 1.55, 2.72]) + # diff = np.abs(expected - actual) + # assert np.all(diff < tolerance) + + # def test_stats_mean_skew(self): + # tolerance = 3e-2 + # consumer = ChainConsumer() + # consumer.add_chain(self.data_skew) + # consumer.configure_overrides(statistics="mean") + # summary = consumer.analysis.get_summary() + # actual = np.array(next(iter(summary.values()))) + # expected = np.array([1.27, 2.19, 3.11]) + # diff = np.abs(expected - actual) + # assert np.all(diff < tolerance) + + # def test_stats_cum_skew(self): + # tolerance = 3e-2 + # consumer = ChainConsumer() + # consumer.add_chain(self.data_skew) + # consumer.configure_overrides(statistics="cumulative") + # summary = consumer.analysis.get_summary() + # actual = np.array(next(iter(summary.values()))) + # expected = np.array([1.27, 2.01, 3.11]) + # diff = np.abs(expected - actual) + # assert np.all(diff < tolerance) + + # def test_stats_list_skew(self): + # tolerance = 3e-2 + # consumer = ChainConsumer() + # consumer.add_chain(self.data_skew) + # consumer.add_chain(self.data_skew) + # consumer.configure_overrides(statistics=["cumulative", "mean"]) + # summary = consumer.analysis.get_summary() + # actual0 = np.array(next(iter(summary[0].values()))) + # actual1 = np.array(next(iter(summary[1].values()))) + # expected0 = np.array([1.27, 2.01, 3.11]) + # expected1 = np.array([1.27, 2.19, 3.11]) + # diff0 = np.abs(expected0 - actual0) + # diff1 = np.abs(expected1 - actual1) + # assert np.all(diff0 < tolerance) + # assert np.all(diff1 < tolerance) + + # def test_weights(self): + # tolerance = 3e-2 + # samples = np.linspace(-4, 4, 200000) + # weights = norm.pdf(samples) + # c = ChainConsumer() + # c.add_chain(samples, weights=weights) + # expected = np.array([-1.0, 0.0, 1.0]) + # summary = c.analysis.get_summary() + # actual = np.array(next(iter(summary.values()))) + # diff = np.abs(expected - actual) + # assert np.all(diff < tolerance) + + # def test_grid_data(self): + # x, y = np.linspace(-3, 3, 200), np.linspace(-5, 5, 200) + # xx, yy = np.meshgrid(x, y, indexing="ij") + # xs, ys = xx.flatten(), yy.flatten() + # chain = np.vstack((xs, ys)).T + # pdf = (1 / (2 * np.pi)) * np.exp(-0.5 * (xs * xs + ys * ys / 4)) + # c = ChainConsumer() + # c.add_chain(chain, parameters=["x", "y"], weights=pdf, grid=True) + # summary = c.analysis.get_summary() + # x_sum = summary["x"] + # y_sum = summary["y"] + # expected_x = np.array([-1.0, 0.0, 1.0]) + # expected_y = np.array([-2.0, 0.0, 2.0]) + # threshold = 0.1 + # assert np.all(np.abs(expected_x - x_sum) < threshold) + # assert np.all(np.abs(expected_y - y_sum) < threshold) + + # def test_grid_list_input(self): + # x, y = np.linspace(-3, 3, 200), np.linspace(-5, 5, 200) + # xx, yy = np.meshgrid(x, y, indexing="ij") + # pdf = (1 / (2 * np.pi)) * np.exp(-0.5 * (xx * xx + yy * yy / 4)) + # c = ChainConsumer() + # c.add_chain([x, y], parameters=["x", "y"], weights=pdf, grid=True) + # summary = c.analysis.get_summary() + # x_sum = summary["x"] + # y_sum = summary["y"] + # expected_x = np.array([-1.0, 0.0, 1.0]) + # expected_y = np.array([-2.0, 0.0, 2.0]) + # threshold = 0.05 + # assert np.all(np.abs(expected_x - x_sum) < threshold) + # assert np.all(np.abs(expected_y - y_sum) < threshold) + + # def test_grid_dict_input(self): + # x, y = np.linspace(-3, 3, 200), np.linspace(-5, 5, 200) + # xx, yy = np.meshgrid(x, y, indexing="ij") + # pdf = (1 / (2 * np.pi)) * np.exp(-0.5 * (xx * xx + yy * yy / 4)) + # c = ChainConsumer() + # with pytest.raises(AssertionError): + # c.add_chain({"x": x, "y": y}, weights=pdf, grid=True) + + # def test_grid_dict_input2(self): + # x, y = np.linspace(-3, 3, 200), np.linspace(-5, 5, 200) + # xx, yy = np.meshgrid(x, y, indexing="ij") + # pdf = (1 / (2 * np.pi)) * np.exp(-0.5 * (xx * xx + yy * yy / 4)) + # c = ChainConsumer() + # c.add_chain({"x": xx.flatten(), "y": yy.flatten()}, weights=pdf.flatten(), grid=True) + # summary = c.analysis.get_summary() + # x_sum = summary["x"] + # y_sum = summary["y"] + # expected_x = np.array([-1.0, 0.0, 1.0]) + # expected_y = np.array([-2.0, 0.0, 2.0]) + # threshold = 0.05 + # assert np.all(np.abs(expected_x - x_sum) < threshold) + # assert np.all(np.abs(expected_y - y_sum) < threshold) + + # def test_normal_list_input(self): + # tolerance = 5e-2 + # consumer = ChainConsumer() + # consumer.add_chain([self.data, self.data2], parameters=["x", "y"]) + # # consumer.configure(bins=1.6) + # summary = consumer.analysis.get_summary() + # actual1 = summary["x"] + # actual2 = summary["y"] + # expected1 = np.array([3.5, 5.0, 6.5]) + # expected2 = np.array([2.0, 3.0, 4.0]) + # diff1 = np.abs(expected1 - actual1) + # diff2 = np.abs(expected2 - actual2) + # assert np.all(diff1 < tolerance) + # assert np.all(diff2 < tolerance) + + # def test_grid_3d(self): + # x, y, z = np.linspace(-3, 3, 30), np.linspace(-3, 3, 30), np.linspace(-3, 3, 30) + # xx, yy, zz = np.meshgrid(x, y, z, indexing="ij") + # pdf = (1 / (2 * np.pi)) * np.exp(-0.5 * (xx * xx + yy * yy + zz * zz)) + # c = ChainConsumer() + # c.add_chain([x, y, z], parameters=["x", "y", "z"], weights=pdf, grid=True) + # summary = c.analysis.get_summary() + # expected = np.array([-1.0, 0.0, 1.0]) + # for k in summary: + # assert np.all(np.abs(summary[k] - expected) < 0.2) + + # def test_correlations_1d(self): + # data = self.rng.normal(0, 1, size=100000) + # parameters = ["x"] + # c = ChainConsumer() + # c.add_chain(data, parameters=parameters) + # p, cor = c.analysis.get_correlations() + # assert p[0] == "x" + # assert np.isclose(cor[0, 0], 1, atol=1e-2) + # assert cor.shape == (1, 1) + + # def test_correlations_2d(self): + # data = self.rng.multivariate_normal([0, 0], [[1, 0], [0, 1]], size=100000) + # parameters = ["x", "y"] + # c = ChainConsumer() + # c.add_chain(data, parameters=parameters) + # p, cor = c.analysis.get_correlations() + # assert p[0] == "x" + # assert p[1] == "y" + # assert np.isclose(cor[0, 0], 1, atol=1e-2) + # assert np.isclose(cor[1, 1], 1, atol=1e-2) + # assert np.abs(cor[0, 1]) < 0.01 + # assert cor.shape == (2, 2) + + # def test_correlations_3d(self): + # data = self.rng.multivariate_normal([0, 0, 1], [[1, 0.5, 0.2], [0.5, 1, 0.3], [0.2, 0.3, 1.0]], size=100000) + # parameters = ["x", "y", "z"] + # c = ChainConsumer() + # c.add_chain(data, parameters=parameters, name="chain1") + # p, cor = c.analysis.get_correlations(chain="chain1", parameters=["y", "z", "x"]) + # assert p[0] == "y" + # assert p[1] == "z" + # assert p[2] == "x" + # assert np.isclose(cor[0, 0], 1, atol=1e-2) + # assert np.isclose(cor[1, 1], 1, atol=1e-2) + # assert np.isclose(cor[2, 2], 1, atol=1e-2) + # assert cor.shape == (3, 3) + # assert np.abs(cor[0, 1] - 0.3) < 0.01 + # assert np.abs(cor[0, 2] - 0.5) < 0.01 + # assert np.abs(cor[1, 2] - 0.2) < 0.01 + + # def test_correlations_2d_non_unitary(self): + # data = self.rng.multivariate_normal([0, 0], [[4, 0], [0, 4]], size=100000) + # parameters = ["x", "y"] + # c = ChainConsumer() + # c.add_chain(data, parameters=parameters) + # p, cor = c.analysis.get_correlations() + # assert p[0] == "x" + # assert p[1] == "y" + # assert np.isclose(cor[0, 0], 1, atol=1e-2) + # assert np.isclose(cor[1, 1], 1, atol=1e-2) + # assert np.abs(cor[0, 1]) < 0.01 + # assert cor.shape == (2, 2) + + # def test_correlation_latex_table(self): + # data = self.rng.multivariate_normal([0, 0, 1], [[1, 0.5, 0.2], [0.5, 1, 0.3], [0.2, 0.3, 1.0]], size=1000000) + # parameters = ["x", "y", "z"] + # c = ChainConsumer() + # c.add_chain(data, parameters=parameters) + # latex_table = c.analysis.get_correlation_table() + + # actual = r"""\begin{table} + # \centering + # \caption{Parameter Correlations} + # \label{tab:parameter_correlations} + # \begin{tabular}{c|ccc} + # & x & y & z\\ + # \hline + # x & 1.00 & 0.50 & 0.20 \\ + # y & 0.50 & 1.00 & 0.30 \\ + # z & 0.20 & 0.30 & 1.00 \\ + # \hline + # \end{tabular} + # \end{table}""" + # assert latex_table.replace(" ", "") == actual.replace(" ", "") + + # def test_covariance_1d(self): + # data = self.rng.normal(0, 2, size=2000000) + # parameters = ["x"] + # c = ChainConsumer() + # c.add_chain(data, parameters=parameters) + # p, cor = c.analysis.get_covariance() + # assert p[0] == "x" + # assert np.isclose(cor[0, 0], 4, atol=1e-2) + # assert cor.shape == (1, 1) + + # def test_covariance_2d(self): + # data = self.rng.multivariate_normal([0, 0], [[3, 0], [0, 9]], size=2000000) + # parameters = ["x", "y"] + # c = ChainConsumer() + # c.add_chain(data, parameters=parameters) + # p, cor = c.analysis.get_covariance() + # assert p[0] == "x" + # assert p[1] == "y" + # assert np.isclose(cor[0, 0], 3, atol=2e-2) + # assert np.isclose(cor[1, 1], 9, atol=2e-2) + # assert np.isclose(cor[0, 1], 0, atol=2e-2) + # assert cor.shape == (2, 2) + + # def test_covariance_3d(self): + # cov = [[3, 0.5, 0.2], [0.5, 4, 0.3], [0.2, 0.3, 5]] + # data = self.rng.multivariate_normal([0, 0, 1], cov, size=2000000) + # parameters = ["x", "y", "z"] + # c = ChainConsumer() + # c.add_chain(data, parameters=parameters, name="chain1") + # p, cor = c.analysis.get_covariance(chain="chain1", parameters=["y", "z", "x"]) + # assert p[0] == "y" + # assert p[1] == "z" + # assert p[2] == "x" + # assert np.isclose(cor[0, 0], 4, atol=2e-2) + # assert np.isclose(cor[1, 1], 5, atol=2e-2) + # assert np.isclose(cor[2, 2], 3, atol=2e-2) + # assert cor.shape == (3, 3) + # assert np.abs(cor[0, 1] - 0.3) < 0.01 + # assert np.abs(cor[0, 2] - 0.5) < 0.01 + # assert np.abs(cor[1, 2] - 0.2) < 0.01 + + # def test_covariance_latex_table(self): + # cov = [[2, 0.5, 0.2], [0.5, 3, 0.3], [0.2, 0.3, 4.0]] + # data = self.rng.multivariate_normal([0, 0, 1], cov, size=20000000) + # parameters = ["x", "y", "z"] + # c = ChainConsumer() + # c.add_chain(data, parameters=parameters) + # latex_table = c.analysis.get_covariance_table() + + # actual = r"""\begin{table} + # \centering + # \caption{Parameter Covariance} + # \label{tab:parameter_covariance} + # \begin{tabular}{c|ccc} + # & x & y & z\\ + # \hline + # x & 2.00 & 0.50 & 0.20 \\ + # y & 0.50 & 3.00 & 0.30 \\ + # z & 0.20 & 0.30 & 4.00 \\ + # \hline + # \end{tabular} + # \end{table}""" + # assert latex_table.replace(" ", "") == actual.replace(" ", "") + + # def test_fail_if_more_parameters_than_data(self): + # with pytest.raises(AssertionError): + # ChainConsumer().add_chain(self.data_combined, parameters=["x", "y", "z"]) + + # def test_covariant_covariance_calc(self): + # data1 = self.rng.multivariate_normal([0, 0], [[1, 0], [0, 1]], size=10000) + # data2 = self.rng.multivariate_normal([0, 0], [[2, 1], [1, 2]], size=10000) + # weights = np.concatenate((np.ones(10000), np.zeros(10000))) + # data = np.concatenate((data1, data2)) + # c = ChainConsumer() + # c.add_chain(data, weights=weights, parameters=["x", "y"]) + # p, cor = c.analysis.get_covariance() + # assert p[0] == "x" + # assert p[1] == "y" + # assert np.isclose(cor[0, 0], 1, atol=4e-2) + # assert np.isclose(cor[1, 1], 1, atol=4e-2) + # assert np.isclose(cor[0, 1], 0, atol=4e-2) + # assert cor.shape == (2, 2) + + # def test_2d_levels(self): + # c = ChainConsumer() + # c.add_chain(self.data) + # c.configure_overrides(sigmas=[0, 1, 2], sigma2d=True) + # levels = c.plotter._get_levels() + # assert np.allclose(levels, [0, 0.39, 0.86], atol=0.01) + + # def test_1d_levels(self): + # c = ChainConsumer() + # c.add_chain(self.data) + # c.configure_overrides(sigmas=[0, 1, 2], sigma2d=False) + # levels = c.plotter._get_levels() + # assert np.allclose(levels, [0, 0.68, 0.95], atol=0.01) + + # def test_summary_area(self): + # c = ChainConsumer() + # c.add_chain(self.data) + # summary = c.analysis.get_summary()["0"] + # expected = [3.5, 5, 6.5] + # assert np.all(np.isclose(summary, expected, atol=0.1)) + + # def test_summary_area_default(self): + # c = ChainConsumer() + # c.add_chain(self.data) + # c.configure_overrides(summary_area=0.6827) + # summary = c.analysis.get_summary()["0"] + # expected = [3.5, 5, 6.5] + # assert np.all(np.isclose(summary, expected, atol=0.1)) + + # def test_summary_area_95(self): + # c = ChainConsumer() + # c.add_chain(self.data) + # c.configure_overrides(summary_area=0.95) + # summary = c.analysis.get_summary()["0"] + # expected = [2, 5, 8] + # assert np.all(np.isclose(summary, expected, atol=0.1)) + + # def test_summary_max_symmetric_1(self): + # c = ChainConsumer() + # c.add_chain(self.data) + # c.configure_overrides(statistics="max_symmetric") + # summary = c.analysis.get_summary()["0"] + # expected = [3.5, 5, 6.5] + # assert np.all(np.isclose(summary, expected, atol=0.1)) + # assert np.isclose(summary[2] - summary[1], summary[1] - summary[0]) + + # def test_summary_max_symmetric_2(self): + # c = ChainConsumer() + # c.add_chain(self.data_skew) + # summary_area = 0.6827 + # c.configure_overrides(statistics="max_symmetric", bins=1.0, summary_area=summary_area) + # summary = c.analysis.get_summary()["0"] + + # xs = np.linspace(0, 2, 1000) + # pdf = skewnorm.pdf(xs, 5, 1, 1.5) + # xmax = xs[pdf.argmax()] + # cdf_top = skewnorm.cdf(summary[2], 5, 1, 1.5) + # cdf_bottom = skewnorm.cdf(summary[0], 5, 1, 1.5) + # area = cdf_top - cdf_bottom + + # assert np.isclose(xmax, summary[1], atol=0.05) + # assert np.isclose(area, summary_area, atol=0.05) + # assert np.isclose(summary[2] - summary[1], summary[1] - summary[0]) + + # def test_summary_max_symmetric_3(self): + # c = ChainConsumer() + # c.add_chain(self.data_skew) + # summary_area = 0.95 + # c.configure_overrides(statistics="max_symmetric", bins=1.0, summary_area=summary_area) + # summary = c.analysis.get_summary()["0"] + + # xs = np.linspace(0, 2, 1000) + # pdf = skewnorm.pdf(xs, 5, 1, 1.5) + # xmax = xs[pdf.argmax()] + # cdf_top = skewnorm.cdf(summary[2], 5, 1, 1.5) + # cdf_bottom = skewnorm.cdf(summary[0], 5, 1, 1.5) + # area = cdf_top - cdf_bottom + + # assert np.isclose(xmax, summary[1], atol=0.05) + # assert np.isclose(area, summary_area, atol=0.05) + # assert np.isclose(summary[2] - summary[1], summary[1] - summary[0]) + + # def test_summary_max_shortest_1(self): + # c = ChainConsumer() + # c.add_chain(self.data) + # c.configure_overrides(statistics="max_shortest") + # summary = c.analysis.get_summary()["0"] + # expected = [3.5, 5, 6.5] + # assert np.all(np.isclose(summary, expected, atol=0.1)) + + # def test_summary_max_shortest_2(self): + # c = ChainConsumer() + # c.add_chain(self.data_skew) + # summary_area = 0.6827 + # c.configure_overrides(statistics="max_shortest", bins=1.0, summary_area=summary_area) + # summary = c.analysis.get_summary()["0"] + + # xs = np.linspace(-1, 5, 1000) + # pdf = skewnorm.pdf(xs, 5, 1, 1.5) + # cdf = skewnorm.cdf(xs, 5, 1, 1.5) + # x2 = interp1d(cdf, xs, bounds_error=False, fill_value=np.inf)(cdf + summary_area) + # dist = x2 - xs + # ind = np.argmin(dist) + # x0 = xs[ind] + # x2 = x2[ind] + # xmax = xs[pdf.argmax()] + + # assert np.isclose(xmax, summary[1], atol=0.05) + # assert np.isclose(x0, summary[0], atol=0.05) + # assert np.isclose(x2, summary[2], atol=0.05) + + # def test_summary_max_shortest_3(self): + # c = ChainConsumer() + # c.add_chain(self.data_skew) + # summary_area = 0.95 + # c.configure_overrides(statistics="max_shortest", bins=1.0, summary_area=summary_area) + # summary = c.analysis.get_summary()["0"] + + # xs = np.linspace(-1, 5, 1000) + # pdf = skewnorm.pdf(xs, 5, 1, 1.5) + # cdf = skewnorm.cdf(xs, 5, 1, 1.5) + # x2 = interp1d(cdf, xs, bounds_error=False, fill_value=np.inf)(cdf + summary_area) + # dist = x2 - xs + # ind = np.argmin(dist) + # x0 = xs[ind] + # x2 = x2[ind] + # xmax = xs[pdf.argmax()] + + # assert np.isclose(xmax, summary[1], atol=0.05) + # assert np.isclose(x0, summary[0], atol=0.05) + # assert np.isclose(x2, summary[2], atol=0.05) + + # def test_summary_max_central_1(self): + # c = ChainConsumer() + # c.add_chain(self.data) + # c.configure_overrides(statistics="max_central") + # summary = c.analysis.get_summary()["0"] + # expected = [3.5, 5, 6.5] + # assert np.all(np.isclose(summary, expected, atol=0.1)) + + # def test_summary_max_central_2(self): + # c = ChainConsumer() + # c.add_chain(self.data_skew) + # summary_area = 0.6827 + # c.configure_overrides(statistics="max_central", bins=1.0, summary_area=summary_area) + # summary = c.analysis.get_summary()["0"] + + # xs = np.linspace(-1, 5, 1000) + # pdf = skewnorm.pdf(xs, 5, 1, 1.5) + # cdf = skewnorm.cdf(xs, 5, 1, 1.5) + # xval = interp1d(cdf, xs)([0.5 - 0.5 * summary_area, 0.5 + 0.5 * summary_area]) + # xmax = xs[pdf.argmax()] + + # assert np.isclose(xmax, summary[1], atol=0.05) + # assert np.isclose(xval[0], summary[0], atol=0.05) + # assert np.isclose(xval[1], summary[2], atol=0.05) + + # def test_summary_max_central_3(self): + # c = ChainConsumer() + # c.add_chain(self.data_skew) + # summary_area = 0.95 + # c.configure_overrides(statistics="max_central", bins=1.0, summary_area=summary_area) + # summary = c.analysis.get_summary()["0"] + + # xs = np.linspace(-1, 5, 1000) + # pdf = skewnorm.pdf(xs, 5, 1, 1.5) + # cdf = skewnorm.cdf(xs, 5, 1, 1.5) + # xval = interp1d(cdf, xs)([0.5 - 0.5 * summary_area, 0.5 + 0.5 * summary_area]) + # xmax = xs[pdf.argmax()] + + # assert np.isclose(xmax, summary[1], atol=0.05) + # assert np.isclose(xval[0], summary[0], atol=0.05) + # assert np.isclose(xval[1], summary[2], atol=0.05) + + # def test_max_likelihood_1(self): + # c = ChainConsumer() + # data = self.rng.multivariate_normal([0, 0], [[1, 0], [0, 1]], size=10000) + # posterior = norm.logpdf(data).sum(axis=1) + # data[:, 1] += 1 + # c.add_chain(data, parameters=["x", "y"], posterior=posterior, name="A") + # result = c.analysis.get_max_posteriors() + # x, y = result["x"], result["y"] + # assert np.isclose(x, 0, atol=0.05) + # assert np.isclose(y, 1, atol=0.05) + + # def test_max_likelihood_2(self): + # c = ChainConsumer() + # data = self.rng.multivariate_normal([0, 0], [[1, 0], [0, 1]], size=10000) + # posterior = norm.logpdf(data).sum(axis=1) + # data[:, 1] += 2 + # c.add_chain(data, parameters=["x", "y"], posterior=posterior, name="A") + # c.add_chain(data + 3, parameters=["x", "y"], name="B") + # result = c.analysis.get_max_posteriors(parameters=["x", "y"], chains="A") + # x, y = result["x"], result["y"] + # assert np.isclose(x, 0, atol=0.05) + # assert np.isclose(y, 2, atol=0.05) + + # def test_max_likelihood_3(self): + # c = ChainConsumer() + # data = self.rng.multivariate_normal([0, 0], [[1, 0], [0, 1]], size=10000) + # posterior = norm.logpdf(data).sum(axis=1) + # data[:, 1] += 3 + # c.add_chain(data, parameters=["x", "y"], posterior=posterior, name="A") + # c.add_chain(data + 3, parameters=["x", "y"], name="B") + # result = c.analysis.get_max_posteriors(chains="A") + # x, y = result["x"], result["y"] + # assert np.isclose(x, 0, atol=0.05) + # assert np.isclose(y, 3, atol=0.05) + + # def test_max_likelihood_4(self): + # c = ChainConsumer() + # data = self.rng.multivariate_normal([0, 0], [[1, 0], [0, 1]], size=10000) + # posterior = norm.logpdf(data).sum(axis=1) + # data[:, 1] += 2 + # c.add_chain(data, parameters=["x", "y"], posterior=posterior, name="A") + # c.add_chain(data + 3, parameters=["x", "y"], name="B") + # result = c.analysis.get_max_posteriors(parameters="x", chains="A", squeeze=False) + # assert len(result) == 1 + # x = result[0]["x"] + # assert np.isclose(x, 0, atol=0.05) + + # def test_max_likelihood_5_failure(self): + # c = ChainConsumer() + # data = self.rng.multivariate_normal([0, 0], [[1, 0], [0, 1]], size=10000) + # data[:, 1] += 2 + # c.add_chain(data, parameters=["x", "y"], name="A") + # result = c.analysis.get_max_posteriors(parameters="x", chains="A") + # assert result is None diff --git a/tests/test_chain.py b/tests/test_chain.py index 227de83d..bfff9820 100644 --- a/tests/test_chain.py +++ b/tests/test_chain.py @@ -1,332 +1,99 @@ import numpy as np import pandas as pd -from scipy.stats import norm -from numpy.random import normal import pytest -import sys -sys.path.append("..") +from pydantic import ValidationError + from chainconsumer.chain import Chain -from chainconsumer.chainconsumer import ChainConsumer -class TestChain(object): - d = normal(size=(100, 3)) - d2 = normal(size=(1000000, 3)) - bad = d.copy() - bad[0, 0] = np.nan - p = ["a", "b", "c"] +class TestChain: + rng = np.random.default_rng(0) + d = rng.normal(size=(100, 3)) + d2 = rng.normal(size=(1000000, 3)) + p = ("a", "b", "c") + df = pd.DataFrame(d, columns=p) + dfw = df.assign(weight=1) + dfp = df.assign(log_posterior=1) + dfp_bad = dfp.copy() + dfp_bad["log_posterior"][0] = np.nan + bad = df.copy() + bad["a"][0] = np.nan n = "A" - w = np.ones(100) - w2 = np.ones(1000000) def test_good_chain(self): - Chain(self.d, self.p, self.n) + Chain(samples=self.df, name=self.n) def test_good_chain_weights1(self): - Chain(self.d, self.p, self.n, self.w) - - def test_good_chain_weights2(self): - Chain(self.d, self.p, self.n, self.w[None]) - - def test_good_chain_weights3(self): - Chain(self.d, self.p, self.n, self.w[None].T) + Chain(samples=self.dfw, name=self.n) def test_chain_with_bad_weights1(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, weights=np.ones((50, 1))) + with pytest.raises(ValidationError): + Chain(samples=self.dfw, name=self.n, weight_column="potato") def test_chain_with_bad_weights2(self): - with pytest.raises(AssertionError): - w = self.w.copy() - w[10] = np.inf - Chain(self.d, self.p, self.n, weights=w) - - def test_chain_with_bad_weights3(self): - with pytest.raises(AssertionError): - w = self.w.copy() - w[10] = np.nan - Chain(self.d, self.p, self.n, weights=w) - - def test_chain_with_bad_weights4(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, weights=np.ones((50, 2))) - - def test_chain_with_bad_name1(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, 1) - - def test_chain_with_bad_name2(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, None) - - def test_chain_with_bad_params1(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p[:-1], self.n) + with pytest.raises(ValidationError): + df2 = self.dfw.copy() + df2["weight"][10] = np.inf + Chain(samples=df2, name=self.n) def test_chain_with_bad_params2(self): - with pytest.raises(AssertionError): - Chain(self.d, ["A", "B", 0], self.n) - - def test_chain_with_bad_params3(self): - with pytest.raises(AssertionError): - Chain(self.d, None, self.n) - - def test_chain_with_bad_chain_initial_success1(self): - Chain(self.bad, self.p, self.n) - - def test_chain_with_bad_chain_initial_success2(self): - c = Chain(self.bad, self.p, self.n) - c.get_data(1) + with pytest.raises(ValidationError): + df2 = self.df.copy() + df2.columns = ["A", "B", 0] + Chain(samples=df2, name=self.n) - def test_chain_with_bad_chain_fails_on_access1(self): - c = Chain(self.bad, self.p, self.n) - with pytest.raises(AssertionError): - c.get_data(0) - - def test_chain_with_bad_chain_fails_on_access2(self): - c = Chain(self.bad, self.p, self.n) - with pytest.raises(AssertionError): - c.get_data(self.p[0]) + def test_chain_with_bad_chain(self): + with pytest.raises(ValidationError): + Chain(samples=self.bad, name=self.n) def test_good_grid(self): - Chain(self.d, self.p, self.n, grid=False) - - def test_bad_grid1(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, grid=0) - - def test_bad_grid2(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, grid=None) - - def test_bad_grid3(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, grid="False") + Chain(samples=self.df, name=self.n, grid=False) def test_good_walkers1(self): - Chain(self.d, self.p, self.n, walkers=10) - - def test_good_walkers2(self): - Chain(self.d, self.p, self.n, walkers=10.0) + Chain(samples=self.df, name=self.n, walkers=10) def test_bad_walkers1(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, walkers=2000) + with pytest.raises(ValidationError): + Chain(samples=self.df, name=self.n, walkers=2000) def test_bad_walkers2(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, walkers=11) - - def test_bad_walkers3(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, walkers="5") - - def test_bad_walkers4(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, walkers=2.5) + with pytest.raises(ValidationError): + Chain(samples=self.df, name=self.n, walkers=11) def test_good_posterior1(self): - Chain(self.d, self.p, self.n, posterior=np.ones(100)) - - def test_good_posterior2(self): - Chain(self.d, self.p, self.n, posterior=np.ones((100, 1))) - - def test_good_posterior3(self): - Chain(self.d, self.p, self.n, posterior=np.ones((1, 100))) + Chain(samples=self.dfp, name=self.n) def test_bad_posterior1(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, posterior=np.ones((2, 50))) - - def test_bad_posterior2(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, posterior=np.ones(50)) - - def test_bad_posterior3(self): - posterior = np.ones(100) - posterior[0] = np.nan - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, posterior=posterior) - - def test_bad_posterior4(self): - posterior = np.ones(100) - posterior[0] = np.inf - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, posterior=posterior) - - def test_bad_posterior5(self): - posterior = np.ones(100) - posterior[0] = -np.inf - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, posterior=posterior) + with pytest.raises(ValidationError): + Chain(samples=self.dfp_bad, name=self.n) def test_good_num_free_params1(self): - Chain(self.d, self.p, self.n, num_free_params=2) - - def test_good_num_free_params2(self): - Chain(self.d, self.p, self.n, num_free_params=2.0) - - def test_bad_num_free_params1(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, num_free_params="2.5") - - def test_bad_num_free_params2(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, num_free_params=np.inf) - - def test_bad_num_free_params3(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, num_free_params=np.nan) + Chain(samples=self.df, name=self.n, num_free_params=2) def test_bad_num_free_params4(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, num_free_params=-10) + with pytest.raises(ValidationError): + Chain(samples=self.df, name=self.n, num_free_params=-10) def test_good_num_eff_data_points1(self): - Chain(self.d, self.p, self.n, num_eff_data_points=2) + Chain(samples=self.df, name=self.n, num_eff_data_points=2) def test_good_num_eff_data_points2(self): - Chain(self.d, self.p, self.n, num_eff_data_points=20.4) - - def test_bad_num_eff_data_points1(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, num_eff_data_points="2.5") + Chain(samples=self.df, name=self.n, num_eff_data_points=20.4) def test_bad_num_eff_data_points2(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, num_eff_data_points=np.nan) + with pytest.raises(ValidationError): + Chain(samples=self.df, name=self.n, num_eff_data_points=np.nan) def test_bad_num_eff_data_points3(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, num_eff_data_points=np.inf) + with pytest.raises(ValidationError): + Chain(samples=self.df, name=self.n, num_eff_data_points=np.inf) def test_bad_num_eff_data_points4(self): - with pytest.raises(AssertionError): - Chain(self.d, self.p, self.n, num_eff_data_points=-100) - - def test_color_data_none(self): - c = ChainConsumer() - c.add_chain(self.d, parameters=self.p, name=self.n, weights=self.w, posterior=np.ones(100)) - c.configure(color_params=None) - chain = c.chains[0] - assert chain.get_color_data() is None + with pytest.raises(ValidationError): + Chain(samples=self.df, name=self.n, num_eff_data_points=-100) def test_color_data_p1(self): - c = ChainConsumer() - c.add_chain(self.d, parameters=self.p, name=self.n, weights=self.w, posterior=np.ones(100)) - c.configure(color_params=self.p[0]) - chain = c.chains[0] - assert np.all(chain.get_color_data() == self.d[:, 0]) - - def test_color_data_w(self): - c = ChainConsumer() - c.add_chain(self.d, parameters=self.p, name=self.n, weights=self.w, posterior=np.ones(100)) - c.configure(color_params="weights") - chain = c.chains[0] - assert np.all(chain.get_color_data() == self.w) - - def test_color_data_logw(self): - c = ChainConsumer() - c.add_chain(self.d, parameters=self.p, name=self.n, weights=self.w, posterior=np.ones(100)) - c.configure(color_params="log_weights") - chain = c.chains[0] - assert np.all(chain.get_color_data() == np.log(self.w)) - - def test_color_data_posterior(self): - c = ChainConsumer() - c.add_chain(self.d, parameters=self.p, name=self.n, weights=self.w, posterior=np.ones(100)) - c.configure(color_params="posterior") - chain = c.chains[0] - assert np.all(chain.get_color_data() == np.ones(100)) - - def test_override_color(self): - c = ChainConsumer() - c.add_chain(self.d, parameters=self.p, color="#4286f4") - c.configure() - assert c.chains[0].config["color"] == "#4286f4" - - def test_override_linewidth(self): - c = ChainConsumer() - c.add_chain(self.d, parameters=self.p, linewidth=2.0) - c.configure(linewidths=[100]) - assert c.chains[0].config["linewidth"] == 100 - - def test_override_linestyle(self): - c = ChainConsumer() - c.add_chain(self.d, parameters=self.p, linestyle="--") - c.configure() - assert c.chains[0].config["linestyle"] == "--" - - def test_override_shade_alpha(self): - c = ChainConsumer() - c.add_chain(self.d, parameters=self.p, shade_alpha=0.8) - c.configure() - assert c.chains[0].config["shade_alpha"] == 0.8 - - def test_override_kde(self): - c = ChainConsumer() - c.add_chain(self.d, parameters=self.p, kde=2.0) - c.configure() - assert c.chains[0].config["kde"] == 2.0 - - def test_override_kde_grid(self): - c = ChainConsumer() - x, y = np.linspace(0, 10, 10), np.linspace(0, 10, 10) - z = np.ones((10, 10)) - c.add_chain([x, y], weights=z, grid=True, kde=2.0) - c.configure() - assert not c.chains[0].config["kde"] - - def test_cache_invalidation(self): - c = ChainConsumer() - c.add_chain(normal(size=(1000000, 1)), parameters=["a"]) - c.configure(summary_area=0.68) - summary1 = c.analysis.get_summary() - c.configure(summary_area=0.95) - summary2 = c.analysis.get_summary() - assert np.isclose(summary1["a"][0], -1, atol=0.03) - assert np.isclose(summary2["a"][0], -2, atol=0.03) - assert np.isclose(summary1["a"][2], 1, atol=0.03) - assert np.isclose(summary2["a"][2], 2, atol=0.03) - - def test_pass_in_dataframe1(self): - df = pd.DataFrame(self.d2, columns=self.p) - c = ChainConsumer() - c.add_chain(df) - summary1 = c.analysis.get_summary() - assert np.isclose(summary1["a"][0], -1, atol=0.03) - assert np.isclose(summary1["a"][1], 0, atol=0.05) - assert np.isclose(summary1["a"][2], 1, atol=0.03) - assert np.isclose(summary1["b"][0], -1, atol=0.03) - assert np.isclose(summary1["c"][0], -1, atol=0.03) - - def test_pass_in_dataframe2(self): - df = pd.DataFrame(self.d2, columns=self.p) - df["weight"] = self.w2 - c = ChainConsumer() - c.add_chain(df) - summary1 = c.analysis.get_summary() - assert np.isclose(summary1["a"][0], -1, atol=0.03) - assert np.isclose(summary1["a"][1], 0, atol=0.05) - assert np.isclose(summary1["a"][2], 1, atol=0.03) - assert np.isclose(summary1["b"][0], -1, atol=0.03) - assert np.isclose(summary1["c"][0], -1, atol=0.03) - - def test_pass_in_dataframe3(self): - data = np.random.uniform(-4, 6, size=(1000000, 1)) - weight = norm.pdf(data) - df = pd.DataFrame(data, columns=["a"]) - df["weight"] = weight - c = ChainConsumer() - c.add_chain(df) - summary1 = c.analysis.get_summary() - assert np.isclose(summary1["a"][0], -1, atol=0.03) - assert np.isclose(summary1["a"][1], 0, atol=0.05) - assert np.isclose(summary1["a"][2], 1, atol=0.03) - -if __name__ == "__main__": - import sys - sys.path.append("..") - c = TestChain() - - c.test_pass_in_dataframe2() \ No newline at end of file + chain = Chain(samples=self.df, name=self.n, color_param="a") + color_data = chain.color_data + assert color_data is not None + assert np.allclose(self.df["a"].to_numpy(), color_data) diff --git a/tests/test_chainconsumer.py b/tests/test_chainconsumer.py index d55bc985..c718239c 100644 --- a/tests/test_chainconsumer.py +++ b/tests/test_chainconsumer.py @@ -1,215 +1,39 @@ import numpy as np -import pytest +import pandas as pd from scipy.stats import skewnorm -from chainconsumer import ChainConsumer +from chainconsumer import Chain, ChainConsumer -class TestChainConsumer(object): - np.random.seed(1) +class TestChainConsumer: + rng = np.random.default_rng(1) n = 2000000 - data = np.random.normal(loc=5.0, scale=1.5, size=n) - data2 = np.random.normal(loc=3, scale=1.0, size=n) + data = rng.normal(loc=5.0, scale=1.5, size=n) + data2 = rng.normal(loc=3, scale=1.0, size=n) data_combined = np.vstack((data, data2)).T data_skew = skewnorm.rvs(5, loc=1, scale=1.5, size=n) - def test_get_chain_name(self): - c = ChainConsumer() - c.add_chain(self.data, name="A") - assert c._get_chain_name(0) == "A" - - def test_get_names(self): - c = ChainConsumer() - c.add_chain(self.data, name="A") - c.add_chain(self.data, name="B") - assert c._all_names() == ["A", "B"] - - def test_get_chain_via_object(self): - c = ChainConsumer() - c.add_chain(self.data, name="A") - c.add_chain(self.data, name="B") - assert c._get_chain(c.chains[0])[0] == 0 - assert c._get_chain(c.chains[1])[0] == 1 - assert len(c._get_chain(c.chains[0])) == 1 - assert len(c._get_chain(c.chains[1])) == 1 - - def test_summary_bad_input1(self): - with pytest.raises(AssertionError): - ChainConsumer().add_chain(self.data).configure(summary_area=None) - - def test_summary_bad_input2(self): - with pytest.raises(AssertionError): - ChainConsumer().add_chain(self.data).configure(summary_area="Nope") - - def test_summary_bad_input3(self): - with pytest.raises(AssertionError): - ChainConsumer().add_chain(self.data).configure(summary_area=0) - - def test_summary_bad_input4(self): - with pytest.raises(AssertionError): - ChainConsumer().add_chain(self.data).configure(summary_area=1) - - def test_summary_bad_input5(self): - with pytest.raises(AssertionError): - ChainConsumer().add_chain(self.data).configure(summary_area=-0.2) - - def test_remove_last_chain(self): - tolerance = 5e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data) - consumer.add_chain(self.data * 2) - consumer.remove_chain() - consumer.configure() - summary = consumer.analysis.get_summary() - assert isinstance(summary, dict) - actual = np.array(list(summary.values())[0]) - expected = np.array([3.5, 5.0, 6.5]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) + chain1 = Chain(samples=pd.DataFrame({"a": data}), name="A") + chain2 = Chain(samples=pd.DataFrame({"b": data2}), name="B") + chain3 = Chain(samples=pd.DataFrame({"c": data_skew}), name="C") + chain4 = Chain(samples=pd.DataFrame(data_combined, columns=["a", "b"]), name="D") - def test_remove_first_chain(self): - tolerance = 5e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data * 2) - consumer.add_chain(self.data) - consumer.remove_chain(chain=0) - consumer.configure() - summary = consumer.analysis.get_summary() - assert isinstance(summary, dict) - actual = np.array(list(summary.values())[0]) - expected = np.array([3.5, 5.0, 6.5]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) + def get(self) -> ChainConsumer: + c = ChainConsumer().add_chain(self.chain1).add_chain(self.chain2).add_chain(self.chain3).add_chain(self.chain4) + return c - def test_remove_chain_by_name(self): - tolerance = 5e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data * 2, name="a") - consumer.add_chain(self.data, name="b") - consumer.remove_chain(chain="a") - consumer.configure() - summary = consumer.analysis.get_summary() - assert isinstance(summary, dict) - actual = np.array(list(summary.values())[0]) - expected = np.array([3.5, 5.0, 6.5]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) - - def test_remove_chain_recompute_params(self): - tolerance = 5e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data * 2, parameters=["p1"], name="a") - consumer.add_chain(self.data, parameters=["p2"], name="b") - consumer.remove_chain(chain="a") - consumer.configure() - summary = consumer.analysis.get_summary() - assert isinstance(summary, dict) - assert "p2" in summary - assert "p1" not in summary - actual = np.array(list(summary.values())[0]) - expected = np.array([3.5, 5.0, 6.5]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) - - def test_remove_multiple_chains(self): - tolerance = 5e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data * 2, parameters=["p1"], name="a") - consumer.add_chain(self.data, parameters=["p2"], name="b") - consumer.add_chain(self.data * 3, parameters=["p3"], name="c") - consumer.remove_chain(chain=["a", "c"]) - consumer.configure() - summary = consumer.analysis.get_summary() - assert isinstance(summary, dict) - assert "p2" in summary - assert "p1" not in summary - assert "p3" not in summary - actual = np.array(list(summary.values())[0]) - expected = np.array([3.5, 5.0, 6.5]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) - - def test_remove_multiple_chains2(self): - tolerance = 5e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data * 2, parameters=["p1"], name="a") - consumer.add_chain(self.data, parameters=["p2"], name="b") - consumer.add_chain(self.data * 3, parameters=["p3"], name="c") - consumer.remove_chain(chain=[0, 2]) - consumer.configure() - summary = consumer.analysis.get_summary() - assert isinstance(summary, dict) - assert "p2" in summary - assert "p1" not in summary - assert "p3" not in summary - actual = np.array(list(summary.values())[0]) - expected = np.array([3.5, 5.0, 6.5]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) - - def test_remove_multiple_chains3(self): - tolerance = 5e-2 - consumer = ChainConsumer() - consumer.add_chain(self.data * 2, parameters=["p1"], name="a") - consumer.add_chain(self.data, parameters=["p2"], name="b") - consumer.add_chain(self.data * 3, parameters=["p3"], name="c") - consumer.remove_chain(chain=["a", 2]) - consumer.configure() - summary = consumer.analysis.get_summary() - assert isinstance(summary, dict) - assert "p2" in summary - assert "p1" not in summary - assert "p3" not in summary - actual = np.array(list(summary.values())[0]) - expected = np.array([3.5, 5.0, 6.5]) - diff = np.abs(expected - actual) - assert np.all(diff < tolerance) - - def test_remove_multiple_chains_fails(self): - with pytest.raises(AssertionError): - ChainConsumer().add_chain(self.data).remove_chain(chain=[0,0]) - - def test_shade_alpha_algorithm1(self): - consumer = ChainConsumer() - consumer.add_chain(self.data) - consumer.configure() - alpha = consumer.chains[0].config["shade_alpha"] - assert alpha == 1.0 - - def test_shade_alpha_algorithm2(self): - consumer = ChainConsumer() - consumer.add_chain(self.data) - consumer.add_chain(self.data) - consumer.configure() - alpha0 = consumer.chains[0].config["shade_alpha"] - alpha1 = consumer.chains[0].config["shade_alpha"] - assert alpha0 == 1.0 / np.sqrt(2.0) - assert alpha1 == 1.0 / np.sqrt(2.0) + def test_get_chain_name(self): + assert self.get().get_chain(self.chain1.name) == self.chain1 - def test_shade_alpha_algorithm3(self): - consumer = ChainConsumer() - consumer.add_chain(self.data) - consumer.add_chain(self.data) - consumer.add_chain(self.data) - consumer.configure() - alphas = [c.config["shade_alpha"] for c in consumer.chains] - assert len(alphas) == 3 - assert alphas[0] == 1.0 / np.sqrt(3.0) - assert alphas[1] == 1.0 / np.sqrt(3.0) - assert alphas[2] == 1.0 / np.sqrt(3.0) + def test_get_chain_names(self): + assert self.get().get_names() == ["A", "B", "C", "D"] - def test_covariance(self): - mean = [0, 1] - cov = [[1, 1], [1, 2.5]] - c = ChainConsumer() - c.add_covariance(mean, cov) - mean_obs = np.mean(c.chains[0].chain, axis=0) - cov_obs = np.cov(c.chains[0].chain.T) - assert np.all(np.isclose(mean, mean_obs, atol=1e-2)) - assert np.all(np.isclose(cov, cov_obs, atol=1e-2)) + def test_remove_chain_str(self): + c = self.get() + c.remove_chain("A") + assert c.get_names() == ["B", "C", "D"] - def test_marker(self): - loc = [0, 1, 2] - c = ChainConsumer() - c.add_marker(loc) - assert np.all(np.equal(loc, c.chains[0].chain[0, :])) + def test_remove_chain_obj(self): + c = self.get() + c.remove_chain(self.chain1) + assert c.get_names() == ["B", "C", "D"] diff --git a/tests/test_colours.py b/tests/test_colours.py index ede4230d..203f2d22 100644 --- a/tests/test_colours.py +++ b/tests/test_colours.py @@ -1,44 +1,39 @@ import numpy as np import pytest -from chainconsumer.colors import Colors +from chainconsumer.color_finder import ALL_COLOURS, colors def test_colors_rgb2hex_1(): c = np.array([1, 1, 1, 1]) - colourmap = Colors() - assert colourmap.get_formatted([c])[0] == "#ffffff" + assert colors.get_formatted([c])[0] == "#ffffff" def test_colors_rgb2hex_2(): c = np.array([0, 0, 0.5, 1]) - colourmap = Colors() - assert colourmap.get_formatted([c])[0] == "#000080" + assert colors.get_formatted([c])[0] == "#000080" def test_colors_alias_works(): - colourmap = Colors() - assert colourmap.get_formatted(["b"])[0] == colourmap.color_map["blue"] + assert colors.format("b") in ALL_COLOURS["blue"] def test_colors_name_works(): - colourmap = Colors() - assert colourmap.get_formatted(["blue"])[0] == colourmap.color_map["blue"] + assert colors.format("blue") in ALL_COLOURS["blue"] def test_colors_error_on_garbage(): - colourmap = Colors() with pytest.raises(ValueError): - colourmap.get_formatted(["java"]) + colors.get_formatted(["java"]) def test_clamp1(): - assert Colors()._clamp(-10) == 0 + assert colors._clamp(-10) == 0 def test_clamp2(): - assert Colors()._clamp(10) == 10 + assert colors._clamp(10) == 10 def test_clamp3(): - assert Colors()._clamp(1000) == 255 + assert colors._clamp(1000) == 255 diff --git a/tests/test_comparisons.py b/tests/test_comparisons.py index 695c34bc..1ceae849 100644 --- a/tests/test_comparisons.py +++ b/tests/test_comparisons.py @@ -1,209 +1,198 @@ import numpy as np +import pandas as pd +import pytest from scipy.stats import norm -from chainconsumer import ChainConsumer +from chainconsumer import Chain, ChainConsumer -def test_aic_fail_no_posterior(): +@pytest.fixture +def cc_noposterior() -> ChainConsumer: d = norm.rvs(size=1000) - c = ChainConsumer() - c.add_chain(d, num_eff_data_points=1000, num_free_params=1) - aics = c.comparison.aic() - assert len(aics) == 1 - assert aics[0] is None + df = pd.DataFrame({"a": d}) + chain = Chain(samples=df, name="A") + return ChainConsumer().add_chain(chain) -def test_aic_fail_no_data_points(): +@pytest.fixture +def cc_no_effective_data_points() -> ChainConsumer: d = norm.rvs(size=1000) p = norm.logpdf(d) - c = ChainConsumer() - c.add_chain(d, posterior=p, num_free_params=1) - aics = c.comparison.aic() - assert len(aics) == 1 - assert aics[0] is None + df = pd.DataFrame({"a": d, "log_posterior": p}) + chain = Chain(samples=df, name="A") + return ChainConsumer().add_chain(chain) -def test_aic_fail_no_num_params(): +@pytest.fixture +def cc_no_free_params() -> ChainConsumer: d = norm.rvs(size=1000) p = norm.logpdf(d) - c = ChainConsumer() - c.add_chain(d, posterior=p, num_eff_data_points=1000) - aics = c.comparison.aic() - assert len(aics) == 1 - assert aics[0] is None + df = pd.DataFrame({"a": d, "log_posterior": p}) + chain = Chain(samples=df, name="A", num_eff_data_points=1000) + return ChainConsumer().add_chain(chain) -def test_aic_0(): +@pytest.fixture +def cc() -> ChainConsumer: d = norm.rvs(size=1000) p = norm.logpdf(d) - c = ChainConsumer() - c.add_chain(d, posterior=p, num_free_params=1, num_eff_data_points=1000) - aics = c.comparison.aic() + df = pd.DataFrame({"a": d, "log_posterior": p}) + chain = Chain(samples=df, name="A", num_eff_data_points=1000, num_free_params=1) + return ChainConsumer().add_chain(chain) + + +def test_aic_fail_no_posterior(cc_noposterior) -> None: + aics = cc_noposterior.comparison.aic() + assert len(aics) == 0 + + +def test_aic_fail_no_data_points(cc_no_effective_data_points) -> None: + aics = cc_no_effective_data_points.comparison.aic() + assert len(aics) == 0 + + +def test_aic_fail_no_num_params(cc_no_free_params) -> None: + aics = cc_no_free_params.comparison.aic() + assert len(aics) == 0 + + +def test_aic_0(cc) -> None: + aics = cc.comparison.aic() assert len(aics) == 1 - assert aics[0] == 0 + assert aics["A"] == 0 -def test_aic_posterior_dependence(): - d = norm.rvs(size=1000) - p = norm.logpdf(d) - p2 = norm.logpdf(d, scale=2) - c = ChainConsumer() - c.add_chain(d, posterior=p, num_free_params=1, num_eff_data_points=1000) - c.add_chain(d, posterior=p2, num_free_params=1, num_eff_data_points=1000) - aics = c.comparison.aic() +def test_aic_posterior_dependence(cc: ChainConsumer) -> None: + chain1 = cc.get_chain("A") + df = chain1.samples.assign(log_posterior=lambda x: norm.logpdf(x["a"], scale=2)) + cc.add_chain(Chain(samples=df, name="B", num_eff_data_points=1000, num_free_params=1)) + aics = cc.comparison.aic() assert len(aics) == 2 - assert aics[0] == 0 + assert aics["A"] == 0 expected = 2 * np.log(2) - assert np.isclose(aics[1], expected, atol=1e-3) + assert np.isclose(aics["B"], expected, atol=1e-3) -def test_aic_parameter_dependence(): - d = norm.rvs(size=1000) - p = norm.logpdf(d) - c = ChainConsumer() - c.add_chain(d, posterior=p, num_free_params=1, num_eff_data_points=1000) - c.add_chain(d, posterior=p, num_free_params=2, num_eff_data_points=1000) - aics = c.comparison.aic() +def test_aic_parameter_dependence(cc: ChainConsumer) -> None: + chain1 = cc.get_chain("A") + chain2 = chain1.model_copy() + chain2.num_free_params = 2 + chain2.name = "B" + cc.add_chain(chain2) + + aics = cc.comparison.aic() assert len(aics) == 2 - assert aics[0] == 0 + assert aics["A"] == 0 expected = 1 * 2 + (2.0 * 2 * 3 / (1000 - 2 - 1)) - (2.0 * 1 * 2 / (1000 - 1 - 1)) - assert np.isclose(aics[1], expected, atol=1e-3) + assert np.isclose(aics["B"], expected, atol=1e-3) -def test_aic_data_dependence(): - d = norm.rvs(size=1000) - p = norm.logpdf(d) - c = ChainConsumer() - c.add_chain(d, posterior=p, num_free_params=1, num_eff_data_points=1000) - c.add_chain(d, posterior=p, num_free_params=1, num_eff_data_points=500) - aics = c.comparison.aic() +def test_aic_data_dependence(cc: ChainConsumer) -> None: + chain1 = cc.get_chain("A") + chain2 = chain1.model_copy() + chain2.num_eff_data_points = 500 + chain2.name = "B" + cc.add_chain(chain2) + + aics = cc.comparison.aic() assert len(aics) == 2 - assert aics[0] == 0 + assert aics["A"] == 0 expected = (2.0 * 1 * 2 / (500 - 1 - 1)) - (2.0 * 1 * 2 / (1000 - 1 - 1)) - assert np.isclose(aics[1], expected, atol=1e-3) + assert np.isclose(aics["B"], expected, atol=1e-3) -def test_bic_fail_no_posterior(): - d = norm.rvs(size=1000) - c = ChainConsumer() - c.add_chain(d, num_eff_data_points=1000, num_free_params=1) - bics = c.comparison.bic() - assert len(bics) == 1 - assert bics[0] is None +def test_bic_fail_no_posterior(cc_noposterior: ChainConsumer) -> None: + bics = cc_noposterior.comparison.bic() + assert len(bics) == 0 -def test_bic_fail_no_data_points(): - d = norm.rvs(size=1000) - p = norm.logpdf(d) - c = ChainConsumer() - c.add_chain(d, posterior=p, num_free_params=1) - bics = c.comparison.bic() - assert len(bics) == 1 - assert bics[0] is None +def test_bic_fail_no_data_points(cc_no_effective_data_points: ChainConsumer) -> None: + bics = cc_no_effective_data_points.comparison.bic() + assert len(bics) == 0 -def test_bic_fail_no_num_params(): - d = norm.rvs(size=1000) - p = norm.logpdf(d) - c = ChainConsumer() - c.add_chain(d, posterior=p, num_eff_data_points=1000) - bics = c.comparison.bic() - assert len(bics) == 1 - assert bics[0] is None +def test_bic_fail_no_num_params(cc_no_free_params: ChainConsumer) -> None: + bics = cc_no_free_params.comparison.bic() + assert len(bics) == 0 -def test_bic_0(): - d = norm.rvs(size=1000) - p = norm.logpdf(d) - c = ChainConsumer() - c.add_chain(d, posterior=p, num_free_params=1, num_eff_data_points=1000) - bics = c.comparison.bic() +def test_bic_0(cc: ChainConsumer) -> None: + bics = cc.comparison.bic() assert len(bics) == 1 - assert bics[0] == 0 + assert bics["A"] == 0 -def test_bic_posterior_dependence(): - d = norm.rvs(size=1000) - p = norm.logpdf(d) - p2 = norm.logpdf(d, scale=2) - c = ChainConsumer() - c.add_chain(d, posterior=p, num_free_params=1, num_eff_data_points=1000) - c.add_chain(d, posterior=p2, num_free_params=1, num_eff_data_points=1000) - bics = c.comparison.bic() +def test_bic_posterior_dependence(cc: ChainConsumer) -> None: + chain1 = cc.get_chain("A") + df = chain1.samples.assign(log_posterior=lambda x: norm.logpdf(x["a"], scale=2)) + cc.add_chain(Chain(samples=df, name="B", num_eff_data_points=1000, num_free_params=1)) + bics = cc.comparison.bic() assert len(bics) == 2 - assert bics[0] == 0 + assert bics["A"] == 0 expected = 2 * np.log(2) - assert np.isclose(bics[1], expected, atol=1e-3) + assert np.isclose(bics["B"], expected, atol=1e-3) -def test_bic_parameter_dependence(): - d = norm.rvs(size=1000) - p = norm.logpdf(d) - c = ChainConsumer() - c.add_chain(d, posterior=p, num_free_params=1, num_eff_data_points=1000) - c.add_chain(d, posterior=p, num_free_params=2, num_eff_data_points=1000) - bics = c.comparison.bic() +def test_bic_parameter_dependence(cc: ChainConsumer) -> None: + chain1 = cc.get_chain("A") + chain2 = chain1.model_copy() + chain2.num_free_params = 2 + chain2.name = "B" + cc.add_chain(chain2) + bics = cc.comparison.bic() assert len(bics) == 2 - assert bics[0] == 0 + assert bics["A"] == 0 expected = np.log(1000) - assert np.isclose(bics[1], expected, atol=1e-3) + assert np.isclose(bics["B"], expected, atol=1e-3) -def test_bic_data_dependence(): - d = norm.rvs(size=1000) - p = norm.logpdf(d) - c = ChainConsumer() - c.add_chain(d, posterior=p, num_free_params=1, num_eff_data_points=1000) - c.add_chain(d, posterior=p, num_free_params=1, num_eff_data_points=500) - bics = c.comparison.bic() +def test_bic_data_dependence(cc: ChainConsumer) -> None: + chain1 = cc.get_chain("A") + chain2 = chain1.model_copy() + chain2.num_eff_data_points = 500 + chain2.name = "B" + cc.add_chain(chain2) + bics = cc.comparison.bic() assert len(bics) == 2 - assert bics[1] == 0 + assert bics["B"] == 0 expected = np.log(1000) - np.log(500) - assert np.isclose(bics[0], expected, atol=1e-3) + assert np.isclose(bics["A"], expected, atol=1e-3) -def test_bic_data_dependence2(): - d = norm.rvs(size=1000) - p = norm.logpdf(d) - c = ChainConsumer() - c.add_chain(d, posterior=p, num_free_params=2, num_eff_data_points=1000) - c.add_chain(d, posterior=p, num_free_params=3, num_eff_data_points=500) - bics = c.comparison.bic() +def test_bic_data_dependence2(cc: ChainConsumer) -> None: + chain1 = cc.get_chain("A") + chain1.num_free_params = 2 + chain2 = chain1.model_copy() + chain2.num_eff_data_points = 500 + chain2.num_free_params = 3 + chain2.name = "B" + cc.add_chain(chain2) + + bics = cc.comparison.bic() assert len(bics) == 2 - assert bics[0] == 0 + assert bics["A"] == 0 expected = 3 * np.log(500) - 2 * np.log(1000) - assert np.isclose(bics[1], expected, atol=1e-3) + assert np.isclose(bics["B"], expected, atol=1e-3) -def test_dic_fail_no_posterior(): - d = norm.rvs(size=1000) - c = ChainConsumer() - c.add_chain(d, num_eff_data_points=1000, num_free_params=1) - dics = c.comparison.dic() - assert len(dics) == 1 - assert dics[0] is None +def test_dic_fail_no_posterior(cc_noposterior: ChainConsumer) -> None: + dics = cc_noposterior.comparison.dic() + assert len(dics) == 0 -def test_dic_0(): - d = norm.rvs(size=1000) - p = norm.logpdf(d) - c = ChainConsumer() - c.add_chain(d, posterior=p) - dics = c.comparison.dic() +def test_dic_0(cc: ChainConsumer) -> None: + dics = cc.comparison.dic() assert len(dics) == 1 - assert dics[0] == 0 + assert dics["A"] == 0 -def test_dic_posterior_dependence(): - d = norm.rvs(size=1000000) - p = norm.logpdf(d) - p2 = norm.logpdf(d, scale=2) - c = ChainConsumer() - c.add_chain(d, posterior=p) - c.add_chain(d, posterior=p2) - bics = c.comparison.dic() +def test_dic_posterior_dependence(cc: ChainConsumer) -> None: + chain1 = cc.get_chain("A") + df = chain1.samples.assign(log_posterior=lambda x: norm.logpdf(x["a"], scale=2)) + cc.add_chain(Chain(samples=df, name="B", num_eff_data_points=1000, num_free_params=1)) + bics = cc.comparison.bic() assert len(bics) == 2 - assert bics[1] == 0 - dic1 = 2 * np.mean(-2 * p) + 2 * norm.logpdf(0) - dic2 = 2 * np.mean(-2 * p2) + 2 * norm.logpdf(0, scale=2) - assert np.isclose(bics[0], dic1 - dic2, atol=1e-3) \ No newline at end of file + assert bics["A"] == 0 + dic1 = 2 * np.mean(-2 * chain1.log_posterior) + 2 * norm.logpdf(0) # type: ignore + dic2 = 2 * np.mean(-2 * chain1.log_posterior) + 2 * norm.logpdf(0, scale=2) # type: ignore + assert np.isclose(bics["B"], dic1 - dic2, atol=1e-3) diff --git a/tests/test_diagnostic.py b/tests/test_diagnostic.py index b253a1ad..3e36d912 100644 --- a/tests/test_diagnostic.py +++ b/tests/test_diagnostic.py @@ -1,128 +1,102 @@ import numpy as np +import pandas as pd import pytest -from chainconsumer import ChainConsumer +from chainconsumer import Chain, ChainConsumer -def test_gelman_rubin_index(): - data = np.vstack((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))).T - consumer = ChainConsumer() - consumer.add_chain(data, walkers=4) - assert consumer.diagnostic.gelman_rubin(chain=0) +@pytest.fixture +def rng(): + return np.random.default_rng(seed=0) + + +@pytest.fixture +def good_chain(rng) -> Chain: + data = np.vstack((rng.normal(loc=0.0, size=100000), rng.normal(loc=1.0, size=100000))).T + chain = Chain(samples=pd.DataFrame(data, columns=["a", "b"]), name="A", walkers=4) + return chain -def test_gelman_rubin_index_not_converged(): - data = np.vstack((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))).T +@pytest.fixture +def bad_chain(rng) -> Chain: + data = np.vstack((rng.normal(loc=0.0, size=100000), rng.normal(loc=1.0, size=100000))).T data[80000:, :] *= 2 data[80000:, :] += 1 - consumer = ChainConsumer() + chain = Chain(samples=pd.DataFrame(data, columns=["a", "b"]), name="A", walkers=4) + return chain - consumer.add_chain(data, walkers=4) - assert not consumer.diagnostic.gelman_rubin(chain=0) +@pytest.fixture +def good_cc(good_chain: Chain) -> ChainConsumer: + return ChainConsumer().add_chain(good_chain) -def test_gelman_rubin_index_not_converged(): - data = np.vstack((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))).T - data[:, 0] += np.linspace(0, 10, 100000) - consumer = ChainConsumer() - consumer.add_chain(data, walkers=8) - assert not consumer.diagnostic.gelman_rubin(chain=0) +@pytest.fixture +def good_cc2(good_chain: Chain) -> ChainConsumer: + c2 = good_chain.model_copy() + c2.name = "B" + return ChainConsumer().add_chain(good_chain).add_chain(c2) -def test_gelman_rubin_index_fails(): - data = np.vstack((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))).T - consumer = ChainConsumer() - consumer.add_chain(data, walkers=4) - with pytest.raises(AssertionError): - consumer.diagnostic.gelman_rubin(chain=10) +@pytest.fixture +def bad_cc(bad_chain: Chain) -> ChainConsumer: + return ChainConsumer().add_chain(bad_chain) -def test_gelman_rubin_name(): - data = np.vstack((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))).T - consumer = ChainConsumer() - consumer.add_chain(data, walkers=4, name="testchain") - assert consumer.diagnostic.gelman_rubin(chain="testchain") +def test_gelman_rubin_index(good_cc: ChainConsumer) -> None: + assert good_cc.diagnostic.gelman_rubin() -def test_gelman_rubin_name_fails(): - data = np.vstack((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))).T - consumer = ChainConsumer() - consumer.add_chain(data, walkers=4, name="testchain") - with pytest.raises(AssertionError): - consumer.diagnostic.gelman_rubin(chain="testchain2") +def test_gelman_rubin_index2(good_cc2: ChainConsumer) -> None: + res = good_cc2.diagnostic.gelman_rubin() + assert res + assert res.passed + assert "A" in res.results + assert res.results["A"] + assert "B" in res.results + assert res.results["B"] -def test_gelman_rubin_unknown_fails(): - data = np.vstack((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))).T - consumer = ChainConsumer() - consumer.add_chain(data, walkers=4, name="testchain") - with pytest.raises(ValueError): - consumer.diagnostic.gelman_rubin(chain=np.pi) +def test_gelman_rubin_index_not_converged(bad_cc: ChainConsumer) -> None: + assert not bad_cc.diagnostic.gelman_rubin() -def test_gelman_rubin_default(): - data = np.vstack((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))).T +def test_gelman_rubin_index_not_converged2(rng) -> None: + data = np.vstack((rng.normal(loc=0.0, size=100000), rng.normal(loc=1.0, size=100000))).T + data[:, 0] += np.linspace(0, 10, 100000) consumer = ChainConsumer() - consumer.add_chain(data, walkers=4, name="c1") - consumer.add_chain(data, walkers=4, name="c2") - consumer.add_chain(data, walkers=4, name="c3") - assert consumer.diagnostic.gelman_rubin() - -def test_gelman_rubin_default_not_converge(): - data = np.vstack((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))).T - consumer = ChainConsumer() - consumer.add_chain(data, walkers=4, name="c1") - consumer.add_chain(data, walkers=4, name="c2") - data2 = data.copy() - data2[:, 0] += np.linspace(-5, 5, 100000) - consumer.add_chain(data2, walkers=4, name="c3") - assert not consumer.diagnostic.gelman_rubin() + consumer.add_chain(Chain(samples=pd.DataFrame(data, columns=["A", "B"]), name="B", walkers=8)) + res = consumer.diagnostic.gelman_rubin() + assert not res + assert not res.passed + assert "B" in res.results + assert not res.results["B"] -def test_geweke_index(): - data = np.vstack((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))).T - consumer = ChainConsumer() - consumer.add_chain(data, walkers=20, name="c1") - assert consumer.diagnostic.geweke(chain=0) +def test_geweke_index(good_cc: ChainConsumer) -> None: + assert good_cc.diagnostic.geweke() -def test_geweke_index_failed(): - data = np.vstack((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))).T - consumer = ChainConsumer() - data[98000:, :] += 0.5 - consumer.add_chain(data, walkers=20, name="c1") - assert not consumer.diagnostic.geweke(chain=0) +def test_geweke_index_failed(bad_cc: ChainConsumer) -> None: + assert not bad_cc.diagnostic.geweke() -def test_geweke_default(): - np.random.seed(0) - data = np.vstack((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))).T - consumer = ChainConsumer() - consumer.add_chain(data, walkers=20, name="c1") - consumer.add_chain(data, walkers=20, name="c2") - assert consumer.diagnostic.geweke(chain=0) +def test_geweke_default(good_cc2: ChainConsumer) -> None: + res = good_cc2.diagnostic.geweke() + assert res + assert res.passed + assert "A" in res.results + assert res.results["A"] + assert "B" in res.results + assert res.results["B"] -def test_geweke_default_failed(): - data = np.vstack((np.random.normal(loc=0.0, size=100000), - np.random.normal(loc=1.0, size=100000))).T +def test_geweke_default_failed(rng: np.random.Generator) -> None: + data = np.vstack((rng.normal(loc=0.0, size=100000), rng.normal(loc=1.0, size=100000))).T consumer = ChainConsumer() - consumer.add_chain(data, walkers=20, name="c1") + consumer.add_chain(Chain(samples=pd.DataFrame(data, columns=["a", "b"]), walkers=20, name="c1")) data2 = data.copy() data2[98000:, :] += 0.3 - consumer.add_chain(data2, walkers=20, name="c2") - assert not consumer.diagnostic.geweke() \ No newline at end of file + consumer.add_chain(Chain(samples=pd.DataFrame(data2, columns=["a", "b"]), walkers=20, name="c2")) + assert not consumer.diagnostic.geweke() diff --git a/tests/test_helpers.py b/tests/test_helpers.py index b6a316cd..d967e61f 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1,11 +1,17 @@ import numpy as np +import pytest from scipy.stats import norm from chainconsumer.helpers import get_extents -def test_extents(): - xs = np.random.normal(size=1000000) +@pytest.fixture +def rng(): + return np.random.default_rng(0) + + +def test_extents(rng): + xs = rng.normal(size=1000000) weights = np.ones(xs.shape) low, high = get_extents(xs, weights) threshold = 0.5 @@ -13,8 +19,8 @@ def test_extents(): assert np.abs(high - 4) < threshold -def test_extents_weighted(): - xs = np.random.uniform(low=-4, high=4, size=1000000) +def test_extents_weighted(rng): + xs = rng.uniform(low=-4, high=4, size=1000000) weights = norm.pdf(xs) low, high = get_extents(xs, weights) threshold = 0.5 @@ -22,8 +28,8 @@ def test_extents_weighted(): assert np.abs(high - 4) < threshold -def test_extents_summary(): - xs = np.random.normal(size=1000000) +def test_extents_summary(rng): + xs = rng.normal(size=1000000) low, high = get_extents(xs, np.ones(xs.shape), plot=True, wide_extents=False) threshold = 0.1 assert np.abs(low + 1.644855) < threshold diff --git a/tests/test_kde.py b/tests/test_kde.py index 3dd219fe..3a14ba36 100644 --- a/tests/test_kde.py +++ b/tests/test_kde.py @@ -1,61 +1,63 @@ import numpy as np +import pytest from scipy.interpolate import interp1d from scipy.stats import norm from chainconsumer.kde import MegKDE -def test_megkde_1d_basic(): +@pytest.fixture +def rng() -> np.random.Generator: + return np.random.default_rng(0) + + +def test_megkde_1d_basic(rng): # Draw from normal, fit KDE, see if sampling from kde's pdf recovers norm - np.random.seed(0) - data = np.random.normal(loc=0, scale=1.0, size=2000) + data = rng.normal(loc=0, scale=1.0, size=2000) xs = np.linspace(-3, 3, 100) ys = MegKDE(data).evaluate(xs) cs = ys.cumsum() cs /= cs[-1] cs[0] = 0 - samps = interp1d(cs, xs)(np.random.uniform(size=10000)) + samps = interp1d(cs, xs)(rng.uniform(size=10000)) mu, std = norm.fit(samps) assert np.isclose(mu, 0, atol=0.1) assert np.isclose(std, 1.0, atol=0.1) -def test_megkde_1d_uniform_weight(): +def test_megkde_1d_uniform_weight(rng): # Draw from normal, fit KDE, see if sampling from kde's pdf recovers norm - np.random.seed(0) - data = np.random.normal(loc=0, scale=1.0, size=2000) + data = rng.normal(loc=0, scale=1.0, size=2000) xs = np.linspace(-3, 3, 100) ys = MegKDE(data, weights=np.ones(2000)).evaluate(xs) cs = ys.cumsum() cs /= cs[-1] cs[0] = 0 - samps = interp1d(cs, xs)(np.random.uniform(size=10000)) + samps = interp1d(cs, xs)(rng.uniform(size=10000)) mu, std = norm.fit(samps) assert np.isclose(mu, 0, atol=0.1) assert np.isclose(std, 1.0, atol=0.1) -def test_megkde_1d_changing_weights(): +def test_megkde_1d_changing_weights(rng): # Draw from normal, fit KDE, see if sampling from kde's pdf recovers norm - np.random.seed(0) xs = np.linspace(-3, 3, 1000) weights = norm.pdf(xs) ys = MegKDE(xs, weights=weights).evaluate(xs) cs = ys.cumsum() cs /= cs[-1] cs[0] = 0 - samps = interp1d(cs, xs)(np.random.uniform(size=10000)) + samps = interp1d(cs, xs)(rng.uniform(size=10000)) mu, std = norm.fit(samps) assert np.isclose(mu, 0, atol=0.1) assert np.isclose(std, 1.0, atol=0.1) -def test_megkde_2d_basic(): +def test_megkde_2d_basic(rng): # Draw from normal, fit KDE, see if sampling from kde's pdf recovers norm - np.random.seed(1) - data = np.random.multivariate_normal([0, 1], [[1.0, 0.], [0., 0.75 ** 2]], size=10000) + data = rng.multivariate_normal([0, 1], [[1.0, 0.0], [0.0, 0.75**2]], size=10000) xs, ys = np.linspace(-4, 4, 50), np.linspace(-4, 4, 50) - xx, yy = np.meshgrid(xs, ys, indexing='ij') + xx, yy = np.meshgrid(xs, ys, indexing="ij") samps = np.vstack((xx.flatten(), yy.flatten())).T zs = MegKDE(data).evaluate(samps).reshape(xx.shape) zs_x = zs.sum(axis=1) @@ -66,8 +68,8 @@ def test_megkde_2d_basic(): cs_y = zs_y.cumsum() cs_y /= cs_y[-1] cs_y[0] = 0 - samps_x = interp1d(cs_x, xs)(np.random.uniform(size=10000)) - samps_y = interp1d(cs_y, ys)(np.random.uniform(size=10000)) + samps_x = interp1d(cs_x, xs)(rng.uniform(size=10000)) + samps_y = interp1d(cs_y, ys)(rng.uniform(size=10000)) mu_x, std_x = norm.fit(samps_x) mu_y, std_y = norm.fit(samps_y) assert np.isclose(mu_x, 0, atol=0.1) diff --git a/tests/test_plotter.py b/tests/test_plotter.py index eab925f8..787b8144 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -1,73 +1,71 @@ import numpy as np +import pandas as pd from scipy.stats import norm -from chainconsumer import ChainConsumer +from chainconsumer import Chain, ChainConsumer -class TestChain(object): - np.random.seed(1) +class TestChain: + rng = np.random.default_rng(1) n = 2000000 - data = np.random.normal(loc=5.0, scale=1.5, size=n) - data2 = np.random.normal(loc=3, scale=1.0, size=n) + data = rng.normal(loc=5.0, scale=1.5, size=n) + data2 = rng.normal(loc=3, scale=1.0, size=n) + + chain1 = Chain(samples=pd.DataFrame(data, columns=["x"]), name="A") + chain2 = Chain(samples=pd.DataFrame(data2, columns=["x"]), name="B") def test_plotter_extents1(self): c = ChainConsumer() - c.add_chain(self.data, parameters=["x"]) - c.configure() - minv, maxv = c.plotter._get_parameter_extents("x", c.chains) + c.add_chain(self.chain1) + minv, maxv = c.plotter._get_parameter_extents("x", list(c._chains.values())) assert np.isclose(minv, (5.0 - 1.5 * 3.7), atol=0.2) assert np.isclose(maxv, (5.0 + 1.5 * 3.7), atol=0.2) def test_plotter_extents2(self): c = ChainConsumer() - c.add_chain(self.data, parameters=["x"]) - c.add_chain(self.data + 5, parameters=["y"]) - c.configure() - minv, maxv = c.plotter._get_parameter_extents("x", c.chains) + c.add_chain(self.chain1) + chain2 = self.chain2.model_copy() + chain2.samples["x"] += 5 + chain2.samples = chain2.samples.rename(columns={"x": "y"}) + c.add_chain(chain2) + minv, maxv = c.plotter._get_parameter_extents("x", list(c._chains.values())) assert np.isclose(minv, (5.0 - 1.5 * 3.7), atol=0.2) assert np.isclose(maxv, (5.0 + 1.5 * 3.7), atol=0.2) def test_plotter_extents3(self): c = ChainConsumer() - c.add_chain(self.data, parameters=["x"]) - c.add_chain(self.data + 5, parameters=["x"]) - c.configure() - minv, maxv = c.plotter._get_parameter_extents("x", c.chains) + c.add_chain(self.chain1) + chain2 = self.chain1.model_copy(deep=True) + chain2.samples["x"] += 5 + chain2.name = "C" + c.add_chain(chain2) + minv, maxv = c.plotter._get_parameter_extents("x", list(c._chains.values())) assert np.isclose(minv, (5.0 - 1.5 * 3.7), atol=0.2) assert np.isclose(maxv, (10.0 + 1.5 * 3.7), atol=0.2) - def test_plotter_extents4(self): - c = ChainConsumer() - c.add_chain(self.data, parameters=["x"]) - c.add_chain(self.data + 5, parameters=["y"]) - c.configure() - minv, maxv = c.plotter._get_parameter_extents("x", c.chains[:1]) - assert np.isclose(minv, (5.0 - 1.5 * 3.7), atol=0.2) - assert np.isclose(maxv, (5.0 + 1.5 * 3.7), atol=0.2) - def test_plotter_extents5(self): x, y = np.linspace(-3, 3, 200), np.linspace(-5, 5, 200) - xx, yy = np.meshgrid(x, y, indexing='ij') + xx, yy = np.meshgrid(x, y, indexing="ij") xs, ys = xx.flatten(), yy.flatten() chain = np.vstack((xs, ys)).T pdf = (1 / (2 * np.pi)) * np.exp(-0.5 * (xs * xs + ys * ys / 4)) + df = pd.DataFrame(chain, columns=["x", "y"]) + df["weight"] = pdf c = ChainConsumer() - c.add_chain(chain, parameters=['x', 'y'], weights=pdf, grid=True) - c.configure() - minv, maxv = c.plotter._get_parameter_extents("x", c.chains) + c.add_chain(Chain(samples=df, grid=True, name="grid")) + minv, maxv = c.plotter._get_parameter_extents("x", list(c._chains.values())) assert np.isclose(minv, -3, atol=0.001) assert np.isclose(maxv, 3, atol=0.001) def test_plotter_extents6(self): c = ChainConsumer() for mid in np.linspace(-1, 1, 3): - data = np.random.normal(loc=0, size=1000) + data = self.rng.normal(loc=0, size=1000) posterior = norm.logpdf(data) data += mid - c.add_chain(data, parameters=['x'], posterior=posterior, plot_point=True, plot_contour=False) + df = pd.DataFrame(data, columns=["x"]).assign(log_posterior=posterior) + c.add_chain(Chain(samples=df, plot_point=True, plot_contour=False, name=f"point only {mid}")) - c.configure() - minv, maxv = c.plotter._get_parameter_extents("x", c.chains) + minv, maxv = c.plotter._get_parameter_extents("x", list(c._chains.values())) assert np.isclose(minv, -1, atol=0.01) assert np.isclose(maxv, 1, atol=0.01) -