diff --git a/.flake8 b/.flake8 index 2d2cb16..a510511 100644 --- a/.flake8 +++ b/.flake8 @@ -1,10 +1,12 @@ +# As of now, flake8 does not natively support configuration via pyproject.toml +# https://github.com/microsoft/vscode-flake8/issues/135 [flake8] exclude = .git, __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..56bcd01 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -34,7 +34,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-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index 5d80dac..5dec5f6 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -7,7 +7,7 @@ on: - "*" # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml jobs: - release: + build-release: uses: scikit-package/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 with: project: diffpy.pdffit2 diff --git a/.isort.cfg b/.isort.cfg index e0926f4..7ce0fb1 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,4 +1,5 @@ [settings] +# Keep import statement below line_length character limit line_length = 115 multi_line_output = 3 include_trailing_comma = True 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/CODE-OF-CONDUCT.rst b/CODE-OF-CONDUCT.rst new file mode 100644 index 0000000..e8199ca --- /dev/null +++ b/CODE-OF-CONDUCT.rst @@ -0,0 +1,133 @@ +===================================== + Contributor Covenant Code of Conduct +===================================== + +Our Pledge +---------- + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socioeconomic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +Our Standards +------------- + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +Enforcement Responsibilities +---------------------------- + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders 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, and will communicate reasons for moderation +decisions when appropriate. + +Scope +----- + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +Enforcement +----------- + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +sb2896@columbia.edu. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +Enforcement Guidelines +---------------------- + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +1. Correction +**************** + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +2. Warning +************* + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +3. Temporary Ban +****************** + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +4. Permanent Ban +****************** + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +Attribution +----------- + +This Code of Conduct is adapted from the `Contributor Covenant `_. + +Community Impact Guidelines were inspired by `Mozilla's code of conduct enforcement ladder `_. + +For answers to common questions about this code of conduct, see the `FAQ `_. `Translations are available `_ diff --git a/LICENSE.rst b/LICENSE.rst index 040c880..092e90c 100644 --- a/LICENSE.rst +++ b/LICENSE.rst @@ -21,32 +21,26 @@ For more information please visit the project web-page: or email 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: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * 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 COPYRIGHT HOLDER "AS IS". COPYRIGHT HOLDER -EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES AND CONDITIONS, EITHER -EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY, TITLE, FITNESS, ADEQUACY OR SUITABILITY -FOR A PARTICULAR PURPOSE, AND ANY WARRANTIES OF FREEDOM FROM -INFRINGEMENT OF ANY DOMESTIC OR FOREIGN PATENT, COPYRIGHTS, TRADE -SECRETS OR OTHER PROPRIETARY RIGHTS OF ANY PARTY. IN NO EVENT SHALL -COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF -USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE OR RELATING TO THIS AGREEMENT, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + 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. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.rst b/README.rst index 4c90e84..ee3d5df 100644 --- a/README.rst +++ b/README.rst @@ -25,6 +25,7 @@ :target: https://anaconda.org/conda-forge/diffpy.pdffit2 .. |PR| image:: https://img.shields.io/badge/PR-Welcome-29ab47ff + :target: https://github.com/diffpy/diffpy.pdffit2/pulls .. |PyPI| image:: https://img.shields.io/pypi/v/diffpy.pdffit2 :target: https://pypi.org/project/diffpy.pdffit2/ @@ -108,24 +109,31 @@ Create a new conda environment ``diffpy.pdffit2_env``: :: conda create -n diffpy.pdffit2_env python=3.13 -Activate the environment: :: - - conda activate diffpy.pdffit2_env - -Install pdffit2 using ``pip`` to download and install the latest version from `Python Package Index `_: :: +If the above does not work, you can use ``pip`` to download and install the latest release from +`Python Package Index `_. +To install using ``pip`` into your ``diffpy.pdffit2_env`` environment, type :: pip install diffpy.pdffit2 -To confirm that the installation was successful, type :: - - python -c "import diffpy.pdffit2; print(diffpy.pdffit2.__version__)" - If you prefer to install from sources, after installing the dependencies, obtain the source archive from `GitHub `_. Once installed, ``cd`` into your ``diffpy.pdffit2`` directory and run the following :: pip install . +This package also provides command-line utilities. To check the software has been installed correctly, type :: + + diffpy.pdffit2 --version + +You can also type the following command to verify the installation. :: + + python -c "import diffpy.pdffit2; print(diffpy.pdffit2.__version__)" + + +To view the basic usage and available commands, type :: + + diffpy.pdffit2 -h + Getting Started --------------- @@ -170,7 +178,7 @@ 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 ------- diff --git a/cookiecutter.json b/cookiecutter.json new file mode 100644 index 0000000..ce9c9b9 --- /dev/null +++ b/cookiecutter.json @@ -0,0 +1,18 @@ +{ + "maintainer_name": "Simon Billinge", + "maintainer_email": "sb2896@columbia.edu", + "maintainer_github_username": "sbillinge", + "contributors": "Sangjoon Lee, Simon Billinge, Billinge Group members", + "license_holders": "The Trustees of Columbia University in the City of New York", + "project_name": "diffpy.pdffit2", + "github_username_or_orgname": "diffpy", + "github_repo_name": "diffpy.pdffit2", + "conda_pypi_package_dist_name": "diffpy.pdffit2", + "package_dir_name": "diffpy.pdffit2", + "project_short_description": "PDFfit2 - real space structure refinement program.", + "project_keywords": "PDF, structure refinement", + "minimum_supported_python_version": "3.11", + "maximum_supported_python_version": "3.13", + "project_needs_c_code_compiled": "No", + "project_has_gui_tests": "No" +} diff --git a/docs/source/conf.py b/docs/source/conf.py index 58fb11f..37c7cf6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# diffpy.pdffit2 documentation build configuration file, created by +# diffpy.pdffit2 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.pdffit2") 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())) @@ -53,10 +53,6 @@ "m2r", ] -autodoc_mock_imports = [ - "diffpy.pdffit2.pdffit2", -] - # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -79,7 +75,6 @@ # |version| and |release|, also used in various other places throughout the # built documents. -fullversion = version(project) # The short X.Y version. version = "".join(fullversion.split(".post")[:1]) # The full version, including alpha/beta/rc tags. @@ -144,7 +139,7 @@ "github_user": "diffpy", "github_repo": "diffpy.pdffit2", "github_version": "main", - "conf_py_path": "/doc/source/", + "conf_py_path": "/docs/source/", } # Theme options are theme-specific and customize the look and feel of a theme diff --git a/docs/source/examples/Ni_refinement.py b/docs/source/examples/Ni_refinement.py index 67d93a3..d4d5d58 100755 --- a/docs/source/examples/Ni_refinement.py +++ b/docs/source/examples/Ni_refinement.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -"""Perform simple refinement of Ni structure to the experimental x-ray PDF. +"""Perform simple refinement of Ni structure to the experimental x-ray +PDF. Save fitted curve, refined structure and results summary. """ diff --git a/docs/source/img/scikit-package-logo-text.png b/docs/source/img/scikit-package-logo-text.png new file mode 100644 index 0000000..823178d Binary files /dev/null and b/docs/source/img/scikit-package-logo-text.png differ diff --git a/docs/source/license.rst b/docs/source/license.rst index fbdd3ed..5cdf347 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -25,32 +25,26 @@ For more information please visit the project web-page: or email 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: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * 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 COPYRIGHT HOLDER "AS IS". COPYRIGHT HOLDER -EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES AND CONDITIONS, EITHER -EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY, TITLE, FITNESS, ADEQUACY OR SUITABILITY -FOR A PARTICULAR PURPOSE, AND ANY WARRANTIES OF FREEDOM FROM -INFRINGEMENT OF ANY DOMESTIC OR FOREIGN PATENT, COPYRIGHTS, TRADE -SECRETS OR OTHER PROPRIETARY RIGHTS OF ANY PARTY. IN NO EVENT SHALL -COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF -USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE OR RELATING TO THIS AGREEMENT, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + 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. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/news/scikit-update.rst b/news/scikit-update.rst new file mode 100644 index 0000000..cac9396 --- /dev/null +++ b/news/scikit-update.rst @@ -0,0 +1,23 @@ +**Added:** + +* No News Added: scikit-update package + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/pyproject.toml b/pyproject.toml index af52ce0..c587b1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,9 @@ include = ["*"] # package names should match these glob patterns (["*"] by defa exclude = [] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) +[project.scripts] +diffpy-pdffit2 = "diffpy.pdffit2_app:main" + [tool.setuptools.dynamic] dependencies = {file = ["requirements/pip.txt"]} @@ -56,6 +59,11 @@ exclude-file = ".codespell/ignore_lines.txt" ignore-words = ".codespell/ignore_words.txt" skip = "*.cif,*.dat,*.cc,*.h" +[tool.docformatter] +recursive = true +wrap-summaries = 72 +wrap-descriptions = 72 + [tool.black] line-length = 79 include = '\.pyi?$' diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index 2efe939..04d8ace 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -14,13 +14,3 @@ # See LICENSE.rst for license information. # ############################################################################## -"""diffpy - tools for structure analysis by diffraction. - -Blank namespace package for module diffpy.""" - - -from pkgutil import extend_path - -__path__ = extend_path(__path__, __name__) - -# End of file diff --git a/src/diffpy/pdffit2/__init__.py b/src/diffpy/pdffit2/__init__.py index 527477a..110878e 100644 --- a/src/diffpy/pdffit2/__init__.py +++ b/src/diffpy/pdffit2/__init__.py @@ -22,18 +22,17 @@ # isort: off # Import the package version before C++ extensions are loaded. -from diffpy.pdffit2.output import redirect_stdout -from diffpy.pdffit2.version import __date__, __version__ +from diffpy.pdffit2.output import redirect_stdout # noqa # Import C++ related modules since the __version__ attribute is used. -from diffpy.pdffit2.pdffit import PdfFit -from diffpy.pdffit2.pdffit2 import is_element + # isort: on -# Ensure all necessary components are imported and initialized +# package version +from diffpy.pdffit2.version import __version__ # noqa + +# silence the pyflakes syntax checker assert __version__ or True -assert __date__ or True -assert all((PdfFit, redirect_stdout, is_element)) # End of file diff --git a/src/diffpy/pdffit2/ipy_ext.py b/src/diffpy/pdffit2/ipy_ext.py index ac2b547..c21f00d 100644 --- a/src/diffpy/pdffit2/ipy_ext.py +++ b/src/diffpy/pdffit2/ipy_ext.py @@ -1,7 +1,7 @@ #!/usr/bin/env python -"""This module defines functions within IPython session to simulate the old -pdffit2 interactive session. +"""This module defines functions within IPython session to simulate the +old pdffit2 interactive session. Usage: %load_ext diffpy.pdffit2.ipy_ext """ diff --git a/src/diffpy/pdffit2/pdffit.py b/src/diffpy/pdffit2/pdffit.py index 2e7ce74..82c9342 100644 --- a/src/diffpy/pdffit2/pdffit.py +++ b/src/diffpy/pdffit2/pdffit.py @@ -46,7 +46,8 @@ def _format_value_std(value, stdev): def _format_bond_length(dij, ddij, ij1, symij): - """Return string with formatted bond length info for a pair of atoms. + """Return string with formatted bond length info for a pair of + atoms. dij -- distance between atoms i and j ddij -- standard deviation of dij. Ignored when small relative to dij. @@ -123,8 +124,8 @@ class PdfFit(object): Sctp = {"X": 0, "N": 1} def _exportAll(self, namespace): - """_exportAll(self, namespace) --> Export all 'public' class methods - into namespace. + """_exportAll(self, namespace) --> Export all 'public' class + methods into namespace. This function allows for a module-level PdfFit object which doesn't have to be referenced when calling a method. This @@ -193,8 +194,8 @@ def read_struct(self, struct): return def read_struct_string(self, struct, name=""): - """read_struct_string(struct, name = "") --> Read structure from a - string into memory. + """read_struct_string(struct, name = "") --> Read structure from + a string into memory. struct -- string containing the contents of the structure file name -- tag with which to label structure @@ -209,8 +210,8 @@ def read_struct_string(self, struct, name=""): return def read_data(self, data, stype, qmax, qdamp): - """read_data(data, stype, qmax, qdamp) --> Read pdf data from file into - memory. + """read_data(data, stype, qmax, qdamp) --> Read pdf data from + file into memory. data -- name of file from which to read data stype -- 'X' (xray) or 'N' (neutron) @@ -225,8 +226,8 @@ def read_data(self, data, stype, qmax, qdamp): return def read_data_string(self, data, stype, qmax, qdamp, name=""): - """read_data_string(data, stype, qmax, qdamp, name = "") --> Read pdf - data from a string into memory. + """read_data_string(data, stype, qmax, qdamp, name = "") --> + Read pdf data from a string into memory. data -- string containing the contents of the data file stype -- 'X' (xray) or 'N' (neutron) @@ -245,8 +246,8 @@ def read_data_string(self, data, stype, qmax, qdamp, name=""): def read_data_lists( self, stype, qmax, qdamp, r_data, Gr_data, dGr_data=None, name="list" ): - """read_data_lists(stype, qmax, qdamp, r_data, Gr_data, dGr_data = - None, name = "list") --> Read pdf data into memory from lists. + """read_data_lists(stype, qmax, qdamp, r_data, Gr_data, dGr_data + = None, name = "list") --> Read pdf data into memory from lists. All lists must be of the same length. stype -- 'X' (xray) or 'N' (neutron) @@ -286,15 +287,16 @@ def pdfrange(self, iset, rmin, rmax): return def reset(self): - """Reset() --> Clear all stored fit, structure, and parameter data.""" + """Reset() --> Clear all stored fit, structure, and parameter + data.""" self.stru_files = [] self.data_files = [] pdffit2.reset(self._handle) return def alloc(self, stype, qmax, qdamp, rmin, rmax, bin): - """Alloc(stype, qmax, qdamp, rmin, rmax, bin) --> Allocate space for a - PDF calculation. + """Alloc(stype, qmax, qdamp, rmin, rmax, bin) --> Allocate space + for a PDF calculation. The structure from which to calculate the PDF must first be imported with the read_struct() or read_struct_string() method. @@ -331,7 +333,8 @@ def calc(self): return def refine(self, toler=0.00000001): - """Refine(toler = 0.00000001) --> Fit the theory to the imported data. + """Refine(toler = 0.00000001) --> Fit the theory to the imported + data. toler -- tolerance of the fit @@ -350,7 +353,8 @@ def refine(self, toler=0.00000001): return def refine_step(self, toler=0.00000001): - """refine_step(toler = 0.00000001) --> Run a single step of the fit. + """refine_step(toler = 0.00000001) --> Run a single step of the + fit. toler -- tolerance of the fit @@ -367,7 +371,8 @@ def refine_step(self, toler=0.00000001): return self.finished def save_pdf(self, iset, fname): - """save_pdf(iset, fname) --> Save calculated or fitted PDF to file. + """save_pdf(iset, fname) --> Save calculated or fitted PDF to + file. iset -- data set to save @@ -379,7 +384,8 @@ def save_pdf(self, iset, fname): return def save_pdf_string(self, iset): - """save_pdf_string(iset) --> Save calculated or fitted PDF to string. + """save_pdf_string(iset) --> Save calculated or fitted PDF to + string. iset -- data set to save @@ -392,8 +398,8 @@ def save_pdf_string(self, iset): return pdffilestring def save_dif(self, iset, fname): - """save_dif(iset, fname) --> Save data and fitted PDF difference to - file. + """save_dif(iset, fname) --> Save data and fitted PDF difference + to file. iset -- data set to save @@ -405,8 +411,8 @@ def save_dif(self, iset, fname): return def save_dif_string(self, iset): - """save_dif_string(iset) --> Save data and fitted PDF difference to - string. + """save_dif_string(iset) --> Save data and fitted PDF difference + to string. iset -- data set to save @@ -455,8 +461,8 @@ def get_structure(self, ip): return stru def save_struct(self, ip, fname): - """save_struct(ip, fname) --> Save structure resulting from fit to - file. + """save_struct(ip, fname) --> Save structure resulting from fit + to file. ip -- phase to save @@ -468,7 +474,8 @@ def save_struct(self, ip, fname): return def save_struct_string(self, ip): - """save_struct(ip) --> Save structure resulting from fit to string. + """save_struct(ip) --> Save structure resulting from fit to + string. ip -- phase to save @@ -491,7 +498,8 @@ def show_struct(self, ip): return def constrain(self, var, par, fcon=None): - """Constrain(var, par[, fcon]) --> Constrain a variable to a parameter. + """Constrain(var, par[, fcon]) --> Constrain a variable to a + parameter. A variable can be constrained to a number or equation string. var -- variable to constrain, such as x(1) @@ -646,7 +654,8 @@ def getpdf_diff(self): return Gdiff def get_atoms(self, ip=None): - """get_atoms() --> Get element symbols of all atoms in the structure. + """get_atoms() --> Get element symbols of all atoms in the + structure. ip -- index of phase to get the elements from (starting from 1) when ip is not given, use current phase @@ -752,7 +761,8 @@ def psel(self, ip): return def pdesel(self, ip): - """Pdesel(ip) --> Exclude phase ip from calculation of total PDF. + """Pdesel(ip) --> Exclude phase ip from calculation of total + PDF. pdesel('ALL') excludes all phases from PDF calculation. @@ -849,9 +859,9 @@ def bang(self, i, j, k): return def bond_angle(self, i, j, k): - """bond_angle(i, j, k) --> bond angle defined by atoms i, j, k. Angle - is calculated using the shortest ji and jk lengths with respect to - periodic boundary conditions. + """bond_angle(i, j, k) --> bond angle defined by atoms i, j, k. + Angle is calculated using the shortest ji and jk lengths with + respect to periodic boundary conditions. i, j, k -- atom indices starting at 1 @@ -940,8 +950,9 @@ def blen(self, *args): return def bond_length_atoms(self, i, j): - """bond_length_atoms(i, j) --> shortest distance between atoms i, j. - Periodic boundary conditions are applied to find the shortest bond. + """bond_length_atoms(i, j) --> shortest distance between atoms + i, j. Periodic boundary conditions are applied to find the + shortest bond. i -- index of the first atom starting at 1 j -- index of the second atom starting at 1 @@ -955,7 +966,8 @@ def bond_length_atoms(self, i, j): return rv def bond_length_types(self, a1, a2, lb, ub): - """bond_length_types(a1, a2, lb, ub) --> get all a1-a2 distances. + """bond_length_types(a1, a2, lb, ub) --> get all a1-a2 + distances. a1 -- symbol of the first element in pair or "ALL" a2 -- symbol of the second element in pair or "ALL" @@ -976,8 +988,8 @@ def bond_length_types(self, a1, a2, lb, ub): return rv def show_scat(self, stype): - """show_scat(stype) --> Print scattering length for all atoms in the - current phase. + """show_scat(stype) --> Print scattering length for all atoms in + the current phase. stype -- 'X' (xray) or 'N' (neutron). @@ -987,8 +999,8 @@ def show_scat(self, stype): return def get_scat_string(self, stype): - """get_scat_string(stype) --> Get string with scattering factors of all - atoms in the current phase. + """get_scat_string(stype) --> Get string with scattering factors + of all atoms in the current phase. stype -- 'X' (xray) or 'N' (neutron). @@ -1000,10 +1012,10 @@ def get_scat_string(self, stype): return pdffit2.get_scat_string(self._handle, stype.encode()) def get_scat(self, stype, element): - """get_scat(stype, element) --> Get active scattering factor for given - element. If scattering factor has been changed using set_scat the - result may depend on the active phase. When no phase has been loaded, - return the standard value. + """get_scat(stype, element) --> Get active scattering factor for + given element. If scattering factor has been changed using + set_scat the result may depend on the active phase. When no + phase has been loaded, return the standard value. stype -- 'X' (xray) or 'N' (neutron). element -- case-insensitive element symbol such as "Na" or "CL" @@ -1017,9 +1029,10 @@ def get_scat(self, stype, element): return rv def set_scat(self, stype, element, value): - """set_scat(stype, element, value) --> Set custom scattering factor for - given element. The new scattering factor applies only for the current - phase, in other phases it keeps its default value. + """set_scat(stype, element, value) --> Set custom scattering + factor for given element. The new scattering factor applies + only for the current phase, in other phases it keeps its default + value. stype -- 'X' (xray) or 'N' (neutron). element -- case-insensitive element symbol such as "Na" or "CL" @@ -1037,9 +1050,9 @@ def set_scat(self, stype, element, value): return def reset_scat(self, element): - """reset_scat(stype, element) --> Reset scattering factors for given - element to their standard values. The reset_scat applies only for the - current phase. + """reset_scat(stype, element) --> Reset scattering factors for + given element to their standard values. The reset_scat applies + only for the current phase. element -- case-insensitive element symbol such as "Na" or "CL" Raises: @@ -1067,7 +1080,8 @@ def num_phases(self): return n def num_datasets(self): - """num_datasets() --> Number of datasets loaded in PdfFit instance. + """num_datasets() --> Number of datasets loaded in PdfFit + instance. Use setdata to bring a specific dataset in focus. @@ -1077,9 +1091,9 @@ def num_datasets(self): return n def phase_fractions(self): - """phase_fractions() --> relative phase fractions for current dataset. - Convert phase scale factors to relative phase fractions given the - scattering type of current dataset. + """phase_fractions() --> relative phase fractions for current + dataset. Convert phase scale factors to relative phase fractions + given the scattering type of current dataset. Return a dictionary of phase fractions with following keys: @@ -1252,7 +1266,8 @@ def qbroad(): qbroad = staticmethod(qbroad) def spdiameter(): - """Spdiameter() --> Get reference to spdiameter (phase property). + """Spdiameter() --> Get reference to spdiameter (phase + property). Diameter value for the spherical particle PDF correction. Spherical envelope is not applied when spdiameter equals 0. @@ -1305,7 +1320,8 @@ def __init__(self, create_intro=True): return def __getRef(self, var_string): - """Return the actual reference to the variable in the var_string. + """Return the actual reference to the variable in the + var_string. This function must be called before trying to actually reference an internal variable. See the constrain method for an example. diff --git a/src/diffpy/pdffit2/pdffit2_app.py b/src/diffpy/pdffit2/pdffit2_app.py new file mode 100644 index 0000000..d8ff2fd --- /dev/null +++ b/src/diffpy/pdffit2/pdffit2_app.py @@ -0,0 +1,33 @@ +import argparse + +from diffpy.pdffit2.version import __version__ # noqa + + +def main(): + parser = argparse.ArgumentParser( + prog="diffpy.pdffit2", + description=( + "PDFfit2 - real space structure refinement program.\n\n" + "For more information, visit: " + "https://github.com/diffpy/diffpy.pdffit2/" + ), + 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.pdffit2 {__version__}") + else: + # Default behavior when no arguments are given + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/src/diffpy/pdffit2/version.py b/src/diffpy/pdffit2/version.py index 8615928..7b2b8dd 100644 --- a/src/diffpy/pdffit2/version.py +++ b/src/diffpy/pdffit2/version.py @@ -4,62 +4,23 @@ # (c) 2025 The Trustees of Columbia University in the City of New York. # All rights reserved. # -# File coded by: Billinge Group members and community contributors. +# File coded by: Sangjoon Lee, Simon Billinge, Billinge Group members. # # See GitHub contributions for a more detailed list of contributors. -# https://github.com/diffpy/diffpy.pdffit2/graphs/contributors +# https://github.com/diffpy/diffpy.pdffit2/graphs/contributors # noqa: E501 # # See LICENSE.rst for license information. # ############################################################################## """Definition of __version__.""" -import datetime -import json -import urllib.request -from importlib.metadata import version -from pathlib import Path +# We do not use the other three variables, but can be added back if needed. +# __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] +# obtain version information +from importlib.metadata import PackageNotFoundError, version -def get_pypi_release_date(package_name, timeout=5): - package_file = Path(__file__).resolve() - - try: - with open(package_file, "r", encoding="utf-8") as f: - lines = f.readlines() - for line in reversed(lines): - if line.startswith("# Release date:"): - return line.split(":", 1)[1].strip() - - url = f"https://pypi.org/pypi/{package_name}/json" - with urllib.request.urlopen(url, timeout=timeout) as response: - data = json.loads(response.read().decode("utf-8")) - - installed_version = version(package_name) - release_data = data["releases"].get(installed_version, []) - if not release_data: - raise ValueError( - f"No release data found for version {installed_version}" - ) - - release_date_str = release_data[-1]["upload_time"] - release_date = datetime.datetime.fromisoformat(release_date_str).date() - - with open(package_file, "a", encoding="utf-8") as f: - f.write(f"\n# Release date: {release_date}") - - except (ValueError, OSError) as e: - print(f"Warning: Could not fetch release date from PyPI: {e}") - release_date = datetime.datetime.fromtimestamp( - package_file.stat().st_ctime - ).isoformat() - - return str(release_date) - - -__version__ = version("diffpy.pdffit2") -__date__ = get_pypi_release_date("diffpy.pdffit2") - -# End of file - -# Release date: 2025-02-07 +try: + __version__ = version("diffpy.pdffit2") +except PackageNotFoundError: + __version__ = "unknown" diff --git a/tests/conftest.py b/tests/conftest.py index 6a14253..e3b6313 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,8 @@ -import io import json from pathlib import Path import pytest -import diffpy.pdffit2 -import diffpy.pdffit2.output # assuming this is the correct import path - @pytest.fixture def user_filesystem(tmp_path): @@ -21,30 +17,3 @@ def user_filesystem(tmp_path): json.dump(home_config_data, f) yield tmp_path - - -@pytest.fixture -def datafile(): - """Fixture to dynamically load any test file.""" - - def _load(filename): - return "tests/testdata/" + filename - - return _load - - -@pytest.fixture -def capture_output(): - """Capture output from pdffit2 engine produced in function call.""" - - def _capture(f, *args, **kwargs): - savestdout = diffpy.pdffit2.output.stdout - fp = io.StringIO() - diffpy.pdffit2.redirect_stdout(fp) - try: - f(*args, **kwargs) - finally: - diffpy.pdffit2.redirect_stdout(savestdout) - return fp.getvalue() - - return _capture diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 83f2b37..1f697c9 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -45,7 +45,8 @@ def test_structureError(self): ) def test_structureErrorZeroVolume(self): - """Raise pdffit2.structureError when unit cell volume is negative.""" + """Raise pdffit2.structureError when unit cell volume is + negative.""" # I don't know how to test for this, but it's in the library code self.assertRaises( pdffit2.structureError, @@ -311,7 +312,8 @@ def tearDown(self): del self.P def test_unassignedError(self): - """Raise pdffit2.unassignedError when no space has been allocated.""" + """Raise pdffit2.unassignedError when no space has been + allocated.""" self.assertRaises(pdffit2.unassignedError, self.P.calc) @@ -646,11 +648,13 @@ def tearDown(self): del self.P def test_unassignedError1(self): - """Raise pdffit2.unassignedError when parameter does not exist.""" + """Raise pdffit2.unassignedError when parameter does not + exist.""" self.assertRaises(pdffit2.unassignedError, self.P.getpar, 1) def test_unassignedError2(self): - """Raise pdffit2.unassignedError when parameter does not exist.""" + """Raise pdffit2.unassignedError when parameter does not + exist.""" self.P.read_struct(self.datafile("Ni.stru")) self.P.constrain(self.P.lat(1), 2) self.assertRaises(pdffit2.unassignedError, self.P.getpar, 1) @@ -984,7 +988,8 @@ def tearDown(self): del self.P def test_unassignedError(self): - """Raise pdffit2.unassignedError when parameter does not exist.""" + """Raise pdffit2.unassignedError when parameter does not + exist.""" self.P.read_struct(self.datafile("Ni.stru")) self.P.read_data(self.datafile("Ni.dat"), "X", 25.0, 0.0) self.assertRaises(pdffit2.unassignedError, self.P.fixpar, 1) @@ -1002,7 +1007,8 @@ def tearDown(self): del self.P def test_unassignedError(self): - """Raise pdffit2.unassignedError when parameter does not exist.""" + """Raise pdffit2.unassignedError when parameter does not + exist.""" self.P.read_struct(self.datafile("Ni.stru")) self.P.read_data(self.datafile("Ni.dat"), "X", 25.0, 0.0) self.assertRaises(pdffit2.unassignedError, self.P.freepar, 1) @@ -1038,7 +1044,8 @@ def tearDown(self): del self.P def test_unassignedError(self): - """Raise pdffit2.unassignedError when data set does not exist.""" + """Raise pdffit2.unassignedError when data set does not + exist.""" self.P.read_struct(self.datafile("Ni.stru")) self.P.read_data(self.datafile("Ni.dat"), "X", 25.0, 0.0) self.assertRaises(pdffit2.unassignedError, self.P.setdata, 2) diff --git a/tests/test_pdffit.py b/tests/test_pdffit.py index 7336f0e..d467d4a 100644 --- a/tests/test_pdffit.py +++ b/tests/test_pdffit.py @@ -340,7 +340,8 @@ def test_getcrw(self): return def test_getcrw_two_datasets(self): - """Check that getcrw() and getrw() are consistent for two datasets.""" + """Check that getcrw() and getrw() are consistent for two + datasets.""" self.P.read_data(self.datafile("Ni.dat"), "X", 25.0, 0.0) self.P.pdfrange(1, 2, 8) self.P.read_data(self.datafile("300K.gr"), "N", 32.0, 0.0) diff --git a/tests/test_shape_factors.py b/tests/test_shape_factors.py index 110ce55..c57ebd3 100644 --- a/tests/test_shape_factors.py +++ b/tests/test_shape_factors.py @@ -82,7 +82,8 @@ def test_refinement(self): return def test_twophase_calculation(self): - """Check PDF calculation for 2 phases with different spdiameters.""" + """Check PDF calculation for 2 phases with different + spdiameters.""" d1 = 6 d2 = 9 self.P.read_struct(self.datafile("Ni.stru")) @@ -113,7 +114,8 @@ def test_twophase_calculation(self): return def test_twophase_refinement(self): - """Check PDF refinement of 2 phases that have different spdiameter.""" + """Check PDF refinement of 2 phases that have different + spdiameter.""" dcheck1 = 8.0 dstart1 = 8.2 dcheck2 = 6.0 @@ -152,7 +154,8 @@ def test_twophase_refinement(self): return def test_spdiameter_io(self): - """Check reading and writing of spdiameter from structure file.""" + """Check reading and writing of spdiameter from structure + file.""" import re self.P.read_struct(self.datafile("Ni.stru")) @@ -217,7 +220,8 @@ def test_stepcut_calculation(self): return def test_twophase_stepcut_calculation(self): - """Check PDF calculation for 2 phases with different spdiameters.""" + """Check PDF calculation for 2 phases with different + spdiameters.""" d1 = 6 d2 = 9 self.P.read_struct(self.datafile("Ni.stru")) diff --git a/tests/test_version.py b/tests/test_version.py index b9b25b0..a232202 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,6 +1,6 @@ """Unit tests for __version__.py.""" -import diffpy.pdffit2 +import diffpy.pdffit2 # noqa def test_package_version():