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..72b4795 100644 --- a/LICENSE.rst +++ b/LICENSE.rst @@ -24,29 +24,24 @@ 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. +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..ba8cb35 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/ @@ -97,34 +98,32 @@ The following creates and activates a new environment named ``diffpy.pdffit2_env conda create -n diffpy.pdffit2_env diffpy.pdffit2 conda activate diffpy.pdffit2_env -To confirm that the installation was successful, type :: +The output should print the latest version displayed on the badges above. - python -c "import diffpy.pdffit2; print(diffpy.pdffit2.__version__)" - -macOS (Arm64) -~~~~~~~~~~~~~ +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 :: -Create a new conda environment ``diffpy.pdffit2_env``: :: - - conda create -n diffpy.pdffit2_env python=3.13 + pip install diffpy.pdffit2 -Activate the environment: :: +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 :: - conda activate diffpy.pdffit2_env + pip install . -Install pdffit2 using ``pip`` to download and install the latest version from `Python Package Index `_: :: +This package also provides command-line utilities. To check the software has been installed correctly, type :: - pip install diffpy.pdffit2 + diffpy.pdffit2 --version -To confirm that the installation was successful, type :: +You can also type the following command to verify the installation. :: 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 . +To view the basic usage and available commands, type :: + + diffpy.pdffit2 -h Getting Started --------------- @@ -170,7 +169,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..9fad4e4 --- /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": "Yes", + "project_has_gui_tests": "No" +} diff --git a/docs/source/conf.py b/docs/source/conf.py index 58fb11f..480243c 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())) @@ -144,7 +144,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..4dfc0a1 --- /dev/null +++ b/news/scikit-update.rst @@ -0,0 +1,23 @@ +**Added:** + +* No News Added: Update package to new scikit-package standard + +**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/setup.py b/setup.py index 9a334a8..2e4c618 100644 --- a/setup.py +++ b/setup.py @@ -1,199 +1,27 @@ #!/usr/bin/env python # Extensions script for diffpy.pdffit2 -"""PDFfit2 - real space structure refinement engine - -Packages: diffpy.pdffit2 -Scripts: pdffit2 -""" import glob import os import re -import shutil import sys -import warnings -from pathlib import Path from setuptools import Extension, setup -from setuptools.command.build_ext import build_ext - -# Use this version when git data are not available, like in git zip archive. -# Update when tagging a new release. -FALLBACK_VERSION = "1.4.3" - -MYDIR = str(Path(__file__).parent.resolve()) - -# Helper functions ----------------------------------------------------------- - - -def get_compiler_type(): - """Return the compiler type used during the build.""" - cc_arg = [a for a in sys.argv if a.startswith("--compiler=")] - if cc_arg: - return cc_arg[-1].split("=", 1)[1] - from distutils.ccompiler import new_compiler - - return new_compiler().compiler_type - - -def get_gsl_config(): - """ - Determine the GSL include and library directories by trying in order: - 1) CONDA_PREFIX, - 2) GSL_PATH, - 3) gsl-config (for Unix-like systems). - Raises EnvironmentError if none are found. - """ - rv = {"include_dirs": [], "library_dirs": []} - - # 1. Check using CONDA_PREFIX. - conda_prefix = os.environ.get("CONDA_PREFIX", "") - if conda_prefix: - if os.name == "nt": - inc = Path(conda_prefix) / "Library" / "include" - lib = Path(conda_prefix) / "Library" / "lib" - else: - inc = Path(conda_prefix) / "include" - lib = Path(conda_prefix) / "lib" - if inc.is_dir() and lib.is_dir(): - rv["include_dirs"].append(str(inc)) - rv["library_dirs"].append(str(lib)) - return rv - else: - warnings.warn( - f"CONDA_PREFIX is set to {conda_prefix}, " - "but GSL not found at those paths. Proceeding..." - ) - # 2. Check using GSL_PATH. - gsl_path = os.environ.get("GSL_PATH", "") - if gsl_path: - inc = Path(gsl_path) / "include" - lib = Path(gsl_path) / "lib" - if inc.is_dir() and lib.is_dir(): - rv["include_dirs"].append(str(inc)) - rv["library_dirs"].append(str(lib)) - return rv - else: - raise EnvironmentError( - f"GSL_PATH={gsl_path} is set, but {inc} or {lib} not found. " - "Please verify your GSL_PATH." - ) - - # 3. Try using the gsl-config executable (only on Unix-like systems). - if os.name != "nt": - path_dirs = os.environ.get("PATH", "").split(os.pathsep) - gslcfg_paths = [Path(p) / "gsl-config" for p in path_dirs if p] - gslcfg_paths = [p for p in gslcfg_paths if p.is_file()] - if gslcfg_paths: - gslcfg = gslcfg_paths[0] - txt = gslcfg.read_text() - prefix_match = re.search(r"(?m)^prefix=(.+)", txt) - include_match = re.search(r"(?m)^[^#]*\s-I(\S+)", txt) - lib_match = re.search(r"(?m)^[^#]*\s-L(\S+)", txt) - if prefix_match: - prefix_path = Path(prefix_match.group(1)) - inc_dir = ( - include_match.group(1) - if include_match - else (prefix_path / "include") - ) - lib_dir = ( - lib_match.group(1) if lib_match else (prefix_path / "lib") - ) - rv["include_dirs"].append(str(inc_dir)) - rv["library_dirs"].append(str(lib_dir)) - return rv - else: - raise RuntimeError(f"Cannot parse 'prefix=' from {gslcfg}.") - else: - warnings.warn( - "No gsl-config found in PATH. GSL may not be installed or not in PATH. " - "Proceeding without GSL configuration." - ) - - # 4. Nothing found: raise error. - raise EnvironmentError( - "Unable to locate GSL:\n" - "1) CONDA_PREFIX not set or no GSL there\n" - "2) GSL_PATH not set or invalid\n" - "3) gsl-config not available\n" - "Please set GSL_PATH or use a conda environment with GSL." - ) - - -class CustomBuildExt(build_ext): - def run(self): - # Retrieve the GSL library directories and append them to each extension. - gsl_cfg = get_gsl_config() - lib_dirs = gsl_cfg.get("library_dirs", []) - for ext in self.extensions: - # Add gsl lib for linking. - ext.library_dirs.extend(lib_dirs) - # Embed RPATH flags, runtime linking without LD_LIBRARY_PATH. - ext.extra_link_args = ext.extra_link_args or [] - for lib in lib_dirs: - ext.extra_link_args.append(f"-Wl,-rpath,{lib}") - super().run() - # Avoid dll error - gsl_path = ( - Path(os.environ.get("GSL_PATH")) - if os.environ.get("GSL_PATH") - else Path(os.environ.get("CONDA_PREFIX", "")) / "Library" - ) - bin_path = gsl_path / "bin" - dest_path = Path(self.build_lib) / "diffpy" / "pdffit2" - dest_path.mkdir(parents=True, exist_ok=True) - for dll_file in bin_path.glob("gsl*.dll"): - shutil.copy(str(dll_file), str(dest_path)) +# Define extension arguments here +ext_kws = { + "libraries": [], + "extra_compile_args": [], + "extra_link_args": [], + "include_dirs": [], +} def create_extensions(): - """Create the list of Extension objects for the build.""" - # lazy evaluation prevents build sdist failure - try: - gcfg = get_gsl_config() - except EnvironmentError: - return [] - - libraries = ["gsl"] - - include_dirs = [MYDIR] + gcfg["include_dirs"] - library_dirs = gcfg["library_dirs"] - define_macros = [] - extra_objects = [] - extra_compile_args = [] - extra_link_args = [] - - compiler_type = get_compiler_type() - if compiler_type in ("unix", "cygwin", "mingw32"): - extra_compile_args = [ - "-std=c++11", - "-Wall", - "-Wno-write-strings", - "-O3", - "-funroll-loops", - "-ffast-math", - ] - elif compiler_type == "msvc": - define_macros += [("_USE_MATH_DEFINES", None)] - extra_compile_args = ["/EHs"] - - # Extension keyword arguments. - ext_kws = { - "include_dirs": include_dirs, - "libraries": libraries, - "library_dirs": library_dirs, - "define_macros": define_macros, - "extra_compile_args": extra_compile_args, - "extra_link_args": extra_link_args, - "extra_objects": extra_objects, - } + "Initialize Extension objects for the setup function." ext = Extension( - "diffpy.pdffit2.pdffit2", - glob.glob("src/extensions/**/*.cc"), - **ext_kws, + "diffpy.pdffit2.pdffit2", glob.glob("src/extensions/*.cpp"), **ext_kws ) return [ext] @@ -201,7 +29,6 @@ def create_extensions(): # Extensions not included in pyproject.toml setup_args = dict( ext_modules=[], - cmdclass={"build_ext": CustomBuildExt}, ) 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..44eaa9b 100644 --- a/src/diffpy/pdffit2/version.py +++ b/src/diffpy/pdffit2/version.py @@ -7,7 +7,7 @@ # File coded by: Billinge Group members and community contributors. # # 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. # 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():