diff --git a/.codecov.yml b/.codecov.yml index 5a94096..4af5eb2 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,14 +1,14 @@ coverage: status: - project: # more options at https://docs.codecov.com/docs/commit-status + project: # more options at https://docs.codecov.com/docs/commit-status default: target: auto # use the coverage from the base commit, fail if coverage is lower - threshold: 0% # allow the coverage to drop by + threshold: 0% # allow the coverage to drop by comment: layout: " diff, flags, files" behavior: default require_changes: false - require_base: false # [true :: must have a base report to post] - require_head: false # [true :: must have a head report to post] + require_base: false # [true :: must have a base report to post] + require_head: false # [true :: must have a head report to post] hide_project_coverage: false # [true :: only show coverage on the git diff aka patch coverage] diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index 0f56027..99ab0d8 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -12,7 +12,7 @@ assignees: "" - [ ] All the badges on the README are passing. - [ ] License information is verified as correct. If you are unsure, please comment below. - [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are - missing), tutorials, and other human written text is up-to-date with any changes in the code. + missing), tutorials, and other human written text is up-to-date with any changes in the code. - [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated. - [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. - [ ] Grammar and writing quality are checked (no typos). @@ -31,5 +31,5 @@ version information and details about the pre-release here: -- [ ] Run tutorial examples and conduct functional testing using the installation guide in the README. Attach screenshots/results as comments. -- [ ] Documentation (README, tutorials, API references, and websites) is deployed without broken links or missing figures. +- [ ] Run tutorial examples and conduct functional testing using the installation guide in the README. Attach screenshots/results as comments. +- [ ] Documentation (README, tutorials, API references, and websites) is deployed without broken links or missing figures. diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index 0af554b..110f95b 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -4,13 +4,15 @@ on: workflow_dispatch: push: tags: - - '*' # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml + - "*" # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml jobs: release: - uses: Billingegroup/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 + uses: scikit-package/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 with: project: diffpy.snmf + c_extension: false + maintainer_GITHUB_username: sbillinge secrets: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} PAT_TOKEN: ${{ secrets.PAT_TOKEN }} diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml index 2f95151..1c7c14f 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -3,10 +3,10 @@ name: Check for News on: pull_request_target: branches: - - main + - main jobs: - build: - uses: Billingegroup/release-scripts/.github/workflows/_check-news-item.yml@v0 + check-news-item: + uses: scikit-package/release-scripts/.github/workflows/_check-news-item.yml@v0 with: project: diffpy.snmf diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml index 4f8649e..5360214 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -11,8 +11,8 @@ on: workflow_dispatch: jobs: - coverage: - uses: Billingegroup/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 + matrix-coverage: + uses: scikit-package/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 with: project: diffpy.snmf c_extension: false diff --git a/.github/workflows/publish-docs-on-release.yml b/.github/workflows/publish-docs-on-release.yml new file mode 100644 index 0000000..2f0efbc --- /dev/null +++ b/.github/workflows/publish-docs-on-release.yml @@ -0,0 +1,12 @@ +name: Deploy Documentation on Release + +on: + workflow_dispatch: + +jobs: + docs: + uses: scikit-package/release-scripts/.github/workflows/_publish-docs-on-release.yml@v0 + with: + project: diffpy.snmf + c_extension: false + headless: false diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml index fabc106..3c428be 100644 --- a/.github/workflows/tests-on-pr.yml +++ b/.github/workflows/tests-on-pr.yml @@ -1,15 +1,12 @@ name: Tests on PR on: - push: - branches: - - main pull_request: workflow_dispatch: jobs: - validate: - uses: Billingegroup/release-scripts/.github/workflows/_tests-on-pr.yml@v0 + tests-on-pr: + uses: scikit-package/release-scripts/.github/workflows/_tests-on-pr.yml@v0 with: project: diffpy.snmf c_extension: false diff --git a/.gitignore b/.gitignore index a25212e..099e294 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ __pycache__/ .Python env/ build/ +_build/ develop-eggs/ dist/ downloads/ @@ -90,10 +91,3 @@ target/ # Ipython Notebook .ipynb_checkpoints - -# version information -setup.cfg -/src/diffpy/*/version.cfg - -# Rever -rever/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9cf0556..0e4a84d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,14 +1,14 @@ default_language_version: - python: python3 + python: python3 ci: - autofix_commit_msg: | - [pre-commit.ci] auto fixes from pre-commit hooks - autofix_prs: true - autoupdate_branch: 'pre-commit-autoupdate' - autoupdate_commit_msg: '[pre-commit.ci] pre-commit autoupdate' - autoupdate_schedule: monthly - skip: [no-commit-to-branch] - submodules: false + autofix_commit_msg: | + [pre-commit.ci] auto fixes from pre-commit hooks + autofix_prs: true + autoupdate_branch: "pre-commit-autoupdate" + autoupdate_commit_msg: "[pre-commit.ci] pre-commit autoupdate" + autoupdate_schedule: monthly + skip: [no-commit-to-branch] + submodules: false repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 @@ -47,6 +47,20 @@ repos: - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: - - id: codespell - additional_dependencies: - - tomli + - id: codespell + additional_dependencies: + - tomli + # prettier - multi formatter for .json, .yml, and .md files + - repo: https://github.com/pre-commit/mirrors-prettier + rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8 + hooks: + - id: prettier + additional_dependencies: + - "prettier@^3.2.4" + # docformatter - PEP 257 compliant docstring formatter + - repo: https://github.com/s-weigand/docformatter + rev: 5757c5190d95e5449f102ace83df92e7d3b06c6c + hooks: + - id: docformatter + additional_dependencies: [tomli] + args: [--in-place, --config, ./pyproject.toml] diff --git a/pyproject.toml b/pyproject.toml index fe6281b..6a91421 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,17 +6,17 @@ build-backend = "setuptools.build_meta" name = "diffpy.snmf" dynamic=['version', 'dependencies'] authors = [ - { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, + { name="Simon J.L. Billinge group", email="sb2896@columbia.edu" }, ] maintainers = [ - { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, + { name="Simon J.L. Billinge group", email="sb2896@columbia.edu" }, ] description = "Python package implementing the stretched NMF algorithm." keywords = ['diffpy', 'PDF'] readme = "README.rst" requires-python = ">=3.11, <3.14" classifiers = [ - 'Development Status :: 4 - Beta', + 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', diff --git a/requirements/build.txt b/requirements/build.txt deleted file mode 100644 index e69de29..0000000 diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index 2baa1ea..eded16c 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python ############################################################################## # -# (c) 2024 The Trustees of Columbia University in the City of New York. +# (c) 2024-2025 The Trustees of Columbia University in the City of New York. # All rights reserved. # # File coded by: Billinge Group members and community contributors. @@ -12,7 +12,6 @@ # See LICENSE.rst for license information. # ############################################################################## - """Blank namespace package for module diffpy.""" diff --git a/src/diffpy/snmf/__init__.py b/src/diffpy/snmf/__init__.py index 9c7220b..bb7f5a5 100644 --- a/src/diffpy/snmf/__init__.py +++ b/src/diffpy/snmf/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python ############################################################################## # -# (c) 2024 The Trustees of Columbia University in the City of New York. +# (c) 2024-2025 The Trustees of Columbia University in the City of New York. # All rights reserved. # # File coded by: Billinge Group members and community contributors. @@ -12,11 +12,10 @@ # See LICENSE.rst for license information. # ############################################################################## - -"""A python package implementing the stretched NMF algorithm.""" +"""Python package implementing the stretched NMF algorithm.""" # package version -from diffpy.snmf.version import __version__ +from diffpy.snmf.version import __version__ # noqa # silence the pyflakes syntax checker assert __version__ or True diff --git a/src/diffpy/snmf/containers.py b/src/diffpy/snmf/containers.py index 1af960a..00b09ea 100644 --- a/src/diffpy/snmf/containers.py +++ b/src/diffpy/snmf/containers.py @@ -26,7 +26,7 @@ def __init__(self, grid, number_of_signals, id_number, perturbation=1e-3): self.id = int(id_number) def apply_stretch(self, m): - """Applies a stretching factor to a component + """Applies a stretching factor to a component. Parameters ---------- @@ -41,7 +41,11 @@ def apply_stretch(self, m): """ normalized_grid = np.arange(len(self.grid)) interpolate_intensity = lambda stretching_factor: np.interp( # noqa: E731 - normalized_grid / stretching_factor, normalized_grid, self.iq, left=0, right=0 + normalized_grid / stretching_factor, + normalized_grid, + self.iq, + left=0, + right=0, ) derivative_func = numdifftools.Derivative(interpolate_intensity) second_derivative_func = numdifftools.Derivative(derivative_func) diff --git a/src/diffpy/snmf/factorizers.py b/src/diffpy/snmf/factorizers.py index b4620fd..a9db32c 100644 --- a/src/diffpy/snmf/factorizers.py +++ b/src/diffpy/snmf/factorizers.py @@ -3,7 +3,8 @@ def lsqnonneg(stretched_component_matrix, target_signal): - """Finds the weights of stretched component signals under one-sided constraint. + """Finds the weights of stretched component signals under one-sided + constraint. Solves ``argmin_x || Ax - b ||_2`` for ``x>=0`` where A is the stretched_component_matrix and b is the target_signal vector. Finds the weights of component signals given undecomposed signal data and stretched diff --git a/src/diffpy/snmf/io.py b/src/diffpy/snmf/io.py index 12eb1b2..62614d0 100644 --- a/src/diffpy/snmf/io.py +++ b/src/diffpy/snmf/io.py @@ -39,7 +39,6 @@ def initialize_variables(data_input, number_of_components, data_type, sparsity=1 guess for the weight factor matrix, an initial guess for the stretching factor matrix, a parameter controlling smoothness of the solution, a parameter controlling sparseness of the solution, the matrix representing the smoothness term, and a matrix used to construct a hessian matrix. - """ signal_length = data_input.shape[0] number_of_signals = data_input.shape[1] @@ -74,7 +73,8 @@ def initialize_variables(data_input, number_of_components, data_type, sparsity=1 def load_input_signals(file_path=None): - """Processes a directory of a series of PDF/XRD patterns into a usable format. + """Processes a directory of a series of PDF/XRD patterns into a usable + format. Constructs a 2d array out of a directory of PDF/XRD patterns containing each files dependent variable column in a new column. Constructs a 1d array containing the grid values. @@ -92,7 +92,6 @@ def load_input_signals(file_path=None): The tuple whose first element is an R x M 2d array made of PDF/XRD patterns as each column; R is the length of the signal and M is the number of patterns. The tuple contains a 1d array containing the values of the grid points as its second element; Has length R. - """ if file_path is None: diff --git a/src/diffpy/snmf/optimizers.py b/src/diffpy/snmf/optimizers.py index d1a8e51..81a1b6d 100644 --- a/src/diffpy/snmf/optimizers.py +++ b/src/diffpy/snmf/optimizers.py @@ -2,8 +2,14 @@ from scipy.optimize import minimize -def get_weights(stretched_component_gram_matrix, linear_coefficient, lower_bound, upper_bound): - """Finds the weights of stretched component signals under a two-sided constraint +def get_weights( + stretched_component_gram_matrix, + linear_coefficient, + lower_bound, + upper_bound, +): + """Finds the weights of stretched component signals under a two-sided + constraint. Solves min J(y) = (linear_coefficient)' * y + (1/2) * y' * (quadratic coefficient) * y where lower_bound <= y <= upper_bound and stretched_component_gram_matrix is symmetric positive definite. @@ -34,7 +40,6 @@ def get_weights(stretched_component_gram_matrix, linear_coefficient, lower_bound 1d array like The vector containing the weightings of the components needed to reconstruct a given input signal from the input set. Has length C - """ stretched_component_gram_matrix = np.asarray(stretched_component_gram_matrix) diff --git a/src/diffpy/snmf/polynomials.py b/src/diffpy/snmf/polynomials.py index c54ff21..a194914 100644 --- a/src/diffpy/snmf/polynomials.py +++ b/src/diffpy/snmf/polynomials.py @@ -2,9 +2,8 @@ def compute_root(linear_coefficient, constant_term): - """ - Returns the largest real root of x^3+(linear_coefficient) * x + constant_term. If there are no real roots - return 0. + """Returns the largest real root of x^3+(linear_coefficient) * x + + constant_term. If there are no real roots return 0. Parameters ---------- @@ -18,7 +17,6 @@ def compute_root(linear_coefficient, constant_term): ndarray of floats The largest real root of x^3+(linear_coefficient) * x + constant_term if roots are real, else return 0 array - """ linear_coefficient = np.asarray(linear_coefficient) constant_term = np.asarray(constant_term) diff --git a/src/diffpy/snmf/stretchednmfapp.py b/src/diffpy/snmf/stretchednmfapp.py index f577b88..f62a9a5 100644 --- a/src/diffpy/snmf/stretchednmfapp.py +++ b/src/diffpy/snmf/stretchednmfapp.py @@ -4,12 +4,18 @@ from diffpy.snmf.io import initialize_variables, load_input_signals from diffpy.snmf.subroutines import initialize_components, lift_data -ALLOWED_DATA_TYPES = ["powder_diffraction", "pd", "pair_distribution_function", "pdf"] +ALLOWED_DATA_TYPES = [ + "powder_diffraction", + "pd", + "pair_distribution_function", + "pdf", +] def create_parser(): parser = argparse.ArgumentParser( - prog="stretched_nmf", description="Stretched Nonnegative Matrix Factorization" + prog="stretched_nmf", + description="Stretched Nonnegative Matrix Factorization", ) parser.add_argument( "-i", @@ -44,7 +50,12 @@ def create_parser(): type=int, help="The number of component signals for the NMF decomposition. Must be an integer greater than 0", ) - parser.add_argument("-v", "--version", action="version", help="Print the software version number") + parser.add_argument( + "-v", + "--version", + action="version", + help="Print the software version number", + ) args = parser.parse_args() return args diff --git a/src/diffpy/snmf/subroutines.py b/src/diffpy/snmf/subroutines.py index ae7f7ef..9d6a656 100644 --- a/src/diffpy/snmf/subroutines.py +++ b/src/diffpy/snmf/subroutines.py @@ -7,7 +7,8 @@ def initialize_components(number_of_components, number_of_signals, grid_vector): - """Initializes ComponentSignals for each of the components in the decomposition. + """Initializes ComponentSignals for each of the components in the + decomposition. Parameters ---------- @@ -182,7 +183,8 @@ def update_weights(components, data_input, method=None): def reconstruct_signal(components, signal_idx): - """Reconstructs a specific signal from its weighted and stretched components. + """Reconstructs a specific signal from its weighted and stretched + components. Calculates the linear combination of stretched components where each term is the stretched component multiplied by its weight factor. @@ -209,7 +211,8 @@ def reconstruct_signal(components, signal_idx): def initialize_arrays(number_of_components, number_of_moments, signal_length): - """Generates the initial guesses for the weight, stretching, and component matrices. + """Generates the initial guesses for the weight, stretching, and component + matrices. Calculates the initial guesses for the component matrix, stretching factor matrix, and weight matrix. The initial guess for the component matrix is a random (signal_length) x (number_of_components) matrix where @@ -245,7 +248,12 @@ def initialize_arrays(number_of_components, number_of_moments, signal_length): def objective_function( - residual_matrix, stretching_factor_matrix, smoothness, smoothness_term, component_matrix, sparsity + residual_matrix, + stretching_factor_matrix, + smoothness, + smoothness_term, + component_matrix, + sparsity, ): """Defines the objective function of the algorithm and returns its value. @@ -322,7 +330,13 @@ def get_stretched_component(stretching_factor, component, signal_length): normalized_grid = np.arange(signal_length) def stretched_component_func(stretching_factor): - return np.interp(normalized_grid / stretching_factor, normalized_grid, component, left=0, right=0) + return np.interp( + normalized_grid / stretching_factor, + normalized_grid, + component, + left=0, + right=0, + ) derivative_func = numdifftools.Derivative(stretched_component_func) second_derivative_func = numdifftools.Derivative(derivative_func) @@ -394,10 +408,15 @@ def update_weights_matrix( stretched_components = np.zeros((signal_length, component_amount)) for n in range(component_amount): stretched_components[:, n] = get_stretched_component( - stretching_factor_matrix[n, i], component_matrix[:, n], signal_length + stretching_factor_matrix[n, i], + component_matrix[:, n], + signal_length, )[0] if method == "align": - weight = lsqnonneg(stretched_components[0:signal_length, :], data_input[0:signal_length, i]) + weight = lsqnonneg( + stretched_components[0:signal_length, :], + data_input[0:signal_length, i], + ) else: weight = get_weights( stretched_components[0:signal_length, :].T @ stretched_components[0:signal_length, :], @@ -410,9 +429,16 @@ def update_weights_matrix( def get_residual_matrix( - component_matrix, weights_matrix, stretching_matrix, data_input, moment_amount, component_amount, signal_length + component_matrix, + weights_matrix, + stretching_matrix, + data_input, + moment_amount, + component_amount, + signal_length, ): - """Obtains the residual matrix between the experimental data and calculated data. + """Obtains the residual matrix between the experimental data and calculated + data. Calculates the difference between the experimental data and the reconstructed experimental data created from the calculated components, weights, and stretching factors. For each experimental pattern, the stretched and @@ -465,7 +491,11 @@ def get_residual_matrix( residual = ( residual + weights_matrix[k, m] - * get_stretched_component(stretching_matrix[k, m], component_matrix[:, k], signal_length)[0] + * get_stretched_component( + stretching_matrix[k, m], + component_matrix[:, k], + signal_length, + )[0] ) residual_matrx[:, m] = residual return residual_matrx diff --git a/src/diffpy/snmf/version.py b/src/diffpy/snmf/version.py index 202bd54..e198622 100644 --- a/src/diffpy/snmf/version.py +++ b/src/diffpy/snmf/version.py @@ -1,18 +1,17 @@ #!/usr/bin/env python ############################################################################## # -# (c) 2024 The Trustees of Columbia University in the City of New York. +# (c) 2024-2025 The Trustees of Columbia University in the City of New York. # All rights reserved. # # File coded by: Billinge Group members and community contributors. # # See GitHub contributions for a more detailed list of contributors. -# https://github.com/diffpy/diffpy.snmf/graphs/contributors +# https://github.com/diffpy/diffpy.snmf/graphs/contributors # noqa: E501 # # See LICENSE.rst for license information. # ############################################################################## - """Definition of __version__.""" # We do not use the other three variables, but can be added back if needed. diff --git a/tests/test_containers.py b/tests/test_containers.py index 9595ae5..5b29dfe 100644 --- a/tests/test_containers.py +++ b/tests/test_containers.py @@ -50,7 +50,18 @@ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 0.88, [ - [1, 2.1364, 3.2727, 4.4091, 5.5455, 6.6818, 7.8182, 8.9545, 0, 0], + [ + 1, + 2.1364, + 3.2727, + 4.4091, + 5.5455, + 6.6818, + 7.8182, + 8.9545, + 0, + 0, + ], [0, -1.29, -2.58, -3.87, -5.165, -6.45, -7.74, -9.039, 0, 0], [0, 2.93, 5.869, 8.084, 11.739, 14.674, 17.608, 20.5437, 0, 0], ], @@ -77,9 +88,54 @@ ], 0.55, [ - [-2.9384, -1.9769, 0.9121, 0.6314, 0.8622, -2.4239, -0.2302, 1.9281, 0, 0, 0, 0, 0, 0], - [0, 2.07933, 38.632, 18.3748, 43.07305, -61.557, 26.005, -73.637, 0, 0, 0, 0, 0, 0], - [0, -7.56, -140.480, -66.81, -156.6293, 223.84, -94.564, 267.7734, 0, 0, 0, 0, 0, 0], + [ + -2.9384, + -1.9769, + 0.9121, + 0.6314, + 0.8622, + -2.4239, + -0.2302, + 1.9281, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 2.07933, + 38.632, + 18.3748, + 43.07305, + -61.557, + 26.005, + -73.637, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + -7.56, + -140.480, + -66.81, + -156.6293, + 223.84, + -94.564, + 267.7734, + 0, + 0, + 0, + 0, + 0, + 0, + ], ], ), ( @@ -89,9 +145,45 @@ [0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5], 0.987, [ - [0, 0.2533, 0.5066, 0.7599, 1.0132, 1.2665, 1.5198, 1.7730, 2.0263, 2.2796, 0], - [0, -0.2566, -0.5132, -0.7699, -1.0265, -1.2831, -1.5398, -1.7964, -2.0530, -2.3097, 0], - [0, 0.5200, 1.0400, 1.56005, 2.08007, 2.6000, 3.1201, 3.6401, 4.1601, 4.6801, 0], + [ + 0, + 0.2533, + 0.5066, + 0.7599, + 1.0132, + 1.2665, + 1.5198, + 1.7730, + 2.0263, + 2.2796, + 0, + ], + [ + 0, + -0.2566, + -0.5132, + -0.7699, + -1.0265, + -1.2831, + -1.5398, + -1.7964, + -2.0530, + -2.3097, + 0, + ], + [ + 0, + 0.5200, + 1.0400, + 1.56005, + 2.08007, + 2.6000, + 3.1201, + 3.6401, + 4.1601, + 4.6801, + 0, + ], ], ), ( @@ -100,7 +192,11 @@ 3, [-1, -2, -3, -4, -5, -6, -7, -8, -9], -0.4, - [[-1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]], + [ + [-1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + ], ), ], ) @@ -116,8 +212,22 @@ def test_apply_stretch(grid, number_of_signals, id_number, iq, stretching_factor "grid, number_of_signals, id_number, iq, weight, expected", [ (np.arange(5), 2, 0, [0, 1, 2, 3, 4], 0.5, [0, 0.5, 1, 1.5, 2]), - (np.arange(5), 20, 2, [0, -1, -2, -3, -4], 0.25, [0, -0.25, -0.5, -0.75, -1]), - (np.arange(40), 200, 4, np.arange(0, 10, 0.25), 0.3, np.arange(0, 10, 0.25) * 0.3), + ( + np.arange(5), + 20, + 2, + [0, -1, -2, -3, -4], + 0.25, + [0, -0.25, -0.5, -0.75, -1], + ), + ( + np.arange(40), + 200, + 4, + np.arange(0, 10, 0.25), + 0.3, + np.arange(0, 10, 0.25) * 0.3, + ), (np.arange(1), 10, 2, [10.5, 11.5, -10.5], 0, [0, 0, 0]), ([-12, -10, -15], 5, 2, [-0.5, -1, -1.2], 0.9, [-0.45, -0.9, -1.08]), ([-12, -10, -15], 5, 2, [0, 0, 0], 0.9, [0, 0, 0]), diff --git a/tests/test_optimizers.py b/tests/test_optimizers.py index 3179915..5315869 100644 --- a/tests/test_optimizers.py +++ b/tests/test_optimizers.py @@ -9,12 +9,47 @@ ([[1, 0], [0, 1]], [1, 1], 0, 0, [0, 0]), ([[1, 0], [0, 1]], [1, 1], -1, 1, [-1, -1]), ([[1.75, 0], [0, 1.5]], [1, 1.2], -1, 1, [-0.571428571428571, -0.8]), - ([[0.75, 0.2], [0.2, 0.75]], [-0.1, -0.2], -1, 1, [0.066985645933014, 0.248803827751196]), - ([[2, -1, 0], [-1, 2, -1], [0, -1, 2]], [1, 1, 1], -10, 12, [-1.5, -2, -1.5]), - ([[2, -1, 0], [-1, 2, -1], [0, -1, 2]], [1, -1, -1], -10, 12, [0, 1, 1]), - ([[4, 0, 0, 0], [0, 3, 0, 0], [0, 0, 2, 0], [0, 0, 0, 1]], [-2, -3, -4, -1], 0, 1000, [0.5, 1, 2, 1]), + ( + [[0.75, 0.2], [0.2, 0.75]], + [-0.1, -0.2], + -1, + 1, + [0.066985645933014, 0.248803827751196], + ), + ( + [[2, -1, 0], [-1, 2, -1], [0, -1, 2]], + [1, 1, 1], + -10, + 12, + [-1.5, -2, -1.5], + ), + ( + [[2, -1, 0], [-1, 2, -1], [0, -1, 2]], + [1, -1, -1], + -10, + 12, + [0, 1, 1], + ), + ( + [[4, 0, 0, 0], [0, 3, 0, 0], [0, 0, 2, 0], [0, 0, 0, 1]], + [-2, -3, -4, -1], + 0, + 1000, + [0.5, 1, 2, 1], + ), ], ) -def test_get_weights(stretched_component_gram_matrix, linear_coefficient, lower_bound, upper_bound, expected): - actual = get_weights(stretched_component_gram_matrix, linear_coefficient, lower_bound, upper_bound) +def test_get_weights( + stretched_component_gram_matrix, + linear_coefficient, + lower_bound, + upper_bound, + expected, +): + actual = get_weights( + stretched_component_gram_matrix, + linear_coefficient, + lower_bound, + upper_bound, + ) assert actual == pytest.approx(expected, rel=1e-4, abs=1e-6) diff --git a/tests/test_subroutines.py b/tests/test_subroutines.py index 3b3e392..6c1dd12 100644 --- a/tests/test_subroutines.py +++ b/tests/test_subroutines.py @@ -21,16 +21,51 @@ @pytest.mark.parametrize( "residual_matrix, stretching_factor_matrix, smoothness, smoothness_term, component_matrix, sparsity, expected", [ - ([[1, 2], [3, 4]], [[5, 6], [7, 8]], 1e11, [[1, 2], [3, 4]], [[1, 2], [3, 4]], 1, 2.574e14), - ([[11, 2], [31, 4]], [[5, 63], [7, 18]], 0.001, [[21, 2], [3, 4]], [[11, 22], [3, 40]], 1, 650.4576), - ([[1, 2], [3, 4]], [[5, 6], [7, 8]], 1e11, [[1, 2], [3, 4]], [[1, 2], [3, 4]], 0, 2.574e14), + ( + [[1, 2], [3, 4]], + [[5, 6], [7, 8]], + 1e11, + [[1, 2], [3, 4]], + [[1, 2], [3, 4]], + 1, + 2.574e14, + ), + ( + [[11, 2], [31, 4]], + [[5, 63], [7, 18]], + 0.001, + [[21, 2], [3, 4]], + [[11, 22], [3, 40]], + 1, + 650.4576, + ), + ( + [[1, 2], [3, 4]], + [[5, 6], [7, 8]], + 1e11, + [[1, 2], [3, 4]], + [[1, 2], [3, 4]], + 0, + 2.574e14, + ), ], ) def test_objective_function( - residual_matrix, stretching_factor_matrix, smoothness, smoothness_term, component_matrix, sparsity, expected + residual_matrix, + stretching_factor_matrix, + smoothness, + smoothness_term, + component_matrix, + sparsity, + expected, ): actual = objective_function( - residual_matrix, stretching_factor_matrix, smoothness, smoothness_term, component_matrix, sparsity + residual_matrix, + stretching_factor_matrix, + smoothness, + smoothness_term, + component_matrix, + sparsity, ) assert actual == pytest.approx(expected) @@ -73,7 +108,18 @@ def test_objective_function( [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10, ( - [1, 2.1364, 3.2727, 4.4091, 5.5455, 6.6818, 7.8182, 8.9545, 0, 0], + [ + 1, + 2.1364, + 3.2727, + 4.4091, + 5.5455, + 6.6818, + 7.8182, + 8.9545, + 0, + 0, + ], [0, -1.29, -2.58, -3.87, -5.165, -6.45, -7.74, -9.039, 0, 0], [0, 2.93, 5.869, 8.084, 11.739, 14.674, 17.608, 20.5437, 0, 0], ), @@ -98,9 +144,54 @@ def test_objective_function( ], 14, ( - [-2.9384, -1.9769, 0.9121, 0.6314, 0.8622, -2.4239, -0.2302, 1.9281, 0, 0, 0, 0, 0, 0], - [0, 2.07933, 38.632, 18.3748, 43.07305, -61.557, 26.005, -73.637, 0, 0, 0, 0, 0, 0], - [0, -7.56, -140.480, -66.81, -156.6293, 223.84, -94.564, 267.7734, 0, 0, 0, 0, 0, 0], + [ + -2.9384, + -1.9769, + 0.9121, + 0.6314, + 0.8622, + -2.4239, + -0.2302, + 1.9281, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 2.07933, + 38.632, + 18.3748, + 43.07305, + -61.557, + 26.005, + -73.637, + 0, + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + -7.56, + -140.480, + -66.81, + -156.6293, + 223.84, + -94.564, + 267.7734, + 0, + 0, + 0, + 0, + 0, + 0, + ], ), ), ( @@ -108,16 +199,56 @@ def test_objective_function( [0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5], 11, ( - [0, 0.2533, 0.5066, 0.7599, 1.0132, 1.2665, 1.5198, 1.7730, 2.0263, 2.2796, 0], - [0, -0.2566, -0.5132, -0.7699, -1.0265, -1.2831, -1.5398, -1.7964, -2.0530, -2.3097, 0], - [0, 0.5200, 1.0400, 1.56005, 2.08007, 2.6000, 3.1201, 3.6401, 4.1601, 4.6801, 0], + [ + 0, + 0.2533, + 0.5066, + 0.7599, + 1.0132, + 1.2665, + 1.5198, + 1.7730, + 2.0263, + 2.2796, + 0, + ], + [ + 0, + -0.2566, + -0.5132, + -0.7699, + -1.0265, + -1.2831, + -1.5398, + -1.7964, + -2.0530, + -2.3097, + 0, + ], + [ + 0, + 0.5200, + 1.0400, + 1.56005, + 2.08007, + 2.6000, + 3.1201, + 3.6401, + 4.1601, + 4.6801, + 0, + ], ), ), ( -0.4, [-1, -2, -3, -4, -5, -6, -7, -8, -9], 9, - ([-1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]), + ( + [-1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + ), ), ], ) @@ -172,10 +303,34 @@ def test_get_stretched_component(stretching_factor, component, signal_length, ex 3, [[0.9, 0.4, 0.5], [1, 0, 0.4], [0, 0, 0.98]], None, - [[1.0, 0.0900485, 0.0], [0.585632, 0.497497, 0.179719], [0.0, 0.52223655, 1.0]], + [ + [1.0, 0.0900485, 0.0], + [0.585632, 0.497497, 0.179719], + [0.0, 0.52223655, 1.0], + ], + ), + ( + 2, + 2, + [[0.5], [0.5]], + [[0, 0], [0, 0]], + [[0, 0], [0, 0]], + 1, + [[0.6], [0.4]], + "align", + [[0], [0]], + ), + ( + 1, + 3, + [[0.5, 0.3]], + [[1], [1.1], [1.3]], + [[1, 2], [2, 3], [3, 2]], + 2, + [[0.6, 0.4]], + None, + [[1, 1]], ), - (2, 2, [[0.5], [0.5]], [[0, 0], [0, 0]], [[0, 0], [0, 0]], 1, [[0.6], [0.4]], "align", [[0], [0]]), - (1, 3, [[0.5, 0.3]], [[1], [1.1], [1.3]], [[1, 2], [2, 3], [3, 2]], 2, [[0.6, 0.4]], None, [[1, 1]]), ( 2, 2, @@ -207,9 +362,23 @@ def test_get_stretched_component(stretching_factor, component, signal_length, ex 3, [[0.9, 0.4, 0.5], [1, 0, 0.4], [0, 0, 0.98]], "align", - [[1.281265, 0.104355, 0], [0.0, 0.0, 0.0], [0.239578, 0.965215, 1.162571]], + [ + [1.281265, 0.104355, 0], + [0.0, 0.0, 0.0], + [0.239578, 0.965215, 1.162571], + ], + ), + ( + 2, + 2, + [[0.5], [0.5]], + [[0, 0], [0, 0]], + [[0, 0], [0, 0]], + 1, + [[0.6], [0.4]], + "align", + [[0], [0]], ), - (2, 2, [[0.5], [0.5]], [[0, 0], [0, 0]], [[0, 0], [0, 0]], 1, [[0.6], [0.4]], "align", [[0], [0]]), ( 1, 3, @@ -262,7 +431,16 @@ def test_update_weights_matrix( 2, [[-9, -22], [-33, -44]], ), - ([[1, 2], [3, 4]], [[1], [1]], [[1], [1]], [[11, 22], [33, 44]], 1, 2, 2, [[-8, -22], [-26, -44]]), + ( + [[1, 2], [3, 4]], + [[1], [1]], + [[1], [1]], + [[11, 22], [33, 44]], + 1, + 2, + 2, + [[-8, -22], [-26, -44]], + ), ( [[1.1, 4.4], [1.2, 4.5], [14, 7.8]], [[0.4, 0.6], [0.75, 0.25]], @@ -351,13 +529,37 @@ def test_reconstruct_data(components): "data_input, lift, expected", [ # Correct structure: Each test case should be a tuple with three elements.pt - ([[1, -1, 1], [0, 0, 0], [2, 10, -3]], 1, [[4, 2, 4], [3, 3, 3], [5, 13, 0]]), - ([[1, -1, 1], [0, 0, 0], [2, 10, -3]], 0, [[1, -1, 1], [0, 0, 0], [2, 10, -3]]), - ([[1, -1, 1], [0, 0, 0], [2, 10, -3]], 0.5, [[2.5, 0.5, 2.5], [1.5, 1.5, 1.5], [3.5, 11.5, -1.5]]), - ([[1, -1, 1], [0, 0, 0], [2, 10, -3]], -1, [[4, 2, 4], [3, 3, 3], [5, 13, 0]]), - ([[0, 0, 0], [0, 0, 0], [0, 0, 0]], 100, [[0, 0, 0], [0, 0, 0], [0, 0, 0]]), + ( + [[1, -1, 1], [0, 0, 0], [2, 10, -3]], + 1, + [[4, 2, 4], [3, 3, 3], [5, 13, 0]], + ), + ( + [[1, -1, 1], [0, 0, 0], [2, 10, -3]], + 0, + [[1, -1, 1], [0, 0, 0], [2, 10, -3]], + ), + ( + [[1, -1, 1], [0, 0, 0], [2, 10, -3]], + 0.5, + [[2.5, 0.5, 2.5], [1.5, 1.5, 1.5], [3.5, 11.5, -1.5]], + ), + ( + [[1, -1, 1], [0, 0, 0], [2, 10, -3]], + -1, + [[4, 2, 4], [3, 3, 3], [5, 13, 0]], + ), + ( + [[0, 0, 0], [0, 0, 0], [0, 0, 0]], + 100, + [[0, 0, 0], [0, 0, 0], [0, 0, 0]], + ), ([[1.5, 2], [10.5, 1], [0.5, 2]], 1, [[2, 2.5], [11, 1.5], [1, 2.5]]), - ([[-10, -10.5], [-12.2, -12.2], [0, 0]], 1, [[2.2, 1.7], [0, 0], [12.2, 12.2]]), + ( + [[-10, -10.5], [-12.2, -12.2], [0, 0]], + 1, + [[2.2, 1.7], [0, 0], [12.2, 12.2]], + ), ], ) def test_lift_data(data_input, lift, expected): @@ -388,9 +590,30 @@ def test_initialize_componentstest_initialize_components(number_of_components, n # ([ComponentSignal([0,.5,1,1.5],20,0)],-2,20), # Raises an exception # ([ComponentSignal([0,.5,1,1.5],20,0)],1,0), # Raises an Exception # ([ComponentSignal([0,.5,1,1.5],20,0)],1,-3), # Raises an exception - ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0), ComponentSignal([0, 0.5, 1, 1.5], 20, 1)], 2, 20), - ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0), ComponentSignal([0, 0.5, 1, 21.5], 20, 1)], 2, 20), - ([ComponentSignal([0, 1, 1.5], 20, 0), ComponentSignal([0, 0.5, 1, 21.5], 20, 1)], 2, 20), + ( + [ + ComponentSignal([0, 0.5, 1, 1.5], 20, 0), + ComponentSignal([0, 0.5, 1, 1.5], 20, 1), + ], + 2, + 20, + ), + ( + [ + ComponentSignal([0, 0.5, 1, 1.5], 20, 0), + ComponentSignal([0, 0.5, 1, 21.5], 20, 1), + ], + 2, + 20, + ), + ( + [ + ComponentSignal([0, 1, 1.5], 20, 0), + ComponentSignal([0, 0.5, 1, 21.5], 20, 1), + ], + 2, + 20, + ), # ([ComponentSignal([0,.5,1,1.5],20,0),ComponentSignal([0,.5,1,1.5],20,1)],1,-3), # Negative signal length. Raises an exception # ([],1,20), # Empty components. Raises an Exception @@ -433,8 +656,19 @@ def test_construct_stretching_matrix(components, number_of_components, number_of ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), ] ), - ([ComponentSignal([0.25], 20, 0), ComponentSignal([0.25], 20, 1), ComponentSignal([0.25], 20, 2)]), - ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1)]), + ( + [ + ComponentSignal([0.25], 20, 0), + ComponentSignal([0.25], 20, 1), + ComponentSignal([0.25], 20, 2), + ] + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1), + ] + ), # ([ComponentSignal([[0, .25, .5, .75, 1],[0, .25, .5, .75, 1]], 20, 0), # ComponentSignal([[0, .25, .5, .75, 1],[0, .25, .5, .75, 1]], 20, 1)]), # iq is multidimensional. Expected to fail @@ -474,8 +708,19 @@ def test_construct_component_matrix(components): ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), ] ), - ([ComponentSignal([0.25], 20, 0), ComponentSignal([0.25], 20, 1), ComponentSignal([0.25], 20, 2)]), - ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1)]), + ( + [ + ComponentSignal([0.25], 20, 0), + ComponentSignal([0.25], 20, 1), + ComponentSignal([0.25], 20, 2), + ] + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1), + ] + ), # (ComponentSignal([], 20, 0)), # Expected to fail # ([]), #Expected to fail ], diff --git a/tests/test_version.py b/tests/test_version.py index eccc3cf..cac0822 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,10 +1,10 @@ -"""Unit tests for __version__.py -""" +"""Unit tests for __version__.py.""" -import diffpy.snmf +import diffpy.snmf # noqa def test_package_version(): - """Ensure the package version is defined and not set to the initial placeholder.""" + """Ensure the package version is defined and not set to the initial + placeholder.""" assert hasattr(diffpy.snmf, "__version__") assert diffpy.snmf.__version__ != "0.0.0"