From e741a273fcc1ccf8dec9d6ff88721f55028204fe Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Thu, 18 Dec 2025 12:46:16 -0500 Subject: [PATCH 1/3] scikit:recut diffpy.utils using latest scikit-package. --- .github/ISSUE_TEMPLATE/release_checklist.md | 2 +- .../workflows/build-wheel-release-upload.yml | 2 +- README.rst | 4 +- cookiecutter.json | 18 +++++++++ docs/source/index.rst | 4 +- pyproject.toml | 8 ++++ src/diffpy/utils/_deprecator.py | 4 +- src/diffpy/utils/diffraction_objects.py | 37 +++++++++++-------- src/diffpy/utils/parsers/serialization.py | 5 ++- src/diffpy/utils/resampler.py | 4 +- src/diffpy/utils/tools.py | 33 +++++++++-------- src/diffpy/utils/transforms.py | 16 +++++--- src/diffpy/utils/utils_app.py | 33 +++++++++++++++++ src/diffpy/utils/version.py | 9 +++-- src/diffpy/utils/wx/gridutils.py | 6 +-- 15 files changed, 129 insertions(+), 56 deletions(-) create mode 100644 cookiecutter.json create mode 100644 src/diffpy/utils/utils_app.py 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 485aa35..e40144a 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.utils diff --git a/README.rst b/README.rst index cf989cd..388598b 100644 --- a/README.rst +++ b/README.rst @@ -25,6 +25,7 @@ :target: https://anaconda.org/conda-forge/diffpy.utils .. |PR| image:: https://img.shields.io/badge/PR-Welcome-29ab47ff + :target: https://github.com/diffpy/diffpy.utils/pulls .. |PyPI| image:: https://img.shields.io/pypi/v/diffpy.utils :target: https://pypi.org/project/diffpy.utils/ @@ -35,8 +36,7 @@ .. |Tracking| image:: https://img.shields.io/badge/issue_tracking-github-blue :target: https://github.com/diffpy/diffpy.utils/issues -diffpy.utils Package -======================================================================== +Shared utilities for diffpy packages. General utilities for analyzing diffraction data diff --git a/cookiecutter.json b/cookiecutter.json new file mode 100644 index 0000000..30db179 --- /dev/null +++ b/cookiecutter.json @@ -0,0 +1,18 @@ +{ + "maintainer_name": "Simon Billinge", + "maintainer_email": "sb2896@columbia.edu", + "maintainer_github_username": "sbillinge", + "contributors": "Timur Davis, Chris Farrow, Pavol Juhas", + "license_holders": "The Trustees of Columbia University in the City of New York", + "project_name": "diffpy.utils", + "github_username_or_orgname": "diffpy", + "github_repo_name": "diffpy.utils", + "conda_pypi_package_dist_name": "diffpy.utils", + "package_dir_name": "diffpy.utils", + "project_short_description": "Shared utilities for diffpy packages.", + "project_keywords": "text data parsers, wx grid, diffraction objects", + "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/index.rst b/docs/source/index.rst index a406daa..27359a3 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,7 +6,7 @@ diffpy.utils - General utilities for analyzing diffraction data -| Software version |release|. +| Software version |release| | Last updated |today|. The diffpy.utils package provides a number of functions and classes designed to help @@ -33,7 +33,7 @@ Illustrations of when and how one would use various diffpy.utils functions. Authors ======= -diffpy.utils is developed by members of the Billinge Group at +``diffpy.utils`` is developed by members of the Billinge Group at Columbia University and at Brookhaven National Laboratory including Pavol Juhás, Christopher L. Farrow, Simon J. L. Billinge, Andrew Yang, with contributions from many Billinge Group members and diff --git a/pyproject.toml b/pyproject.toml index ef9192f..02377e6 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-utils = "diffpy.utils.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" +[tool.docformatter] +recursive = true +wrap-summaries = 72 +wrap-descriptions = 72 + [tool.black] line-length = 79 include = '\.pyi?$' diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 80b183e..72172ca 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -11,8 +11,8 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): - """Deprecation decorator for functions and classes that is compatible with - Python versions prior to 3.13. + """Deprecation decorator for functions and classes that is + compatible with Python versions prior to 3.13. Examples -------- diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 733d370..aa58033 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -211,8 +211,8 @@ def __eq__(self, other): return True def __add__(self, other): - """Add a scalar value or another DiffractionObject to the yarray of the - DiffractionObject. + """Add a scalar value or another DiffractionObject to the yarray + of the DiffractionObject. Parameters ---------- @@ -262,8 +262,8 @@ def __add__(self, other): __radd__ = __add__ def __sub__(self, other): - """Subtract scalar value or another DiffractionObject to the yarray of - the DiffractionObject. + """Subtract scalar value or another DiffractionObject to the + yarray of the DiffractionObject. This method behaves similarly to the `__add__` method, but performs subtraction instead of addition. For details on parameters, returns @@ -290,8 +290,8 @@ def __sub__(self, other): __rsub__ = __sub__ def __mul__(self, other): - """Multiply a scalar value or another DiffractionObject with the yarray - of this DiffractionObject. + """Multiply a scalar value or another DiffractionObject with the + yarray of this DiffractionObject. This method behaves similarly to the `__add__` method, but performs multiplication instead of addition. For details on parameters, @@ -318,8 +318,8 @@ def __mul__(self, other): __rmul__ = __mul__ def __truediv__(self, other): - """Divide the yarray of this DiffractionObject by a scalar value or - another DiffractionObject. + """Divide the yarray of this DiffractionObject by a scalar value + or another DiffractionObject. This method behaves similarly to the `__add__` method, but performs division instead of addition. For details on parameters, returns, @@ -474,7 +474,8 @@ def _get_original_array(self): return self.on_d(), "d" def on_q(self): - """Return the tuple of two 1D numpy arrays containing q and y data. + """Return the tuple of two 1D numpy arrays containing q and y + data. Returns ------- @@ -484,7 +485,8 @@ def on_q(self): return [self.all_arrays[:, 1], self.all_arrays[:, 0]] def on_tth(self): - """Return the tuple of two 1D numpy arrays containing tth and y data. + """Return the tuple of two 1D numpy arrays containing tth and y + data. Returns ------- @@ -494,7 +496,8 @@ def on_tth(self): return [self.all_arrays[:, 2], self.all_arrays[:, 0]] def on_d(self): - """Return the tuple of two 1D numpy arrays containing d and y data. + """Return the tuple of two 1D numpy arrays containing d and y + data. Returns ------- @@ -506,8 +509,8 @@ def on_d(self): def scale_to( self, target_diff_object, q=None, tth=None, d=None, offset=None ): - """Return a new diffraction object which is the current object but - rescaled in y to the target. + """Return a new diffraction object which is the current object + but rescaled in y to the target. By default, if `q`, `tth`, or `d` are not provided, scaling is based on the max intensity from each object. Otherwise, y-value in @@ -568,7 +571,8 @@ def scale_to( return scaled_do def on_xtype(self, xtype): - """Return a tuple of two 1D numpy arrays containing x and y data. + """Return a tuple of two 1D numpy arrays containing x and y + data. Parameters ---------- @@ -597,8 +601,9 @@ def on_xtype(self, xtype): raise ValueError(_xtype_wmsg(xtype)) def dump(self, filepath, xtype=None): - """Dump the xarray and yarray of the diffraction object to a two-column - file, with the associated information included in the header. + """Dump the xarray and yarray of the diffraction object to a + two-column file, with the associated information included in the + header. Parameters ---------- diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index 7531c77..b8ed0c6 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -33,8 +33,9 @@ def serialize_data( show_path=True, serial_file=None, ): - """Serialize file data into a dictionary. Can also save dictionary into a - serial language file. Dictionary is formatted as {filename: data}. + """Serialize file data into a dictionary. Can also save dictionary + into a serial language file. Dictionary is formatted as {filename: + data}. Requires hdata and data_table (can be generated by loadData). diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 4bf2d52..354eb37 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -80,8 +80,8 @@ def wsinterp(x, xp, fp, left=None, right=None): def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): - """One-dimensional Whittaker-Shannon interpolation onto the Nyquist-Shannon - grid. + """One-dimensional Whittaker-Shannon interpolation onto the Nyquist- + Shannon grid. Takes a band-limited function fp and original grid xp and resamples fp on the NS grid. Uses the minimum number of points N required by the Nyquist diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 3a42990..d6f9617 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -51,8 +51,8 @@ def _load_config(file_path): def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): - """Get name, email, and ORCID of the owner/user from various sources and - return it as a metadata dictionary. + """Get name, email, and ORCID of the owner/user from various sources + and return it as a metadata dictionary. The function looks for information in JSON configuration files named ``diffpyconfig.json``. These can be in the user's home directory and in @@ -109,8 +109,8 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): def check_and_build_global_config(skip_config_creation=False): - """Check for a global diffpu config file in user's home directory and - creates one if it is missing. + """Check for a global diffpu config file in user's home directory + and creates one if it is missing. The file it looks for is called diffpyconfig.json. This can contain anything in json format, but minimally contains information about the @@ -234,7 +234,8 @@ def get_density_from_cloud(sample_composition, mp_token=""): def compute_mu_using_xraydb( sample_composition, energy, sample_mass_density=None, packing_fraction=None ): - """Compute the attenuation coefficient (mu) using the XrayDB database. + """Compute the attenuation coefficient (mu) using the XrayDB + database. Computes mu based on the sample composition and energy. User should provide a sample mass density or a packing fraction. @@ -287,8 +288,8 @@ def compute_mu_using_xraydb( def _top_hat(z, half_slit_width): - """Create a top-hat function, return 1.0 for values within the specified - slit width and 0 otherwise.""" + """Create a top-hat function, return 1.0 for values within the + specified slit width and 0 otherwise.""" return np.where((z >= -half_slit_width) & (z <= half_slit_width), 1.0, 0.0) @@ -319,9 +320,10 @@ def _model_function(z, diameter, z0, I0, mud, slope): def _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope): - """Extend z values and I values for padding (so that we don't have tails in - convolution), then perform convolution (note that the convolved I values - are the same as modeled I values if slit width is close to 0)""" + """Extend z values and I values for padding (so that we don't have + tails in convolution), then perform convolution (note that the + convolved I values are the same as modeled I values if slit width is + close to 0)""" n_points = len(z) z_left_pad = np.linspace( z.min() - n_points * (z[1] - z[0]), z.min(), n_points @@ -342,8 +344,8 @@ def _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope): def _objective_function(params, z, observed_data): """Compute the objective function for fitting a model to the - observed/experimental data by minimizing the sum of squared residuals - between the observed data and the convolved model data.""" + observed/experimental data by minimizing the sum of squared + residuals between the observed data and the convolved model data.""" diameter, half_slit_width, z0, I0, mud, slope = params convolved_model_data = _extend_z_and_convolve( z, diameter, half_slit_width, z0, I0, mud, slope @@ -353,7 +355,8 @@ def _objective_function(params, z, observed_data): def _compute_single_mud(z_data, I_data): - """Perform dual annealing optimization and extract the parameters.""" + """Perform dual annealing optimization and extract the + parameters.""" bounds = [ ( 1e-5, @@ -382,8 +385,8 @@ def _compute_single_mud(z_data, I_data): def compute_mud(filepath): - """Compute the best-fit mu*D value from a z-scan file, removing the sample - holder effect. + """Compute the best-fit mu*D value from a z-scan file, removing the + sample holder effect. This function loads z-scan data and fits it to a model that convolves a top-hat function with I = I0 * e^{-mu * l}. diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index ab1d009..8cb0760 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -76,7 +76,8 @@ def q_to_tth(q, wavelength): def tth_to_q(tth, wavelength): - r"""Helper function to convert two-theta to q on independent variable axis. + r"""Helper function to convert two-theta to q on independent variable + axis. If wavelength is missing, returns independent variable axis as integer indexes. @@ -124,8 +125,8 @@ def tth_to_q(tth, wavelength): def q_to_d(q): - r"""Helper function to convert q to d on independent variable axis, using - :math:`d = \frac{2 \pi}{q}`. + r"""Helper function to convert q to d on independent variable axis, + using :math:`d = \frac{2 \pi}{q}`. Parameters ---------- @@ -144,7 +145,8 @@ def q_to_d(q): def tth_to_d(tth, wavelength): - r"""Helper function to convert two-theta to d on independent variable axis. + r"""Helper function to convert two-theta to d on independent variable + axis. The formula is .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}. @@ -178,7 +180,8 @@ def tth_to_d(tth, wavelength): def d_to_q(d): - r"""Helper function to convert q to d using :math:`d = \frac{2 \pi}{q}`. + r"""Helper function to convert q to d using :math:`d = \frac{2 + \pi}{q}`. Parameters ---------- @@ -197,7 +200,8 @@ def d_to_q(d): def d_to_tth(d, wavelength): - r"""Helper function to convert d to two-theta on independent variable axis. + r"""Helper function to convert d to two-theta on independent variable + axis. The formula is .. math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right). diff --git a/src/diffpy/utils/utils_app.py b/src/diffpy/utils/utils_app.py new file mode 100644 index 0000000..588e28c --- /dev/null +++ b/src/diffpy/utils/utils_app.py @@ -0,0 +1,33 @@ +import argparse + +from diffpy.utils.version import __version__ # noqa + + +def main(): + parser = argparse.ArgumentParser( + prog="diffpy.utils", + description=( + "Shared utilities for diffpy packages.\n\n" + "For more information, visit: " + "https://github.com/diffpy/diffpy.utils/" + ), + 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.utils {__version__}") + else: + # Default behavior when no arguments are given + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index a17ccb5..d94610a 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -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.utils") - -# End of file +try: + __version__ = version("diffpy.utils") +except PackageNotFoundError: + __version__ = "unknown" diff --git a/src/diffpy/utils/wx/gridutils.py b/src/diffpy/utils/wx/gridutils.py index d6f2874..d94860c 100644 --- a/src/diffpy/utils/wx/gridutils.py +++ b/src/diffpy/utils/wx/gridutils.py @@ -146,9 +146,9 @@ def quickResizeColumns(grid, indices): def _indicesToBlocks(indices): - """Convert a list of integer indices to a list of (start, stop) tuples. The - (start, stop) tuple defines a continuous block, where the stop index is - included in the block. + """Convert a list of integer indices to a list of (start, stop) + tuples. The (start, stop) tuple defines a continuous block, where + the stop index is included in the block. Parameters ---------- From c8fbcfefb55cc29894c5a1bc90909abd0e0f6fc6 Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Thu, 18 Dec 2025 13:07:44 -0500 Subject: [PATCH 2/3] chore: add news item --- news/scikit-package.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/scikit-package.rst diff --git a/news/scikit-package.rst b/news/scikit-package.rst new file mode 100644 index 0000000..9f7ee0e --- /dev/null +++ b/news/scikit-package.rst @@ -0,0 +1,23 @@ +**Added:** + +* No News Added: recut diffpy.utils using latest scikit-package + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From f0d2a4dcb4e8e810d68e3715533eade8768a249b Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Thu, 18 Dec 2025 13:09:37 -0500 Subject: [PATCH 3/3] fix: change script character to underscore --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 02377e6..9799bbf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ exclude = [] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) [project.scripts] -diffpy-utils = "diffpy.utils.app:main" +diffpy-utils = "diffpy.utils_app:main" [tool.setuptools.dynamic] dependencies = {file = ["requirements/pip.txt"]}