From 195b30209d2b25275438c54a7c07c1cfa49eaa7a Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Fri, 3 Apr 2026 16:43:53 -0400 Subject: [PATCH 1/3] chore: re-scikit-packaged diffpy.srmise --- .flake8 | 2 +- .github/ISSUE_TEMPLATE/release_checklist.md | 7 +- .../build-and-publish-docs-on-dispatch.yml | 18 +++++ .../workflows/build-wheel-release-upload.yml | 66 ++++++++++++++- .github/workflows/check-news-item.yml | 2 +- .github/workflows/matrix-and-codecov.yml | 21 +++++ .readthedocs.yaml | 2 +- AUTHORS.rst | 3 +- CODE_OF_CONDUCT.rst => CODE-OF-CONDUCT.rst | 0 README.rst | 81 +++++++------------ cookiecutter.json | 20 +++++ docs/source/conf.py | 8 +- docs/source/index.rst | 7 +- docs/source/license.rst | 27 ++----- news/skpkg.rst | 23 ++++++ pyproject.toml | 22 ++--- requirements/docs.txt | 2 +- src/diffpy/__init__.py | 10 +-- src/diffpy/srmise/__init__.py | 5 +- src/diffpy/srmise/applications/extract.py | 24 +++++- src/diffpy/srmise/applications/plot.py | 42 ++++++++-- src/diffpy/srmise/baselines/arbitrary.py | 15 +++- src/diffpy/srmise/baselines/base.py | 10 ++- src/diffpy/srmise/baselines/fromsequence.py | 15 +++- src/diffpy/srmise/baselines/nanospherical.py | 15 +++- src/diffpy/srmise/baselines/polynomial.py | 15 +++- src/diffpy/srmise/dataclusters.py | 27 ++++++- src/diffpy/srmise/modelcluster.py | 54 ++++++++++--- src/diffpy/srmise/modelparts.py | 8 +- src/diffpy/srmise/multimodelselection.py | 32 +++++++- src/diffpy/srmise/pdfdataset.py | 7 +- src/diffpy/srmise/pdfpeakextraction.py | 11 ++- src/diffpy/srmise/peakextraction.py | 53 ++++++++++-- src/diffpy/srmise/peaks/base.py | 10 ++- src/diffpy/srmise/peaks/gaussian.py | 39 ++++++++- src/diffpy/srmise/peaks/gaussianoverr.py | 39 ++++++++- src/diffpy/srmise/peaks/terminationripples.py | 10 ++- src/diffpy/srmise/peakstability.py | 9 ++- src/diffpy/srmise/srmise_app.py | 33 ++++++++ src/diffpy/srmise/version.py | 13 +-- tests/test_version.py | 2 +- 41 files changed, 628 insertions(+), 181 deletions(-) create mode 100644 .github/workflows/build-and-publish-docs-on-dispatch.yml create mode 100644 .github/workflows/matrix-and-codecov.yml rename CODE_OF_CONDUCT.rst => CODE-OF-CONDUCT.rst (100%) create mode 100644 cookiecutter.json create mode 100644 news/skpkg.rst create mode 100644 src/diffpy/srmise/srmise_app.py diff --git a/.flake8 b/.flake8 index 04d2d0b..a510511 100644 --- a/.flake8 +++ b/.flake8 @@ -6,7 +6,7 @@ exclude = __pycache__, build, dist, - doc/source/conf.py + docs/source/conf.py max-line-length = 115 # Ignore some style 'errors' produced while formatting by 'black' # https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index 6107962..56c5fca 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -11,12 +11,13 @@ assignees: "" - [ ] All PRs/issues attached to the release are merged. - [ ] All the badges on the README are passing. - [ ] License information is verified as correct. If you are unsure, please comment below. -- [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are - missing), tutorials, and other human-written text is up-to-date with any changes in the code. +- [ ] Locally rendered documentation contains all appropriate pages, tutorials, and other human-written text is up-to-date with any changes in the code. +- [ ] All API references are included. To check this, run `conda install scikit-package` and then `package build api-doc`. Review any edits made by rerendering the docs locally. - [ ] Installation instructions in the README, documentation, and the website are updated. - [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. - [ ] Grammar and writing quality are checked (no typos). - [ ] Install `pip install build twine`, run `python -m build` and `twine check dist/*` to ensure that the package can be built and is correctly formatted for PyPI release. +- [ ] Dispatch matrix testing to test the release on all Python versions and systems. If you do not have permission to run this workflow, tag the maintainer and say `@maintainer, please dispatch matrix testing workflow`. Please tag the maintainer (e.g., @username) in the comment here when you are ready for the PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: @@ -34,7 +35,7 @@ Please let the maintainer know that all checks are done and the package is ready - [ ] Ensure that the full release has appeared on PyPI successfully. -- [ ] New package dependencies listed in `conda.txt` and `test.txt` are added to `meta.yaml` in the feedstock. +- [ ] New package dependencies listed in `conda.txt` and `tests.txt` are added to `meta.yaml` in the feedstock. - [ ] Close any open issues on the feedstock. Reach out to the maintainer if you have questions. - [ ] Tag the maintainer for conda-forge release. diff --git a/.github/workflows/build-and-publish-docs-on-dispatch.yml b/.github/workflows/build-and-publish-docs-on-dispatch.yml new file mode 100644 index 0000000..7d4c244 --- /dev/null +++ b/.github/workflows/build-and-publish-docs-on-dispatch.yml @@ -0,0 +1,18 @@ +name: Build and Publish Docs on Dispatch + +on: + workflow_dispatch: + +jobs: + get-python-version: + uses: scikit-package/release-scripts/.github/workflows/_get-python-version-latest.yml@v0 + with: + python_version: 0 + + docs: + uses: scikit-package/release-scripts/.github/workflows/_release-docs.yml@v0 + with: + project: diffpy.srmise + c_extension: false + headless: false + python_version: ${{ fromJSON(needs.get-python-version.outputs.latest_python_version) }} diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index 00f39f4..ff353b3 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -1,18 +1,76 @@ -name: Release (GitHub/PyPI) and Deploy Docs +name: Build Wheel and Release +# Trigger on tag push or manual dispatch. +# Tag and release privilege are verified inside the reusable workflow. on: workflow_dispatch: push: tags: - - "*" # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml + - "*" + +# ── Release modality ────────────────────────────────────────────────────────── +# Three options are provided below. Only ONE job should be active at a time. +# To switch: comment out the active job and uncomment your preferred option, +# then commit the change to main before tagging a release. +# ───────────────────────────────────────────────────────────────────────────── jobs: - release: + # Option 1 (default): Release to GitHub, publish to PyPI, and deploy docs. + # + # The wheel is uploaded to PyPI so users can install with `pip install`. + # A GitHub release is created with the changelog as the release body, and + # the Sphinx documentation is rebuilt and deployed to GitHub Pages. + # + # Choose this for open-source packages distributed via PyPI and/or + # conda-forge where broad public availability is the goal. + build-release: uses: scikit-package/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 with: project: diffpy.srmise c_extension: false - maintainer_GITHUB_username: sbillinge + maintainer_github_username: sbillinge secrets: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} PAT_TOKEN: ${{ secrets.PAT_TOKEN }} + + # Option 2: Release to GitHub and deploy docs, without publishing to PyPI. + # + # A GitHub release is created and the Sphinx docs are deployed, but the + # wheel is not uploaded to PyPI. The source code remains publicly visible + # on GitHub and can be installed directly from there. + # + # Choose this when the package is public but you prefer to keep it off the + # default pip index — for example, if you distribute via conda-forge only, + # or if the package is not yet ready for a permanent PyPI presence. + # + # To use: comment out Option 1 above and uncomment the lines below. + # build-release-no-pypi: + # uses: scikit-package/release-scripts/.github/workflows/_build-release-github-no-pypi.yml@v0 + # with: + # project: diffpy.srmise + # c_extension: false + # maintainer_github_username: sbillinge, Ainamacar + # secrets: + # PAT_TOKEN: ${{ secrets.PAT_TOKEN }} + + # Option 3: Release to GitHub with wheel, license, and instructions bundled + # as a downloadable zip attached to the GitHub release asset. + # + # The wheel is built and packaged together with INSTRUCTIONS.txt and the + # LICENSE file into a zip that is attached directly to the GitHub release. + # Users with access to the (private) repo download the zip, follow the + # instructions inside, and install locally with pip. No PyPI or conda-forge + # upload occurs, and no docs are deployed. + # + # Choose this for private or restricted packages where distribution must be + # controlled: only users with repo access can download the release asset, + # making the GitHub release itself the distribution channel. + # + # To use: comment out Option 1 above and uncomment the lines below. + # build-release-private: + # uses: scikit-package/release-scripts/.github/workflows/_build-release-github-private-pure.yml@v0 + # with: + # project: diffpy.srmise + # maintainer_github_username: sbillinge, Ainamacar + # secrets: + # PAT_TOKEN: ${{ secrets.PAT_TOKEN }} diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml index e6f5a5e..43479c3 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -3,7 +3,7 @@ name: Check for News on: pull_request_target: branches: - - main + - main # GitHub does not evaluate expressions in trigger filters; edit this value if your base branch is not main jobs: check-news-item: diff --git a/.github/workflows/matrix-and-codecov.yml b/.github/workflows/matrix-and-codecov.yml new file mode 100644 index 0000000..e1aea98 --- /dev/null +++ b/.github/workflows/matrix-and-codecov.yml @@ -0,0 +1,21 @@ +name: Matrix and Codecov + +on: + # push: + # branches: + # - main + release: + types: + - prereleased + - published + workflow_dispatch: + +jobs: + matrix-coverage: + uses: scikit-package/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 + with: + project: diffpy.srmise + c_extension: false + headless: false + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 47f7a01..aaa8889 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,4 +10,4 @@ python: - requirements: requirements/docs.txt sphinx: - configuration: doc/source/conf.py + configuration: docs/source/conf.py diff --git a/AUTHORS.rst b/AUTHORS.rst index 7fe1918..1d4a95d 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,8 +1,7 @@ Authors ======= -Luke Granlund -Billinge Group and community contributors. +Luke Granlund, Simon Billinge, Billinge Group members Contributors ------------ diff --git a/CODE_OF_CONDUCT.rst b/CODE-OF-CONDUCT.rst similarity index 100% rename from CODE_OF_CONDUCT.rst rename to CODE-OF-CONDUCT.rst diff --git a/README.rst b/README.rst index 63d9aca..2327cff 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ :target: https://diffpy.github.io/diffpy.srmise :height: 100px -|PyPi| |Forge| |PythonVersion| |PR| +|PyPI| |Forge| |PythonVersion| |PR| |CI| |Codecov| |Black| |Tracking| @@ -25,8 +25,9 @@ :target: https://anaconda.org/conda-forge/diffpy.srmise .. |PR| image:: https://img.shields.io/badge/PR-Welcome-29ab47ff + :target: https://github.com/diffpy/diffpy.srmise/pulls -.. |PyPi| image:: https://img.shields.io/pypi/v/diffpy.srmise +.. |PyPI| image:: https://img.shields.io/pypi/v/diffpy.srmise :target: https://pypi.org/project/diffpy.srmise/ .. |PythonVersion| image:: https://img.shields.io/pypi/pyversions/diffpy.srmise @@ -35,54 +36,18 @@ .. |Tracking| image:: https://img.shields.io/badge/issue_tracking-github-blue :target: https://github.com/diffpy/diffpy.srmise/issues -Implementation of the ParSCAPE algorithm for peak extraction from atomic pair distribution functions (PDFs) - -SrMise is an implementation of the `ParSCAPE algorithm -`_ for peak extraction from -atomic pair distribution functions (PDFs). It is designed to function even -when *a priori* knowledge of the physical sample is limited, utilizing the -Akaike Information Criterion (AIC) to estimate whether peaks are -statistically justified relative to alternate models. Three basic use cases -are anticipated for SrMise. The first is peak fitting a user-supplied -collections of peaks. The second is peak extraction from a PDF with no (or -only partial) user-supplied peaks. The third is an AIC-driven multimodeling -analysis where the output of multiple SrMise trials are ranked. - -The framework for peak extraction defines peak-like clusters within the data, -extracts a single peak within each cluster, and iteratively combines nearby -clusters while performing a recursive search on the residual to identify -occluded peaks. Eventually this results in a single global cluster -containing many peaks fit over all the data. Over- and underfitting are -discouraged by use of the AIC when adding or, during a pruning step, removing -peaks. Termination effects, which can lead to physically spurious peaks in -the PDF, are incorporated in the mathematical peak model and the pruning step -attempts to remove peaks which are fit better as termination ripples due to -another peak. - -Where possible, SrMise provides physically reasonable default values -for extraction parameters. However, the PDF baseline should be estimated by -the user before extraction, or by performing provisional peak extraction with -varying baseline parameters. The package defines a linear (crystalline) -baseline, arbitrary polynomial baseline, a spherical nanoparticle baseline, -and an arbitrary baseline interpolated from a list of user-supplied values. -In addition, PDFs with accurate experimentally-determined uncertainties are -necessary to provide the most reliable results, but historically such PDFs -are rare. In the absence of accurate uncertainties an *ad hoc* uncertainty -must be specified. +Peak extraction and peak fitting tool for atomic pair distribution functions. + +* LONGER DESCRIPTION HERE For more information about the diffpy.srmise library, please consult our `online documentation `_. Citation -------- -If you use this program for a scientific research that leads -to publication, we ask that you acknowledge use of the program -by citing the following paper in your publication: +If you use diffpy.srmise in a scientific publication, we would like you to cite this package as - L. Granlund, S. J. L. Billinge and P. M. Duxbury, - `Algorithm for systematic peak extraction from atomic pair distribution functions - `__, - *Acta Crystallogr. A* **4**, 392-409 (2015). + diffpy.srmise Package, https://github.com/diffpy/diffpy.srmise Installation ------------ @@ -101,10 +66,6 @@ The following creates and activates a new environment named ``diffpy.srmise_env` conda create -n diffpy.srmise_env diffpy.srmise conda activate diffpy.srmise_env -To confirm that the installation was successful, type :: - - python -c "import diffpy.srmise; print(diffpy.srmise.__version__)" - The output should print the latest version displayed on the badges above. If the above does not work, you can use ``pip`` to download and install the latest release from @@ -119,6 +80,19 @@ and run the following :: pip install . +This package also provides command-line utilities. To check the software has been installed correctly, type :: + + diffpy.srmise --version + +You can also type the following command to verify the installation. :: + + python -c "import diffpy.srmise; print(diffpy.srmise.__version__)" + + +To view the basic usage and available commands, type :: + + diffpy.srmise -h + Getting Started --------------- @@ -127,9 +101,7 @@ You may consult our `online documentation `_ is the discussion forum for general questions and discussions about the use of diffpy.srmise. Please join the diffpy.srmise users community by joining the Google group. The diffpy.srmise project welcomes your expertise and enthusiasm! - -If you see a bug or want to request a feature, please `report it as an issue `_ and/or `submit a fix as a PR `_. You can also post it to the `Diffpy user group `_. +If you see a bug or want to request a feature, please `report it as an issue `_ and/or `submit a fix as a PR `_. Feel free to fork the project and contribute. To install diffpy.srmise in a development mode, with its sources being directly used by Python @@ -152,9 +124,14 @@ trying to commit again. Improvements and fixes are always appreciated. -Before contributing, please read our `Code of Conduct `_. +Before contributing, please read our `Code of Conduct `_. Contact ------- -For more information on diffpy.srmise please visit the project `web-page `_ or email Prof. Simon Billinge at sb2896@columbia.edu. +For more information on diffpy.srmise please visit the project `web-page `_ or email the maintainers ``Simon Billinge(sbillinge@ucsb.edu)``. + +Acknowledgements +---------------- + +``diffpy.srmise`` is built and maintained with `scikit-package `_. diff --git a/cookiecutter.json b/cookiecutter.json new file mode 100644 index 0000000..e50c40f --- /dev/null +++ b/cookiecutter.json @@ -0,0 +1,20 @@ +{ + "author_names": "Simon J.L Billinge group, Luke Granlund", + "author_emails": "sbillinge@ucsb.edu, granlund@pa.msu.edu", + "maintainer_names": "Simon Billinge", + "maintainer_emails": "sbillinge@ucsb.edu", + "maintainer_github_usernames": "sbillinge, Ainamacar", + "contributors": "Simon Billinge, Billinge Group members", + "license_holders": "The Trustees of Columbia University in the City of New York", + "project_name": "diffpy.srmise", + "github_username_or_orgname": "diffpy", + "github_repo_name": "diffpy.srmise", + "conda_pypi_package_dist_name": "diffpy.srmise", + "package_dir_name": "diffpy.srmise", + "project_short_description": "Peak extraction and peak fitting tool for atomic pair distribution functions.", + "project_keywords": "peak extraction, fitting, PDF, AIC, multimodelling", + "minimum_supported_python_version": "3.12", + "maximum_supported_python_version": "3.14", + "project_needs_c_code_compiled": "No", + "project_has_gui_tests": "No" +} diff --git a/docs/source/conf.py b/docs/source/conf.py index f60b475..1b50979 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# diffpy.srmise documentation build configuration file, created by +# diffpy.srmise documentation build configuration file, created by # noqa: E501 # sphinx-quickstart on Thu Jan 30 15:49:41 2014. # # This file is execfile()d with the current directory set to its @@ -22,11 +22,11 @@ try: fullversion = version("diffpy.srmise") except Exception: - fullversion = "No version found. The correct version will appear in the released version." + fullversion = "No version found. The correct version will appear in the released version." # noqa: E501 # 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 Path().resolve() to make it absolute, like shown here. +# documentation root, use Path().resolve() to make it absolute, like shown here. # noqa: E501 # sys.path.insert(0, str(Path(".").resolve())) sys.path.insert(0, str(Path("../..").resolve())) sys.path.insert(0, str(Path("../../src").resolve())) @@ -50,7 +50,7 @@ "sphinx.ext.intersphinx", "sphinx_rtd_theme", "sphinx_copybutton", - "m2r", + "m2r2", ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/source/index.rst b/docs/source/index.rst index 2e91dbe..732fc29 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -4,7 +4,7 @@ .. |title| replace:: diffpy.srmise documentation -``diffpy.srmise`` - Peak extraction and peak fitting tool for atomic pair distribution functions +``diffpy.srmise`` - Peak extraction and peak fitting tool for atomic pair distribution functions. | Software version |release| | Last updated |today|. @@ -21,7 +21,7 @@ To get started, please visit the :ref:`Getting started ` page. Authors ======= -``diffpy.srmise`` is developed by Simon Billinge, Billinge Group members. The maintainer for this project is Simon Billinge. For a detailed list of contributors see +``diffpy.srmise`` is developed by Simon Billinge, Billinge Group members. This project is maintained by Simon Billinge. For a detailed list of contributors see https://github.com/diffpy/diffpy.srmise/graphs/contributors. ============ @@ -41,8 +41,7 @@ Acknowledgements Table of contents ================= .. toctree:: - :maxdepth: 1 - :titlesonly: + :maxdepth: 2 tutorial/index extending diff --git a/docs/source/license.rst b/docs/source/license.rst index 65646b0..7428e28 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -9,25 +9,8 @@ OPEN SOURCE LICENSE AGREEMENT ============================= BSD 3-Clause License -Copyright 2014-2015, Board of Trustees of Michigan State University - -Copyright 2016-2025, The Trustees of Columbia University in the City of New York. - -All rights reserved. - -If you use this program to do productive scientific research that -leads to publication, we ask that you acknowledge use of the -program by citing the following paper in your publication: - - L. Granlund, S.J.L. Billinge, P.M. Duxbury, Algorithm for - systematic peak extraction from atomic pair distribution - functions, Acta Crystallographica A 71(4), 392-409 (2015). - doi:10.1107/S2053273315005276 - -For more information please visit the diffpy web-page at - http://www.diffpy.org -or email Luke Granlund at luke.r.granlund@gmail.com, or Prof. Simon -Billinge at sb2896@columbia.edu. +Copyright (c) 2026, The Trustees of Columbia University in the City of New York. +All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -39,9 +22,9 @@ modification, are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -3. Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE diff --git a/news/skpkg.rst b/news/skpkg.rst new file mode 100644 index 0000000..a4bde95 --- /dev/null +++ b/news/skpkg.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: re-scikit-packaged `diffpy.srmise` + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/pyproject.toml b/pyproject.toml index 5fccebe..0dd7456 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,22 +6,21 @@ build-backend = "setuptools.build_meta" name = "diffpy.srmise" dynamic=['version', 'dependencies'] authors = [ - { name="Simon J.L. Billinge group", email="sb2896@columbia.edu" }, - {name="Luke Granlund", email="granlund@pa.msu.edu"}, + {name='Simon J.L Billinge group', email='sbillinge@ucsb.edu'}, + {name='Luke Granlund', email='granlund@pa.msu.edu'}, ] maintainers = [ - { name="Simon Billinge", email="sb2896@columbia.edu" }, + {name='Simon Billinge', email='sbillinge@ucsb.edu'}, ] description = "Peak extraction and peak fitting tool for atomic pair distribution functions." -keywords = ['peak extraction', 'fitting', 'PDF', 'AIC', 'multimodeling'] +keywords = ['peak extraction', 'fitting', 'PDF', 'AIC', 'multimodelling'] readme = "README.rst" requires-python = ">=3.12, <3.15" classifiers = [ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', - 'Intended Audience :: Education', 'License :: OSI Approved :: BSD License', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', @@ -32,8 +31,6 @@ classifiers = [ 'Programming Language :: Python :: 3.14', 'Topic :: Scientific/Engineering :: Physics', 'Topic :: Scientific/Engineering :: Chemistry', - 'Topic :: Software Development :: Libraries', - ] [project.urls] @@ -46,15 +43,15 @@ template = "{tag}" dev_template = "{tag}" dirty_template = "{tag}" -[project.scripts] -srmise = "diffpy.srmise.applications.extract:main" - [tool.setuptools.packages.find] where = ["src"] # list of folders that contain the packages (["."] by default) include = ["*"] # package names should match these glob patterns (["*"] by default) exclude = [] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) +[project.scripts] +srmise = "diffpy.srmise.applications.extract:main" + [tool.setuptools.dynamic] dependencies = {file = ["requirements/pip.txt"]} @@ -67,9 +64,6 @@ skip = "*.cif,*.dat" recursive = true wrap-summaries = 72 wrap-descriptions = 72 -exclude = [ - "src/diffpy/srmise/baselines/arbitrary.py" -] [tool.black] line-length = 115 diff --git a/requirements/docs.txt b/requirements/docs.txt index 5f34c6e..1de813f 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -2,4 +2,4 @@ sphinx sphinx_rtd_theme sphinx-copybutton doctr -m2r +m2r2 diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index 0163760..6763cd6 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python ############################################################################## # -# (c) 2025 The Trustees of Columbia University in the City of New York. +# (c) 2026 The Trustees of Columbia University in the City of New York. # All rights reserved. # # File coded by: Billinge Group members and community contributors. @@ -12,11 +12,3 @@ # See LICENSE.rst for license information. # ############################################################################## -"""Blank namespace package for module diffpy.""" - - -from pkgutil import extend_path - -__path__ = extend_path(__path__, __name__) - -# End of file diff --git a/src/diffpy/srmise/__init__.py b/src/diffpy/srmise/__init__.py index a4e8b2b..34d9ab1 100644 --- a/src/diffpy/srmise/__init__.py +++ b/src/diffpy/srmise/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python ############################################################################## # -# (c) 2025 The Trustees of Columbia University in the City of New York. +# (c) 2026 The Trustees of Columbia University in the City of New York. # All rights reserved. # # File coded by: Simon Billinge, Billinge Group members. @@ -15,9 +15,8 @@ """Peak extraction and peak fitting tool for atomic pair distribution functions.""" - # package version -from diffpy.srmise.version import __version__ +from diffpy.srmise.version import __version__ # noqa # silence the pyflakes syntax checker assert __version__ or True diff --git a/src/diffpy/srmise/applications/extract.py b/src/diffpy/srmise/applications/extract.py index 99157ff..f55b4d3 100755 --- a/src/diffpy/srmise/applications/extract.py +++ b/src/diffpy/srmise/applications/extract.py @@ -288,7 +288,13 @@ def main(): "--dg-mode", dest="dg_mode", type="choice", - choices=["absolute", "data", "max-fraction", "ptp-fraction", "dG-fraction"], + choices=[ + "absolute", + "data", + "max-fraction", + "ptp-fraction", + "dG-fraction", + ], help="Define how values passed to '--dg' are treated. " "Possible values are: \n" "'absolute' - The actual uncertainty in the PDF.\n" @@ -338,7 +344,13 @@ def main(): metavar="FILE", help="Save result of extraction to FILE (.srmise " "format).", ) - group.add_option("--plot", "-p", action="store_true", dest="plot", help="Plot extracted peaks.") + group.add_option( + "--plot", + "-p", + action="store_true", + dest="plot", + help="Plot extracted peaks.", + ) group.add_option( "--liveplot", "-l", @@ -601,7 +613,13 @@ def _format_text(self, text): # the above is still the same bits = text.split("\n") formatted_bits = [ - textwrap.fill(bit, text_width, initial_indent=indent, subsequent_indent=indent) for bit in bits + textwrap.fill( + bit, + text_width, + initial_indent=indent, + subsequent_indent=indent, + ) + for bit in bits ] result = "\n".join(formatted_bits) + "\n" return result diff --git a/src/diffpy/srmise/applications/plot.py b/src/diffpy/srmise/applications/plot.py index 788b7d0..7da23b7 100755 --- a/src/diffpy/srmise/applications/plot.py +++ b/src/diffpy/srmise/applications/plot.py @@ -193,7 +193,16 @@ def makeplot(ppe_or_stability, ip=None, **kwds): x = ppe.x[rangeslice] y = ppe.y[rangeslice] dy = ppe.effective_dy[rangeslice] - mcluster = ModelCluster(ppe.initial_peaks, ppe.baseline, x, y, dy, None, ppe.error_method, ppe.pf) + mcluster = ModelCluster( + ppe.initial_peaks, + ppe.baseline, + x, + y, + dy, + None, + ppe.error_method, + ppe.pf, + ) ext = mcluster else: ext = ppe.extracted @@ -252,7 +261,13 @@ def makeplot(ppe_or_stability, ip=None, **kwds): # Define the various data which will be plotted r = ext.r_cluster dr = (r[-1] - r[0]) / len(r) - rexpand = np.concatenate((np.arange(r[0] - dr, xlo, -dr)[::-1], r, np.arange(r[-1] + dr, xhi + dr, dr))) + rexpand = np.concatenate( + ( + np.arange(r[0] - dr, xlo, -dr)[::-1], + r, + np.arange(r[-1] + dr, xhi + dr, dr), + ) + ) rfine = np.arange(r[0], r[-1], 0.1 * dr) gr_obs = np.array(resample(ppe.x, ppe.y, rexpand)) * scale # gr_fit = resample(r, ext.value(), rfine) @@ -475,7 +490,15 @@ def makeplot(ppe_or_stability, ip=None, **kwds): plt.axes(ax_cmp) ylo2, yhi2 = ax_cmp.get_ylim() ypos = yhi2 / 2.0 - plt.text(xpos, ypos, dgstr, va="center", ha="center", size=8, color="green") + plt.text( + xpos, + ypos, + dgstr, + va="center", + ha="center", + size=8, + color="green", + ) else: # Text only in main plot region # Must change axes @@ -584,9 +607,18 @@ def main(): type="int", help="Plot given model from set. Ignored if srmise_file is not a PeakStability file.", ) - parser.add_option("--show", action="store_true", help="execute pylab.show() blocking call") + parser.add_option( + "--show", + action="store_true", + help="execute pylab.show() blocking call", + ) parser.add_option("-o", "--output", type="string", help="save plot to the specified file") - parser.add_option("--format", type="string", default="eps", help="output format for plot saving") + parser.add_option( + "--format", + type="string", + default="eps", + help="output format for plot saving", + ) parser.allow_interspersed_args = True opts, args = parser.parse_args(sys.argv[1:]) diff --git a/src/diffpy/srmise/baselines/arbitrary.py b/src/diffpy/srmise/baselines/arbitrary.py index 6101bbb..d0f8c99 100644 --- a/src/diffpy/srmise/baselines/arbitrary.py +++ b/src/diffpy/srmise/baselines/arbitrary.py @@ -71,7 +71,10 @@ def __init__(self, npars, valuef, jacobianf=None, estimatef=None, Cache=None): for d in range(testnpars + 1): parameterdict["a_" + str(d)] = d formats = ["internal"] - default_formats = {"default_input": "internal", "default_output": "internal"} + default_formats = { + "default_input": "internal", + "default_output": "internal", + } # Check that the provided functions are at least callable if valuef is None or callable(valuef): @@ -98,7 +101,15 @@ def __init__(self, npars, valuef, jacobianf=None, estimatef=None, Cache=None): "jacobianf": (jacobianf, repr), "estimatef": (estimatef, repr), } - BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) + BaselineFunction.__init__( + self, + parameterdict, + formats, + default_formats, + metadict, + None, + Cache, + ) # Methods required by BaselineFunction #### diff --git a/src/diffpy/srmise/baselines/base.py b/src/diffpy/srmise/baselines/base.py index bad8244..b1e9a06 100644 --- a/src/diffpy/srmise/baselines/base.py +++ b/src/diffpy/srmise/baselines/base.py @@ -94,7 +94,15 @@ def __init__( The class (not instance) which implements caching of BaseFunction evaluations. """ - BaseFunction.__init__(self, parameterdict, parformats, default_formats, metadict, base, Cache) + BaseFunction.__init__( + self, + parameterdict, + parformats, + default_formats, + metadict, + base, + Cache, + ) # "Virtual" class methods #### diff --git a/src/diffpy/srmise/baselines/fromsequence.py b/src/diffpy/srmise/baselines/fromsequence.py index 347c14d..6333977 100644 --- a/src/diffpy/srmise/baselines/fromsequence.py +++ b/src/diffpy/srmise/baselines/fromsequence.py @@ -90,14 +90,25 @@ def __init__(self, *args, **kwds): raise ValueError(emsg) parameterdict = {} formats = ["internal"] - default_formats = {"default_input": "internal", "default_output": "internal"} + default_formats = { + "default_input": "internal", + "default_output": "internal", + } self.spline = spi.InterpolatedUnivariateSpline(x, y) self.minx = x[0] self.maxx = x[-1] metadict = {} metadict["x"] = (x, self.xyrepr) metadict["y"] = (y, self.xyrepr) - BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache=None) + BaselineFunction.__init__( + self, + parameterdict, + formats, + default_formats, + metadict, + None, + Cache=None, + ) # Methods required by BaselineFunction #### diff --git a/src/diffpy/srmise/baselines/nanospherical.py b/src/diffpy/srmise/baselines/nanospherical.py index 1c199c2..c788b6b 100644 --- a/src/diffpy/srmise/baselines/nanospherical.py +++ b/src/diffpy/srmise/baselines/nanospherical.py @@ -51,9 +51,20 @@ def __init__(self, Cache=None): # Define parameterdict parameterdict = {"scale": 0, "radius": 1} formats = ["internal"] - default_formats = {"default_input": "internal", "default_output": "internal"} + default_formats = { + "default_input": "internal", + "default_output": "internal", + } metadict = {} - BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) + BaselineFunction.__init__( + self, + parameterdict, + formats, + default_formats, + metadict, + None, + Cache, + ) # Methods required by BaselineFunction #### diff --git a/src/diffpy/srmise/baselines/polynomial.py b/src/diffpy/srmise/baselines/polynomial.py index 9d61574..13ca878 100644 --- a/src/diffpy/srmise/baselines/polynomial.py +++ b/src/diffpy/srmise/baselines/polynomial.py @@ -52,9 +52,20 @@ def __init__(self, degree, Cache=None): for d in range(self.degree + 1): parameterdict["a_" + str(d)] = self.degree - d formats = ["internal"] - default_formats = {"default_input": "internal", "default_output": "internal"} + default_formats = { + "default_input": "internal", + "default_output": "internal", + } metadict = {"degree": (degree, repr)} - BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) + BaselineFunction.__init__( + self, + parameterdict, + formats, + default_formats, + metadict, + None, + Cache, + ) # Methods required by BaselineFunction #### diff --git a/src/diffpy/srmise/dataclusters.py b/src/diffpy/srmise/dataclusters.py index 5cac3b1..4fe29b5 100644 --- a/src/diffpy/srmise/dataclusters.py +++ b/src/diffpy/srmise/dataclusters.py @@ -219,7 +219,12 @@ def __next__(self): else: # insert right of nearest cluster self.lastcluster_idx = nearest_cluster[0] + 1 - self.clusters = np.insert(self.clusters, int(self.lastcluster_idx), [test_idx, test_idx], 0) + self.clusters = np.insert( + self.clusters, + int(self.lastcluster_idx), + [test_idx, test_idx], + 0, + ) return self def makeclusters(self): @@ -515,7 +520,25 @@ def animate(self): # simple test code if __name__ == "__main__": - x = np.array([-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0]) + x = np.array( + [ + -2.0, + -1.5, + -1.0, + -0.5, + 0.0, + 0.5, + 1.0, + 1.5, + 2.0, + 2.5, + 3.0, + 3.5, + 4.0, + 4.5, + 5.0, + ] + ) y = np.array( [ 0.0183156, diff --git a/src/diffpy/srmise/modelcluster.py b/src/diffpy/srmise/modelcluster.py index 0d92f30..1643e38 100644 --- a/src/diffpy/srmise/modelcluster.py +++ b/src/diffpy/srmise/modelcluster.py @@ -396,7 +396,10 @@ def prettypar(self, i): if self.model is None or self.cov is None: return "Model and/or Covariance matrix undefined." k = i if i in self.ipmap else self.pmap[i] - return "%.5e (%.5e)" % (self.getvalue(k), np.sqrt(self.getcovariance(k, k))) + return "%.5e (%.5e)" % ( + self.getvalue(k), + np.sqrt(self.getcovariance(k, k)), + ) # End of class ModelCovariance @@ -685,7 +688,10 @@ def factory(mcstr, **kwds): # Instantiating baseline functions if readblf: blfbaselist = [] - res = re.split(r"(?m)^#+ BaselineFunction \d+\s*(?:#.*\s+)*", baselinefunctions) + res = re.split( + r"(?m)^#+ BaselineFunction \d+\s*(?:#.*\s+)*", + baselinefunctions, + ) for s in res[1:]: blfbaselist.append(BaseFunction.factory(s, blfbaselist)) @@ -1076,7 +1082,10 @@ def fit( cov_format, ) except SrMiseFitError as e: - logger.debug("Error while fitting cluster: %s\nReverting to original model.", e) + logger.debug( + "Error while fitting cluster: %s\nReverting to original model.", + e, + ) self.model = orig_model self.baseline = orig_baseline return None @@ -1350,7 +1359,12 @@ def plottable(self, joined=False): A sequence of plottable objects. """ if joined: - return [self.r_cluster, self.y_cluster, self.r_cluster, self.value()] + return [ + self.r_cluster, + self.y_cluster, + self.r_cluster, + self.value(), + ] else: toreturn = [self.r_cluster, self.y_cluster] bl = self.valuebl() @@ -1424,7 +1438,12 @@ def augment(self, source): # TODO: Do I need this? If test_model contains peaks # by reference, the fit peaks will change as well. self.fit() - msg = ["Best model after fit is:", "%s", "w/ quality: %s", "================="] + msg = [ + "Best model after fit is:", + "%s", + "w/ quality: %s", + "=================", + ] logger.debug("\n".join(msg), self.model, best_qual.stat) return @@ -1483,7 +1502,12 @@ def prune(self): best_model = self.model.copy() best_qual = self.quality() - msg = ["====Pruning fits:====", "Original model:", "%s", "w/ quality: %s"] + msg = [ + "====Pruning fits:====", + "Original model:", + "%s", + "w/ quality: %s", + ] logger.info("\n".join(msg), best_model, best_qual.stat) # Main prune loop #### @@ -1518,7 +1542,11 @@ def prune(self): check_models[i].extend(best_model[i + 1 : hi].copy()) prune_mc.model = check_models[i] - msg = ["len(check_models): %s", "len(best_model): %s", "i: %s"] + msg = [ + "len(check_models): %s", + "len(best_model): %s", + "i: %s", + ] logger.debug("\n".join(msg), len(check_models), len(best_model), i) addpars = best_model.npars() - check_models[i].npars() - best_model[i].npars(count_fixed=False) @@ -1526,7 +1554,10 @@ def prune(self): # Remove contribution of (effectively) fixed peaks y = np.array(y_nobl) if lo > 0: - logger.debug("len(sum): %s", len(np.sum(best_modely[:lo], axis=0))) + logger.debug( + "len(sum): %s", + len(np.sum(best_modely[:lo], axis=0)), + ) y -= np.sum(best_modely[:lo], axis=0) if hi < len(best_modely): y -= np.sum(best_modely[hi:], axis=0) @@ -1563,7 +1594,12 @@ def prune(self): "check_qual: %s", "sorted check_qual: %s", ] - logger.debug("\n".join(msg), best_qual.stat, [c.stat for c in check_qual], arg) + logger.debug( + "\n".join(msg), + best_qual.stat, + [c.stat for c in check_qual], + arg, + ) arg = arg[-1] newbest_qual = check_qual[arg] diff --git a/src/diffpy/srmise/modelparts.py b/src/diffpy/srmise/modelparts.py index 8c591e1..1778b9b 100644 --- a/src/diffpy/srmise/modelparts.py +++ b/src/diffpy/srmise/modelparts.py @@ -654,7 +654,13 @@ def copy(self): ModelPart A deep copy of this ModelPart. """ - return type(self).__call__(self._owner, self.pars, self.free, self.removable, self.static_owner) + return type(self).__call__( + self._owner, + self.pars, + self.free, + self.removable, + self.static_owner, + ) def __getitem__(self, key_or_idx): """Return parameter of peak corresponding with key_or_idx. diff --git a/src/diffpy/srmise/multimodelselection.py b/src/diffpy/srmise/multimodelselection.py index 4bb85d1..8e97afd 100644 --- a/src/diffpy/srmise/multimodelselection.py +++ b/src/diffpy/srmise/multimodelselection.py @@ -105,7 +105,16 @@ def makeaics(self, dgs, dr, filename=None): # modelevaluators subpackage are in need of a rewrite, and so it would be # best to do them all at once. dg0 = self.dgs[0] - mc = ModelCluster(result[1], result[2], r, y, dg0 * np.ones(len(r)), None, em, self.ppe.pf) + mc = ModelCluster( + result[1], + result[2], + r, + y, + dg0 * np.ones(len(r)), + None, + em, + self.ppe.pf, + ) em0 = mc.quality() for dg in self.dgs: @@ -265,7 +274,11 @@ def animate_classprobs(self, step=False, duration=0.0, **kwds): arrow_left = len(self.classes) - 1 arrow_right = arrow_left + 0.05 * arrow_left (line,) = plt.plot(range(len(self.classes)), self.classprobs[self.dgs[0]]) - (dot,) = plt.plot(self.dgs[best_idx], self.classprobs[self.dgs[0]][bestclass_idx], "ro") + (dot,) = plt.plot( + self.dgs[best_idx], + self.classprobs[self.dgs[0]][bestclass_idx], + "ro", + ) plt.axvline(arrow_left, color="k") ax2 = ax1.twinx() ax2.set_ylim(self.dgs[0], self.dgs[-1]) @@ -628,7 +641,10 @@ def plot3dclassprobs(self, **kwds): maxys = np.max(ys) if maxys >= probfilter[0] and maxys <= probfilter[1]: - p0, p1 = ((xs[0], 0), (xs[-1], 0)) # points to close the vertices + p0, p1 = ( + (xs[0], 0), + (xs[-1], 0), + ) # points to close the vertices verts.append(np.concatenate([[p0], zip(xs, ys), [p1], [p0]])) zlabels.append(i) @@ -717,7 +733,15 @@ def plot3dclassprobs(self, **kwds): cbaxis = fig.add_axes(rect) # Remove all colorbar.make_axes keywords except orientation - kwds = eatkwds("fraction", "pad", "shrink", "aspect", "anchor", "panchor", **kwds) + kwds = eatkwds( + "fraction", + "pad", + "shrink", + "aspect", + "anchor", + "panchor", + **kwds, + ) else: kwds.setdefault("shrink", 0.75) diff --git a/src/diffpy/srmise/pdfdataset.py b/src/diffpy/srmise/pdfdataset.py index 56c473a..2d72878 100644 --- a/src/diffpy/srmise/pdfdataset.py +++ b/src/diffpy/srmise/pdfdataset.py @@ -479,7 +479,12 @@ class PDFDataFormatError(Exception): print(k, "=", v) print("== robs Gobs drobs dGobs ==") for i in range(len(dataset.robs)): - print(dataset.robs[i], dataset.Gobs[i], dataset.drobs[i], dataset.dGobs[i]) + print( + dataset.robs[i], + dataset.Gobs[i], + dataset.drobs[i], + dataset.dGobs[i], + ) print("== writeStr() ==") print(dataset.writeStr()) print("== datasetcopy.writeStr() ==") diff --git a/src/diffpy/srmise/pdfpeakextraction.py b/src/diffpy/srmise/pdfpeakextraction.py index af52fb4..f665e3f 100644 --- a/src/diffpy/srmise/pdfpeakextraction.py +++ b/src/diffpy/srmise/pdfpeakextraction.py @@ -520,7 +520,16 @@ def extract(self, **kwds): break else: - ext = ModelCluster(ext.model, bl, r1, y1, y_error1, None, self.error_method, self.pf) + ext = ModelCluster( + ext.model, + bl, + r1, + y1, + y_error1, + None, + self.error_method, + self.pf, + ) ext.prune() logger.info("Model after resampling and termination ripples:\n%s", ext) diff --git a/src/diffpy/srmise/peakextraction.py b/src/diffpy/srmise/peakextraction.py index 3bcb084..06af50f 100644 --- a/src/diffpy/srmise/peakextraction.py +++ b/src/diffpy/srmise/peakextraction.py @@ -760,7 +760,11 @@ def estimate_peak(self, x, add=True): dy = self.effective_dy[rangeslice] if x < x1[0] or x > x1[-1]: - emsg = "Argument x=%s outside allowed range (%s, %s)." % (x, x1[0], x1[-1]) + emsg = "Argument x=%s outside allowed range (%s, %s)." % ( + x, + x1[0], + x1[-1], + ) raise ValueError(emsg) # Object performing clustering on data. Note that DataClusters @@ -848,7 +852,18 @@ def extract_single(self, recursion_depth=1): y = self.y[rangeslice] - ip.value(x) # List of ModelClusters containing extracted peaks. - mclusters = [ModelCluster(None, bl, x, y, dy, dclusters.cut(0), self.error_method, self.pf)] + mclusters = [ + ModelCluster( + None, + bl, + x, + y, + dy, + dclusters.cut(0), + self.error_method, + self.pf, + ) + ] # The minimum number of points required to make a valid fit, as # determined by the peak functions and error method used. This is a @@ -982,7 +997,12 @@ def extract_single(self, recursion_depth=1): right_data = min(len(x), x.searchsorted(peak_pos[pivot + 1]) + 1) near_peaks = np.append(near_peaks, pivot) - other_peaks = np.concatenate([np.arange(0, pivot - 1), np.arange(pivot + 1, len(peak_pos))]) + other_peaks = np.concatenate( + [ + np.arange(0, pivot - 1), + np.arange(pivot + 1, len(peak_pos)), + ] + ) # Go from indices to lists of peaks. near_peaks = Peaks([full_cluster.model[i] for i in near_peaks]) @@ -1064,7 +1084,11 @@ def extract_single(self, recursion_depth=1): "%s", "---End of combining clusters---", ] - logger.debug("\n".join(msg), mclusters[step.lastcluster_idx], full_cluster) + logger.debug( + "\n".join(msg), + mclusters[step.lastcluster_idx], + full_cluster, + ) mclusters[step.lastcluster_idx] = full_cluster # End update cluster fits ### @@ -1126,7 +1150,12 @@ def extract_single(self, recursion_depth=1): right_data = min(len(x), x.searchsorted(peak_pos[pivot + 1]) + 1) near_peaks = np.append(near_peaks, pivot) - other_peaks = np.concatenate([np.arange(0, pivot - 1), np.arange(pivot + 1, len(peak_pos))]) + other_peaks = np.concatenate( + [ + np.arange(0, pivot - 1), + np.arange(pivot + 1, len(peak_pos)), + ] + ) # Go from indices to lists of peaks. near_peaks = Peaks([new_cluster.model[i] for i in near_peaks]) @@ -1286,7 +1315,12 @@ def extract_single(self, recursion_depth=1): "---End of combining clusters---", ] - logger.debug("\n".join(msg), mclusters[idx - 1], mclusters[idx], new_cluster) + logger.debug( + "\n".join(msg), + mclusters[idx - 1], + mclusters[idx], + new_cluster, + ) mclusters[idx - 1] = new_cluster del mclusters[idx] @@ -1358,7 +1392,12 @@ def fit_single(self): # Fit model with baseline and calculate covariance matrix cov = ModelCovariance() - ext.fit(fitbaseline=True, estimate=False, cov=cov, cov_format="default_output") + ext.fit( + fitbaseline=True, + estimate=False, + cov=cov, + cov_format="default_output", + ) # Update calculated instance variables self.extraction_type = "fit_single" diff --git a/src/diffpy/srmise/peaks/base.py b/src/diffpy/srmise/peaks/base.py index a13a312..a8b2c35 100644 --- a/src/diffpy/srmise/peaks/base.py +++ b/src/diffpy/srmise/peaks/base.py @@ -90,7 +90,15 @@ def __init__( if "position" not in parameterdict: emsg = "Argument parameterdict missing required key 'position'." raise ValueError(emsg) - BaseFunction.__init__(self, parameterdict, parformats, default_formats, metadict, base, Cache) + BaseFunction.__init__( + self, + parameterdict, + parformats, + default_formats, + metadict, + base, + Cache, + ) # # "Virtual" class methods #### diff --git a/src/diffpy/srmise/peaks/gaussian.py b/src/diffpy/srmise/peaks/gaussian.py index 51a8513..abf2a08 100644 --- a/src/diffpy/srmise/peaks/gaussian.py +++ b/src/diffpy/srmise/peaks/gaussian.py @@ -49,10 +49,21 @@ def __init__(self, maxwidth, Cache=None): corresponding Gaussian, which is physically relevant.""" parameterdict = {"position": 0, "width": 1, "area": 2} formats = ["internal", "pwa", "mu_sigma_area"] - default_formats = {"default_input": "internal", "default_output": "pwa"} + default_formats = { + "default_input": "internal", + "default_output": "pwa", + } metadict = {} metadict["maxwidth"] = (maxwidth, repr) - PeakFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) + PeakFunction.__init__( + self, + parameterdict, + formats, + default_formats, + metadict, + None, + Cache, + ) if maxwidth <= 0: emsg = "'maxwidth' must be greater than 0." @@ -123,7 +134,17 @@ def estimate_parameters(self, r, y): else: sigma_left = np.sqrt( -0.5 - * np.mean(np.abs(np.array([use_r[0] - guesspars[0], use_r[-1] - guesspars[0]]))) ** 2 + * np.mean( + np.abs( + np.array( + [ + use_r[0] - guesspars[0], + use_r[-1] - guesspars[0], + ] + ) + ) + ) + ** 2 / np.log(min_y / max_y) ) if use_y[-1] < max_y: @@ -131,7 +152,17 @@ def estimate_parameters(self, r, y): else: sigma_right = np.sqrt( -0.5 - * np.mean(np.abs(np.array([use_r[0] - guesspars[0], use_r[-1] - guesspars[0]]))) ** 2 + * np.mean( + np.abs( + np.array( + [ + use_r[0] - guesspars[0], + use_r[-1] - guesspars[0], + ] + ) + ) + ) + ** 2 / np.log(min_y / max_y) ) guesspars[1] = 0.5 * (sigma_right + sigma_left) * self.sigma2fwhm diff --git a/src/diffpy/srmise/peaks/gaussianoverr.py b/src/diffpy/srmise/peaks/gaussianoverr.py index 8e5d4cf..3333e7a 100644 --- a/src/diffpy/srmise/peaks/gaussianoverr.py +++ b/src/diffpy/srmise/peaks/gaussianoverr.py @@ -49,10 +49,21 @@ def __init__(self, maxwidth, Cache=None): corresponding Gaussian, which is physically relevant.""" parameterdict = {"position": 0, "width": 1, "area": 2} formats = ["internal", "pwa", "mu_sigma_area"] - default_formats = {"default_input": "internal", "default_output": "pwa"} + default_formats = { + "default_input": "internal", + "default_output": "pwa", + } metadict = {} metadict["maxwidth"] = (maxwidth, repr) - PeakFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) + PeakFunction.__init__( + self, + parameterdict, + formats, + default_formats, + metadict, + None, + Cache, + ) if maxwidth <= 0: emsg = "'maxwidth' must be greater than 0." @@ -123,7 +134,17 @@ def estimate_parameters(self, r, y): else: sigma_left = np.sqrt( -0.5 - * np.mean(np.abs(np.array([use_r[0] - guesspars[0], use_r[-1] - guesspars[0]]))) ** 2 + * np.mean( + np.abs( + np.array( + [ + use_r[0] - guesspars[0], + use_r[-1] - guesspars[0], + ] + ) + ) + ) + ** 2 / np.log(min_y / max_y) ) if use_y[-1] < max_y: @@ -131,7 +152,17 @@ def estimate_parameters(self, r, y): else: sigma_right = np.sqrt( -0.5 - * np.mean(np.abs(np.array([use_r[0] - guesspars[0], use_r[-1] - guesspars[0]]))) ** 2 + * np.mean( + np.abs( + np.array( + [ + use_r[0] - guesspars[0], + use_r[-1] - guesspars[0], + ] + ) + ) + ) + ** 2 / np.log(min_y / max_y) ) guesspars[1] = 0.5 * (sigma_right + sigma_left) * self.sigma2fwhm diff --git a/src/diffpy/srmise/peaks/terminationripples.py b/src/diffpy/srmise/peaks/terminationripples.py index e135083..0e4ddba 100644 --- a/src/diffpy/srmise/peaks/terminationripples.py +++ b/src/diffpy/srmise/peaks/terminationripples.py @@ -61,7 +61,15 @@ def __init__(self, base, qmax, extension=4.0, supersample=5.0, Cache=None): metadict["qmax"] = (qmax, repr) metadict["extension"] = (extension, repr) metadict["supersample"] = (supersample, repr) - PeakFunction.__init__(self, parameterdict, formats, default_formats, metadict, base, Cache) + PeakFunction.__init__( + self, + parameterdict, + formats, + default_formats, + metadict, + base, + Cache, + ) return # Methods required by PeakFunction #### diff --git a/src/diffpy/srmise/peakstability.py b/src/diffpy/srmise/peakstability.py index f95774b..ebeede1 100644 --- a/src/diffpy/srmise/peakstability.py +++ b/src/diffpy/srmise/peakstability.py @@ -146,7 +146,14 @@ def setcurrent(self, idx): self.ppe.setvars(quiet=True, effective_dy=result[0] * np.ones(len(self.ppe.x))) (r, y, dr, dy) = self.ppe.resampledata(result[3]) self.ppe.extracted = ModelCluster( - result[1], result[2], r, y, dy, None, self.ppe.error_method, self.ppe.pf + result[1], + result[2], + r, + y, + dy, + None, + self.ppe.error_method, + self.ppe.pf, ) else: self.ppe.clearcalc() diff --git a/src/diffpy/srmise/srmise_app.py b/src/diffpy/srmise/srmise_app.py new file mode 100644 index 0000000..abd2aec --- /dev/null +++ b/src/diffpy/srmise/srmise_app.py @@ -0,0 +1,33 @@ +import argparse + +from diffpy.srmise.version import __version__ # noqa + + +def main(): + parser = argparse.ArgumentParser( + prog="diffpy.srmise", + description=( + "Peak extraction and peak fitting tool for atomic pair distribution functions.\n\n" + "For more information, visit: " + "https://github.com/diffpy/diffpy.srmise/" + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + parser.add_argument( + "--version", + action="store_true", + help="Show the program's version number and exit", + ) + + args = parser.parse_args() + + if args.version: + print(f"diffpy.srmise {__version__}") + else: + # Default behavior when no arguments are given + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/src/diffpy/srmise/version.py b/src/diffpy/srmise/version.py index 1b97580..06f3a18 100644 --- a/src/diffpy/srmise/version.py +++ b/src/diffpy/srmise/version.py @@ -1,13 +1,13 @@ #!/usr/bin/env python ############################################################################## # -# (c) 2025 The Trustees of Columbia University in the City of New York. +# (c) 2026 The Trustees of Columbia University in the City of New York. # All rights reserved. # # File coded by: Simon Billinge, Billinge Group members. # # See GitHub contributions for a more detailed list of contributors. -# https://github.com/diffpy/diffpy.srmise/graphs/contributors +# https://github.com/diffpy/diffpy.srmise/graphs/contributors # noqa: E501 # # See LICENSE.rst for license information. # @@ -18,8 +18,9 @@ # __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] # obtain version information -from importlib.metadata import version +from importlib.metadata import PackageNotFoundError, version -__version__ = version("diffpy.srmise") - -# End of file +try: + __version__ = version("diffpy.srmise") +except PackageNotFoundError: + __version__ = "unknown" diff --git a/tests/test_version.py b/tests/test_version.py index 3ac5816..06a0a7c 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,6 +1,6 @@ """Unit tests for __version__.py.""" -import diffpy.srmise +import diffpy.srmise # noqa def test_package_version(): From 195ee488866f5e5e9d785cb48a514730583038e3 Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Fri, 3 Apr 2026 16:48:57 -0400 Subject: [PATCH 2/3] chore: fix long description in readme and license info --- README.rst | 46 ++++++++++++++++++++++++++++++++++++----- docs/source/license.rst | 21 +++++++++++++++++-- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 2327cff..46a026a 100644 --- a/README.rst +++ b/README.rst @@ -36,18 +36,54 @@ .. |Tracking| image:: https://img.shields.io/badge/issue_tracking-github-blue :target: https://github.com/diffpy/diffpy.srmise/issues -Peak extraction and peak fitting tool for atomic pair distribution functions. - -* LONGER DESCRIPTION HERE +Implementation of the ParSCAPE algorithm for peak extraction from atomic pair distribution functions (PDFs) + +SrMise is an implementation of the `ParSCAPE algorithm +`_ for peak extraction from +atomic pair distribution functions (PDFs). It is designed to function even +when *a priori* knowledge of the physical sample is limited, utilizing the +Akaike Information Criterion (AIC) to estimate whether peaks are +statistically justified relative to alternate models. Three basic use cases +are anticipated for SrMise. The first is peak fitting a user-supplied +collections of peaks. The second is peak extraction from a PDF with no (or +only partial) user-supplied peaks. The third is an AIC-driven multimodeling +analysis where the output of multiple SrMise trials are ranked. + +The framework for peak extraction defines peak-like clusters within the data, +extracts a single peak within each cluster, and iteratively combines nearby +clusters while performing a recursive search on the residual to identify +occluded peaks. Eventually this results in a single global cluster +containing many peaks fit over all the data. Over- and underfitting are +discouraged by use of the AIC when adding or, during a pruning step, removing +peaks. Termination effects, which can lead to physically spurious peaks in +the PDF, are incorporated in the mathematical peak model and the pruning step +attempts to remove peaks which are fit better as termination ripples due to +another peak. + +Where possible, SrMise provides physically reasonable default values +for extraction parameters. However, the PDF baseline should be estimated by +the user before extraction, or by performing provisional peak extraction with +varying baseline parameters. The package defines a linear (crystalline) +baseline, arbitrary polynomial baseline, a spherical nanoparticle baseline, +and an arbitrary baseline interpolated from a list of user-supplied values. +In addition, PDFs with accurate experimentally-determined uncertainties are +necessary to provide the most reliable results, but historically such PDFs +are rare. In the absence of accurate uncertainties an *ad hoc* uncertainty +must be specified. For more information about the diffpy.srmise library, please consult our `online documentation `_. Citation -------- -If you use diffpy.srmise in a scientific publication, we would like you to cite this package as +If you use this program for a scientific research that leads +to publication, we ask that you acknowledge use of the program +by citing the following paper in your publication: - diffpy.srmise Package, https://github.com/diffpy/diffpy.srmise + L. Granlund, S. J. L. Billinge and P. M. Duxbury, + `Algorithm for systematic peak extraction from atomic pair distribution functions + `__, + *Acta Crystallogr. A* **4**, 392-409 (2015). Installation ------------ diff --git a/docs/source/license.rst b/docs/source/license.rst index 7428e28..ad516ee 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -9,8 +9,25 @@ OPEN SOURCE LICENSE AGREEMENT ============================= BSD 3-Clause License -Copyright (c) 2026, The Trustees of Columbia University in the City of New York. -All Rights Reserved. +Copyright 2014-2015, Board of Trustees of Michigan State University + +Copyright 2016-2026, The Trustees of Columbia University in the City of New York. + +All rights reserved. + +If you use this program to do productive scientific research that +leads to publication, we ask that you acknowledge use of the +program by citing the following paper in your publication: + + L. Granlund, S.J.L. Billinge, P.M. Duxbury, Algorithm for + systematic peak extraction from atomic pair distribution + functions, Acta Crystallographica A 71(4), 392-409 (2015). + doi:10.1107/S2053273315005276 + +For more information please visit the diffpy web-page at + http://www.diffpy.org +or email Luke Granlund at luke.r.granlund@gmail.com, or Prof. Simon +Billinge at sb2896@columbia.edu. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: From 9b89c1db21a0d6b9bc5f289a2fb8253236d53921 Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Sat, 4 Apr 2026 09:58:47 -0400 Subject: [PATCH 3/3] chore: add new license holders and clean workflow --- .../matrix-and-codecov-on-merge-to-main.yml | 21 ------------------- .github/workflows/publish-docs-on-release.yml | 12 ----------- LICENSE-PDFgui.rst | 1 + LICENSE.rst | 1 + README.rst | 4 ++-- docs/source/license.rst | 4 +++- 6 files changed, 7 insertions(+), 36 deletions(-) delete mode 100644 .github/workflows/matrix-and-codecov-on-merge-to-main.yml delete mode 100644 .github/workflows/publish-docs-on-release.yml diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml deleted file mode 100644 index 9f677b4..0000000 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: CI - -on: - push: - branches: - - main - release: - types: - - prereleased - - published - workflow_dispatch: - -jobs: - matrix-coverage: - uses: scikit-package/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 - with: - project: diffpy.srmise - c_extension: false - headless: false - secrets: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/publish-docs-on-release.yml b/.github/workflows/publish-docs-on-release.yml deleted file mode 100644 index 6df1afc..0000000 --- a/.github/workflows/publish-docs-on-release.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Deploy Documentation on Release - -on: - workflow_dispatch: - -jobs: - docs: - uses: scikit-package/release-scripts/.github/workflows/_publish-docs-on-release.yml@v0 - with: - project: diffpy.srmise - c_extension: false - headless: false diff --git a/LICENSE-PDFgui.rst b/LICENSE-PDFgui.rst index 3ba9b69..27488fe 100644 --- a/LICENSE-PDFgui.rst +++ b/LICENSE-PDFgui.rst @@ -2,6 +2,7 @@ BSD 3-Clause License Copyright 2006-2007, Board of Trustees of Michigan State University 2008-2024, The Trustees of Columbia University in the City of New York. + 2026-present, The Contributors to the diffpy.pdfgui project. All rights reserved. SrMise incorporates source code from diffpy.pdfgui in the file diff --git a/LICENSE.rst b/LICENSE.rst index 3675628..3c8ea03 100644 --- a/LICENSE.rst +++ b/LICENSE.rst @@ -2,6 +2,7 @@ BSD 3-Clause License Copyright 2014-2015, Board of Trustees of Michigan State University 2016-2024, The Trustees of Columbia University in the City of New York. + 2026-present, The Contributors to the diffpy.srmise project. All rights reserved. If you use this program to do productive scientific research that diff --git a/README.rst b/README.rst index 46a026a..68ee72e 100644 --- a/README.rst +++ b/README.rst @@ -15,8 +15,8 @@ .. |Black| image:: https://img.shields.io/badge/code_style-black-black :target: https://github.com/psf/black -.. |CI| image:: https://github.com/diffpy/diffpy.srmise/actions/workflows/matrix-and-codecov-on-merge-to-main.yml/badge.svg - :target: https://github.com/diffpy/diffpy.srmise/actions/workflows/matrix-and-codecov-on-merge-to-main.yml +.. |CI| image:: https://github.com/diffpy/diffpy.srmise/actions/workflows/matrix-and-codecov.yml/badge.svg + :target: https://github.com/diffpy/diffpy.srmise/actions/workflows/matrix-and-codecov.yml .. |Codecov| image:: https://codecov.io/gh/diffpy/diffpy.srmise/branch/main/graph/badge.svg :target: https://codecov.io/gh/diffpy/diffpy.srmise diff --git a/docs/source/license.rst b/docs/source/license.rst index ad516ee..e37f8fb 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -11,7 +11,9 @@ BSD 3-Clause License Copyright 2014-2015, Board of Trustees of Michigan State University -Copyright 2016-2026, The Trustees of Columbia University in the City of New York. +Copyright 2016-2025, The Trustees of Columbia University in the City of New York. + +Copyright (c) 2026-present, The Contributors to the diffpy.srmise project. All rights reserved.