diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..67e4a5e --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behaviour that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behaviour by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behaviour and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behaviour. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviours that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behaviour may be +reported by contacting the project team at williamjameshandley@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..06e9169 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,24 @@ +Thank you for considering contributing to unimpeded. + +If you have a new feature/bug report, make sure you create an [issue](https://github.com/handley-lab/unimpeded/issues), and consult existing ones first, in case your suggestion is already being addressed. + +If you want to go ahead and create the feature yourself, you should fork the repository to you own github account and create a new branch with an appropriate name. Commit any code modifications to that branch, push to GitHub, and then create a pull request via your forked repository. More detail can be found [here](https://gist.github.com/Chaser324/ce0505fbed06b947d962). + +Note that a [code of conduct](https://github.com/handley-lab/unimpeded/blob/master/CODE_OF_CONDUCT.md) applies to all spaces managed by the unimpeded project, including issues and pull requests. + +## Contributing - `pre-commit` + +unimpeded uses flake8 and pydocstyle to maintain a consistent style. To speed up linting, contributors can optionally use pre-commit to ensure their commits comply. + +First, ensure that pre-commit, flake8 and pydocstyle are installed: +``` +pip install pre-commit flake8 pydocstyle +``` +Then install the pre-commit to the .git folder: +``` +pre-commit install +``` +This will check changed files, and abort the commit if they do not comply. To uninstall, run: +``` +pre-commit uninstall +``` diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..f77f578 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,21 @@ +--- +name: Bug report +about: Create a report to help us improve +labels: + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behaviour. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..751cb5d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,18 @@ +--- +name: Feature request +about: Suggest an idea for this project +labels: + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..a3ee333 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +# Description + +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. + +Fixes # (issue) + +# Checklist: + +- [ ] I have performed a self-review of my own code +- [ ] My code is PEP8 compliant (`flake8 unimpeded tests`) +- [ ] My code contains compliant docstrings (`pydocstyle --convention=numpy unimpeded`) +- [ ] New and existing unit tests pass locally with my changes (`python -m pytest`) +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] I have appropriately incremented the [semantic version number](https://semver.org/) in both README.rst and unimpeded/_version.py diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml new file mode 100644 index 0000000..e894344 --- /dev/null +++ b/.github/workflows/CI.yaml @@ -0,0 +1,165 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + schedule: + - cron: "0 0 * * *" + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + - name: Upgrade pip and install linters + run: | + python -m pip install --upgrade pip + python -m pip install flake8 pydocstyle packaging + - name: flake8 PEP compliance checks + run: python -m flake8 anesthetic tests + - name: pydocstyle documentation style checks + run: python -m pydocstyle --convention=numpy anesthetic + - name: Count missing test fixtures + run: | + if [ $( grep -L 'close_figures_on_teardown' + $(grep -l 'matplotlib' tests/test*.py) | wc -w ) -ne 0] + then + missing_tests=$( grep -L 'close_figures_on_teardown' + $(grep -l 'matplotlib' tests/test*.py) ) + echo "$missing_tests are missing the + close_figures_on_teardown fixture." + exit 1 + fi + + sphinx: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.8' + - name: Upgrade pip and install doc requirements + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[all,docs]" + - name: build documentation + run: | + cd docs + make clean + make html SPHINXOPTS="-W --keep-going -n" + + pip: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + python-version: ['3.8', '3.9', '3.10'] + extras: [true, false] + include: + - os: macos-latest + python-version: '3.10' + extras: true + - os: windows-latest + python-version: '3.10' + extras: true + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[test]" + + - name: Install extra dependencies + if: ${{ matrix.extras }} + run: | + python -m pip install -e ".[all]" + + - name: Test with pytest + run: python -m pytest --cov=anesthetic tests + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + + conda: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + python-version: ['3.8', '3.9', '3.10'] + include: + - os: macos-latest + python-version: '3.10' + - os: windows-latest + python-version: '3.10' + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: conda-incubator/setup-miniconda@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + shell: bash -l {0} + run: | + conda config --append channels conda-forge + conda install pytest pytest-cov + conda install scipy numpy 'matplotlib>=3.6.1' 'pandas>=2.0.0' + + - name: Test with pytest + shell: bash -l {0} + run: python -m pytest --cov=anesthetic tests + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + + test-build-n-publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.8 + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Install pypa/build + run: python -m pip install build --user + - name: Build a binary wheel and a source tarball + run: python -m build --sdist --wheel --outdir dist/ + - name: Publish to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + continue-on-error: true + with: + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + skip_existing: true + + minimum-dependencies: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.8 + uses: actions/setup-python@v4 + with: + python-version: 3.8 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install tomli + python -m pip install $(./bin/min_dependencies.py) + python -m pip install -e ".[test]" + + - name: Test with pytest + run: python -m pytest --cov=anesthetic tests + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..3dc572e --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,85 @@ +name: Build and push to PyPI on merging to master +on: + push: + branches: + - master +jobs: + git-tag-and-release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Get version number + run: | + VERSION="$(grep ':Version:' README.rst | awk '{print $2}')" + echo "DIST_VERSION=v${VERSION}" >> $GITHUB_ENV + - name: Create Tag + uses: actions/github-script@v6 + with: + script: | + const {DIST_VERSION} = process.env + github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/tags/${DIST_VERSION}`, + sha: context.sha + }) + - name: Create GitHub Release + uses: ncipollo/release-action@v1 + with: + name: "${{ env.DIST_VERSION }}" + tag: "${{ env.DIST_VERSION }}" + generateReleaseNotes: true + token: ${{ secrets.GITHUB_TOKEN }} + + pypi-release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.8 + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Install pypa/build + run: python -m pip install build --user + - name: Build a binary wheel and a source tarball + run: python -m build --sdist --wheel --outdir dist/ + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + + aur-release: + needs: pypi-release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.8 + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install tomli + - name: Generate PKGBUILD + run: python ./bin/gen_PKGBUILD.py > ./PKGBUILD + - name: Get version number + run: | + VERSION="$(cat ./PKGBUILD | grep pkgver= | awk -F= '{print $2}')" + echo "DIST_VERSION=${VERSION}" >> $GITHUB_ENV + - name: Wait for PyPi + uses: nev7n/wait_for_response@v1 + with: + url: "https://files.pythonhosted.org/packages/source/a/unimpeded/unimpeded-${{ env.DIST_VERSION }}.tar.gz" + responseCode: 200 + timeout: 600000 + interval: 10000 + - name: Publish AUR package + uses: KSXGitHub/github-actions-deploy-aur@v2.7.0 + with: + pkgname: python-unimpeded + pkgbuild: ./PKGBUILD + updpkgsums: True + commit_username: ${{ secrets.AUR_USERNAME }} + commit_email: ${{ secrets.AUR_EMAIL }} + ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} diff --git a/.github/workflows/version.yaml b/.github/workflows/version.yaml new file mode 100644 index 0000000..3afc571 --- /dev/null +++ b/.github/workflows/version.yaml @@ -0,0 +1,18 @@ +name: PR checks + +on: + pull_request: + branches: [master] + +jobs: + version-is-unit-incremented: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + - name: Upgrade pip and install linters + run: | + python -m pip install --upgrade pip + python -m pip install packaging + - name: Check version number + run: python ./bin/check_version.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..1f1aa1f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +repos: + - repo: local + hooks: + - id: flake8 + name: flake8 + entry: python -m flake8 + language: system + types: [python] + files: "anesthetic/|tests/" + - id: pydocstyle + name: pydocstyle + entry: python -m pydocstyle --convention=numpy + language: system + types: [python] + files: "anesthetic/" diff --git a/README.rst b/README.rst index 268e590..8dfc07a 100644 --- a/README.rst +++ b/README.rst @@ -13,11 +13,11 @@ unimpeded: Universal model comparison & parameter estimation over diverse datase .. image:: https://codecov.io/gh/handley-lab/unimpeded/branch/master/graph/badge.svg :target: https://codecov.io/gh/handley-lab/unimpeded :alt: Test Coverage Status -.. image:: https://readthedocs.org/projects/anesthetic/badge/?version=latest - :target: https://anesthetic.readthedocs.io/en/latest/?badge=latest +.. image:: https://readthedocs.org/projects/unimpeded/badge/?version=latest + :target: https://unimpeded.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status -.. image:: https://badge.fury.io/py/anesthetic.svg - :target: https://badge.fury.io/py/anesthetic +.. image:: https://badge.fury.io/py/unimpeded.svg + :target: https://badge.fury.io/py/unimpeded :alt: PyPi location .. image:: https://zenodo.org/badge/175663535.svg :target: https://zenodo.org/badge/latestdoi/175663535 @@ -38,6 +38,8 @@ It provides mcmc and nested sampling chains, allowing parameter estimation, mode Current functionality includes: +UNDER CONSTRUCTION + Features -------- @@ -56,8 +58,8 @@ or via the setup.py .. code:: bash git clone https://github.com/handley-lab/unimpeded - cd anesthetic - python setup.py install --user + cd unimpeded + python -m pip install . You can check that things are working by running the test suite: diff --git a/bin/check_version.py b/bin/check_version.py new file mode 100755 index 0000000..6e18166 --- /dev/null +++ b/bin/check_version.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +import sys +import subprocess +from packaging import version +from utils import unit_incremented +vfile = "unimpeded/_version.py" +README = "README.rst" + + +def run(*args): + return subprocess.run(args, text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE).stdout + + +current_version = run("cat", vfile) +current_version = current_version.split("=")[-1].strip().strip("'") + +run("git", "fetch", "origin", "master") +previous_version = run("git", "show", "remotes/origin/master:" + vfile) +previous_version = previous_version.split("=")[-1].strip().strip("'") + +readme_version = run("grep", ":Version:", README) +readme_version = readme_version.split(":")[-1].strip() + +if version.parse(current_version) != version.parse(readme_version): + sys.stderr.write("Version mismatch: {} != {}".format(vfile, README)) + sys.exit(1) + +elif not unit_incremented(current_version, previous_version): + sys.stderr.write(("Version must be incremented by one:\n" + "HEAD: {},\n" + "master: {}.\n" + ).format(current_version, previous_version)) + sys.exit(1) diff --git a/bin/dependencies.py b/bin/dependencies.py new file mode 100755 index 0000000..9df0379 --- /dev/null +++ b/bin/dependencies.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +import tomli + +with open("pyproject.toml", 'rb') as f: + pyproject = tomli.load(f) + +print(' '.join(pyproject["project"]["dependencies"])) diff --git a/bin/gen_PKGBUILD.py b/bin/gen_PKGBUILD.py new file mode 100644 index 0000000..8ef8826 --- /dev/null +++ b/bin/gen_PKGBUILD.py @@ -0,0 +1,44 @@ +import tomli + +with open("pyproject.toml", 'rb') as f: + pyproject = tomli.load(f) + +description = pyproject["project"]["description"] +version = open('unimpeded/_version.py','r').readlines()[0].split("=")[1].strip().strip("'") +url = pyproject["project"]["urls"]["Homepage"] +pyproject["project"]["dependencies"] +rel=1 + + +PKGBUILD = """# Maintainer: Will Handley (aur.archlinux.org/account/wjhandley) +pkgname=python-unimpeded +_name=${pkgname#python-} +pkgver=%s +pkgrel=%s +pkgdesc="%s" +arch=(any) +url="%s" +license=(MIT) +groups=() +depends=(python-numpy python-matplotlib python-scipy python-pandas) +makedepends=(python-build python-installer) +provides=(unimpeded) +conflicts=() +replaces=() +backup=() +options=(!emptydirs) +install= +source=("https://files.pythonhosted.org/packages/source/${_name::1}/$_name/$_name-$pkgver.tar.gz") +sha256sums=(XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX) + +build() { + cd "$srcdir/$_name-$pkgver" + python -m build --wheel --no-isolation +} + +package() { + cd "$srcdir/$_name-$pkgver" + python -m installer --destdir="$pkgdir" dist/*.whl +} +""" % (version, rel, description, url) +print(PKGBUILD) diff --git a/bin/min_dependencies.py b/bin/min_dependencies.py new file mode 100755 index 0000000..850ed64 --- /dev/null +++ b/bin/min_dependencies.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +import tomli + +with open("pyproject.toml", 'rb') as f: + pyproject = tomli.load(f) + +deps = pyproject["project"]["dependencies"] +deps = [dep.replace(">=", "==") for dep in deps] +deps = [dep.replace("~=", "==") for dep in deps] + +print(' '.join(deps)) diff --git a/bin/run_tests b/bin/run_tests new file mode 100755 index 0000000..149176f --- /dev/null +++ b/bin/run_tests @@ -0,0 +1,10 @@ +#!/bin/bash + +echo "Running PEP code style tests" +flake8 unimpeded tests + +echo "Running docstring checks" +pydocstyle --convention=numpy unimpeded + +echo "Running code tests" +python -m pytest diff --git a/bin/utils.py b/bin/utils.py new file mode 100644 index 0000000..dfd3809 --- /dev/null +++ b/bin/utils.py @@ -0,0 +1,27 @@ +from packaging import version + + +def unit_incremented(a, b): + """Check if a is one version larger than b.""" + a = version.parse(a) + b = version.parse(b) + if a.pre is not None and b.pre is not None: + if a.pre[0] == b.pre[0]: + return a.pre[1] == b.pre[1]+1 and a.base_version == b.base_version + else: + return (a.pre[1] == 0 and a.pre[0] > b.pre[0] + and a.base_version == b.base_version) + elif a.pre is not None: + return a.base_version > b.base_version and a.pre[1] == 0 + elif b.pre is not None: + return a.base_version == b.base_version + else: + return (a.micro == b.micro+1 and + a.minor == b.minor and + a.major == b.major or + a.micro == 0 and + a.minor == b.minor+1 and + a.major == b.major or + a.micro == 0 and + a.minor == 0 and + a.major == b.major+1)