diff --git a/.copier-answers.yml b/.copier-answers.yml index 4003a370e..b8de5ab8f 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,6 +1,6 @@ # WARNING: Do not edit this file manually. # Any changes will be overwritten by Copier. -_commit: v0.10.1-2-gd05af4f +_commit: v0.10.1-23-g55e8e0e _src_path: gh:easyscience/templates app_docs_url: https://easyscience.github.io/dynamics-app app_doi: 10.5281/zenodo.18877180 diff --git a/.github/workflows/lint-format.yml b/.github/workflows/lint-format.yml index fc451d14f..457d67f4b 100644 --- a/.github/workflows/lint-format.yml +++ b/.github/workflows/lint-format.yml @@ -1,9 +1,12 @@ -# The workflow is divided into several steps: -# - Check the validity of pyproject.toml -# - Check code linting -# - Check code formatting -# - Check formatting of docstrings in the code -# - Check formatting of Markdown, YAML, TOML, etc. files +# The workflow checks +# - the validity of pyproject.toml, +# - the presence and correctness of SPDX license headers, +# - linting and formatting of Python code, +# - linting and formatting of docstrings in Python code, +# - formatting of non-Python files (like markdown and toml). +# - linting of Python code in Jupyter notebooks (for library template). +# +# A summary of the checks is added to the GitHub Actions summary. name: Lint and format checks @@ -46,56 +49,53 @@ jobs: - name: Run post-install developer steps run: pixi run post-install - # Check the validity of pyproject.toml - name: Check validity of pyproject.toml - id: check_pyproject + id: pyproject continue-on-error: true shell: bash run: pixi run pyproject-check - # Check the presence and correctness of SPDX license headers in - # the code files - name: Check SPDX license headers - id: check_spdx_headers + id: license_headers continue-on-error: true shell: bash - run: pixi run spdx-check + run: pixi run license-check - # Check code linting with Ruff in the project root - - name: Check code linting - id: check_code_linting + - name: Check linting of Python code + id: py_lint continue-on-error: true shell: bash run: pixi run py-lint-check - # Check code formatting with Ruff in the project root - - name: Check code formatting - id: check_code_formatting + - name: Check formatting of Python code + id: py_format continue-on-error: true shell: bash run: pixi run py-format-check - # Check formatting of docstrings in the code with docformatter - - name: Check formatting of docstrings in the code - id: check_docs_formatting + - name: Check linting of docstrings in Python code + id: docstring_lint continue-on-error: true shell: bash - run: pixi run docs-format-check + run: pixi run docstring-lint-check - # Check formatting of MD, YAML, TOML, etc. files with Prettier in - # the project root - - name: Check formatting of MD, YAML, TOML, etc. files - id: check_others_formatting + - name: Check formatting of docstrings in Python code + id: docstring_format + continue-on-error: true + shell: bash + run: pixi run docstring-format-check + + - name: Check formatting of non-Python files (md, toml, etc.) + id: nonpy_format continue-on-error: true shell: bash run: pixi run nonpy-format-check - # Check formatting of Jupyter Notebooks in the tutorials folder - - name: Prepare notebooks and check formatting - id: check_notebooks_formatting + - name: Check linting of Python code in Jupyter notebooks (ipynb) + id: notebook_lint continue-on-error: true shell: bash - run: pixi run notebook-format-check + run: pixi run notebook-lint-check # Add summary - name: Add quality checks summary @@ -107,24 +107,26 @@ jobs: echo "" echo "| Check | Status |" echo "|-------|--------|" - echo "| pyproject.toml | ${{ steps.check_pyproject.outcome == 'success' && '✅' || '❌' }} |" - echo "| SPDX headers | ${{ steps.check_spdx_headers.outcome == 'success' && '✅' || '❌' }} |" - echo "| py lint | ${{ steps.check_code_linting.outcome == 'success' && '✅' || '❌' }} |" - echo "| py format | ${{ steps.check_code_formatting.outcome == 'success' && '✅' || '❌' }} |" - echo "| docstring format | ${{ steps.check_docs_formatting.outcome == 'success' && '✅' || '❌' }} |" - echo "| nonpy format | ${{ steps.check_others_formatting.outcome == 'success' && '✅' || '❌' }} |" - echo "| notebooks format | ${{ steps.check_notebooks_formatting.outcome == 'success' && '✅' || '❌' }} |" + echo "| pyproject.toml | ${{ steps.pyproject.outcome == 'success' && '✅' || '❌' }} |" + echo "| license headers | ${{ steps.license_headers.outcome == 'success' && '✅' || '❌' }} |" + echo "| py lint | ${{ steps.py_lint.outcome == 'success' && '✅' || '❌' }} |" + echo "| py format | ${{ steps.py_format.outcome == 'success' && '✅' || '❌' }} |" + echo "| docstring lint | ${{ steps.docstring_lint.outcome == 'success' && '✅' || '❌' }} |" + echo "| docstring format | ${{ steps.docstring_format.outcome == 'success' && '✅' || '❌' }} |" + echo "| nonpy format | ${{ steps.nonpy_format.outcome == 'success' && '✅' || '❌' }} |" + echo "| notebooks lint | ${{ steps.notebook_lint.outcome == 'success' && '✅' || '❌' }} |" } >> "$GITHUB_STEP_SUMMARY" # Fail job if any check failed - name: Fail job if any check failed if: | - steps.check_pyproject.outcome == 'failure' - || steps.check_spdx_headers.outcome == 'failure' - || steps.check_code_linting.outcome == 'failure' - || steps.check_code_formatting.outcome == 'failure' - || steps.check_docs_formatting.outcome == 'failure' - || steps.check_others_formatting.outcome == 'failure' - || steps.check_notebooks_formatting.outcome == 'failure' + steps.pyproject.outcome == 'failure' + || steps.license_headers.outcome == 'failure' + || steps.py_lint.outcome == 'failure' + || steps.py_format.outcome == 'failure' + || steps.docstring_lint.outcome == 'failure' + || steps.docstring_format.outcome == 'failure' + || steps.nonpy_format.outcome == 'failure' + || steps.notebook_lint.outcome == 'failure' shell: bash run: exit 1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index feb102db2..5fa0c2cbb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,9 +11,9 @@ repos: pass_filenames: false stages: [manual] - - id: spdx-headers-check - name: pixi run spdx-check - entry: pixi run spdx-check + - id: license-headers-check + name: pixi run license-check + entry: pixi run license-check language: system pass_filenames: false stages: [manual] @@ -32,23 +32,30 @@ repos: pass_filenames: false stages: [manual] - - id: pixi-nonpy-format-check - name: pixi run nonpy-format-check - entry: pixi run nonpy-format-check + - id: pixi-docstring-lint-check + name: pixi run docstring-lint-check + entry: pixi run docstring-lint-check + language: system + pass_filenames: false + stages: [manual] + + - id: pixi-docstring-format-check + name: pixi run docstring-format-check + entry: pixi run docstring-format-check language: system pass_filenames: false stages: [manual] - - id: pixi-docs-format-check - name: pixi run docs-format-check - entry: pixi run docs-format-check + - id: pixi-nonpy-format-check + name: pixi run nonpy-format-check + entry: pixi run nonpy-format-check language: system pass_filenames: false stages: [manual] - - id: pixi-notebook-format-check - name: pixi run notebook-format-check - entry: pixi run notebook-format-check + - id: pixi-notebook-lint-check + name: pixi run notebook-lint-check + entry: pixi run notebook-lint-check language: system pass_filenames: false stages: [manual] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d3dd2479e..85365779f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Thank you for your interest in contributing to **EasyDynamics**! -This guide explains how to: +This guide explains how you can: - Report issues - Contribute code @@ -39,7 +39,7 @@ Please make sure you follow the EasyScience organization-wide ## How to Interact With This Project -If you are not planning to modify the code, you may want to: +If you are not planning to contribute code, you may want to: - 🐞 Report a bug — see [Reporting Issues](#11-reporting-issues) - 🛡 Report a security issue — see @@ -62,17 +62,18 @@ We use the following branches: - `master` — stable releases only - `develop` — active development branch -- Short-lived branches — one branch per contribution - -All normal contributions must target the `develop` branch. - -This means: - -- Do **not** open Pull Requests against `master` -- Always create your branch from `develop` -- Always target `develop` when opening a Pull Request - -See ADR easyscience/.github#12 for full details on the branching +- Short-lived branches — feature or fix branches created for a single + contribution and deleted after merge + +> [!IMPORTANT] +> +> All normal contributions must target the `develop` branch. +> +> - Do **not** open Pull Requests against `master` +> - Always create your branch from `develop` +> - Always target `develop` when opening a Pull Request + +See ADR easyscience/.github#12 for more details on the branching strategy. --- @@ -122,8 +123,8 @@ This ensures you are working on the latest version of the project. ### 2.2. If You Are a Core Team Member -Core team members do not need to fork the repository. You can create a -new branch directly from `develop`, but the rest of the workflow remains +Core team members can create branches directly in this repository and +therefore do not need to fork it, but the rest of the workflow remains the same. --- @@ -150,12 +151,13 @@ Set up the environment: ```bash pixi install -pixi run post-install +pixi run post-install # Install additional tooling ``` After this step, your development environment is ready. -See ADR easyscience/.github#63 for more details about this decision. +See ADR easyscience/.github#63 for more details about using Pixi for +development. --- @@ -166,14 +168,16 @@ Never work directly on `develop`. Create a new branch: ```bash -git checkout -b my-change +git checkout -b my-change develop ``` -Use a clear and descriptive name, for example: - -- `improve-solver-speed` -- `fix-boundary-condition` -- `add-tutorial-example` +> [!IMPORTANT] +> +> Use a clear and descriptive name, for example: +> +> - `improve-solver-speed` +> - `fix-boundary-condition` +> - `add-tutorial-example` Clear branch names make reviews and history easier to understand. @@ -181,12 +185,7 @@ Clear branch names make reviews and history easier to understand. ## 5. Implementing Your Changes -While developing: - -- Make small, logical commits -- Write clear and descriptive commit messages -- Follow the Google docstring convention -- Add or update unit tests if behavior changes +While developing, make small, logical commits with clear messages. Example: @@ -195,48 +194,57 @@ git add . git commit -m "Improve performance of time integrator for large systems" ``` -Run tests locally: - -```bash -pixi run unit-tests -``` - -Running tests frequently is strongly recommended. - --- ## 6. Code Quality Checks +> [!IMPORTANT] +> +> When adding new functionality or making changes, make sure to add or +> update the following as needed: +> +> - 📘 docstrings +> - 🧪 unit tests + Before opening a Pull Request, always run: ```bash pixi run check ``` -This command runs: +This command: -- Formatting checks -- Linting -- Docstring validation -- Notebook checks -- Unit tests -- Other project validations +- Validates the pyproject.toml file +- Checks for licence headers in code files +- Identifies linting and formatting issues in Python code +- Checks docstring linting and formatting issues in Python code +- Detects formatting issues in non-Python files (MD, YAML, TOML etc.) +- Checks linting issues in Jupyter notebooks (if applicable) +- Runs unit tests A successful run should look like this: ```bash -pixi run pyproject-check...................................Passed -pixi run py-lint-check.....................................Passed -pixi run py-format-check...................................Passed -pixi run nonpy-format-check................................Passed -pixi run docs-format-check.................................Passed -pixi run notebook-format-check.............................Passed -pixi run unit-tests........................................Passed +pixi run pyproject-check.......................Passed +pixi run license-check.........................Passed +pixi run py-lint-check.........................Passed +pixi run py-format-check.......................Passed +pixi run docstring-lint-check..................Passed +pixi run docstring-format-check................Passed +pixi run nonpy-format-check....................Passed +pixi run notebook-lint-check...................Passed +pixi run unit-tests............................Passed ``` If something fails, read the error message carefully and fix the issue. -You can run individual checks, for example: +You can run individual checks, for example, to run only unit tests: + +```bash +pixi run unit-tests +``` + +or to run only Python linting checks: ```bash pixi run py-lint-check @@ -251,7 +259,7 @@ pixi run fix If everything is correctly formatted, you will see: ```text -✅ All code auto-formatting steps have been applied. +✅ All auto-formatting steps completed successfully! ``` This indicates that the auto-formatting pipeline completed successfully. @@ -264,7 +272,9 @@ If errors are reported, resolve them and re-run: pixi run check ``` -All checks must pass before your Pull Request can be merged. +> [!IMPORTANT] +> +> All checks must pass before your Pull Request can be merged. If you are unsure how to fix an issue, ask for help in your Pull Request discussion. @@ -289,8 +299,10 @@ On GitHub: ### Pull Request Title -The PR title appears in release notes and changelogs. It should be -concise and informative. +> [!IMPORTANT] +> +> The PR title appears in release notes and changelogs. It should be +> concise and informative. Good examples: @@ -302,7 +314,12 @@ Good examples: ### Required `[scope]` Label -Each Pull Request must include one `[scope]` label: +> [!IMPORTANT] +> +> Each Pull Request must include a `[scope]` label, which is used for +> automatically suggesting version bumps when preparing a new release. + +The available scopes are: | Label | Description | | ----------------------- | ----------------------------------------------------------------------- | @@ -312,7 +329,8 @@ Each Pull Request must include one `[scope]` label: | `[scope] maintenance` | Code/tooling cleanup without feature or bug fix (major.minor.**PATCH**) | | `[scope] significant` | Breaking or major changes (**MAJOR**.minor.patch) | -See ADR easyscience/.github#33 for full versioning rules. +See ADR easyscience/.github#33 for more details on the standardized +labeling scheme. --- @@ -355,7 +373,19 @@ git push ## 10. Documentation Contributions -If your change affects users, update the documentation. +> [!IMPORTANT] +> +> If your change affects user-facing functionality, update the project +> documentation accordingly — specifically the `nav:` (navigation) +> structure in `mkdocs.yml` and the relevant documentation Markdown +> files in `docs/docs/`. +> +> ```text +> 📁 docs +> ├── 📁 docs - Markdown files for documentation +> │ └── ... +> └── 📄 mkdocs.yml - Configuration file (navigation, theme, etc.) +> ``` This may include: @@ -376,31 +406,40 @@ Open the URL shown in the terminal to review your changes. ## 11. Reporting Issues -If you find a bug but do not want to fix it: +If you find a bug but cannot work on a fix, please consider opening an +issue. + +When reporting an issue, it helps to: -- Search existing issues first -- Provide clear reproduction steps -- Include logs and environment details +- Search existing issues first. +- Provide clear reproduction steps. +- Include logs, screenshots, and environment details. -Clear issue reports help maintainers significantly. +Clear and detailed reports help maintainers investigate and resolve +issues more effectively. --- ## 12. Security Issues -Do **not** report security vulnerabilities publicly. +> [!IMPORTANT] +> +> Please do **not** report security vulnerabilities publicly. -If you discover a potential vulnerability, contact the maintainers -privately. +If you discover a potential vulnerability, please contact the +maintainers privately so the issue can be investigated and addressed +responsibly. --- ## 13. Releases -Releases are created by merging `develop` into `master`. +Once your contribution is merged into `develop`, it will eventually be +included in the next stable release. -Once your contribution is merged into `develop`, it will be included in -the next stable release. +When enough changes have accumulated in `develop`, core team members +merge `develop` into `master` to prepare a new release. The release is +then tagged and published on GitHub and PyPI. --- diff --git a/pixi.lock b/pixi.lock index 79b0bdd02..f827c396e 100644 --- a/pixi.lock +++ b/pixi.lock @@ -84,6 +84,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl @@ -200,6 +201,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -334,6 +336,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl @@ -422,7 +425,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl @@ -450,6 +453,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -477,7 +481,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/3d/1e/f1e27cc6cfa9d5f941d9a0fabd86c3ddd515e8513ada591d1cfac143cec8/scipp-25.12.0-cp312-cp312-macosx_11_0_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -584,6 +588,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl @@ -672,7 +677,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl @@ -700,6 +705,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -727,7 +733,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ca/26/c38eeff61db9477fcc1af14f3dc0b1761a2e5567fe3d87b463f9beae2907/scipp-25.12.0-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -824,6 +830,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl @@ -938,6 +945,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -1089,6 +1097,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl @@ -1206,6 +1215,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -1340,6 +1350,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl @@ -1428,7 +1439,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl @@ -1457,6 +1468,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -1484,7 +1496,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/12/0d/3f98a936a30bff4a460b51b9f85c4d994f94249930b2d8bedeb8111a359e/scipp-25.12.0-cp311-cp311-macosx_11_0_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -1591,6 +1603,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl @@ -1679,7 +1692,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl @@ -1708,6 +1721,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -1735,7 +1749,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/6a/3a/ab0eb61593569d5a0d080002e4b8c0998cb1116d8710781b7225c304b880/scipp-25.12.0-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -1832,6 +1846,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl @@ -1947,6 +1962,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -2098,6 +2114,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl @@ -2214,6 +2231,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -2348,6 +2366,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl @@ -2436,7 +2455,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl @@ -2464,6 +2483,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -2491,7 +2511,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/3d/1e/f1e27cc6cfa9d5f941d9a0fabd86c3ddd515e8513ada591d1cfac143cec8/scipp-25.12.0-cp312-cp312-macosx_11_0_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -2598,6 +2618,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl @@ -2686,7 +2707,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl @@ -2714,6 +2735,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -2741,7 +2763,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ca/26/c38eeff61db9477fcc1af14f3dc0b1761a2e5567fe3d87b463f9beae2907/scipp-25.12.0-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -2838,6 +2860,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl @@ -2952,6 +2975,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -4029,6 +4053,17 @@ packages: - tomli>=2.0.0,<3.0.0 ; python_full_version < '3.11' and extra == 'tomli' - untokenize>=0.1.1,<0.2.0 requires_python: '>=3.9,<4.0' +- pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + name: docstring-parser-fork + version: 0.0.14 + sha256: 4c544f234ef2cc2749a3df32b70c437d77888b1099143a1ad5454452c574b9af + requires_dist: + - docstring-parser[docs] ; extra == 'dev' + - docstring-parser[test] ; extra == 'dev' + - pre-commit>=2.16.0 ; python_full_version >= '3.9' and extra == 'dev' + - pydoctor>=25.4.0 ; extra == 'docs' + - pytest ; extra == 'test' + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl name: dunamai version: 1.26.0 @@ -4039,8 +4074,8 @@ packages: requires_python: '>=3.5' - pypi: ./ name: easydynamics - version: 0.2.0+dev17 - sha256: a7275b719cb0a568d02f7ae8fd44370175019772c2629eac30bbec5e0adbfdd3 + version: 0.2.1+devdirty31 + sha256: 173a472b278902a0a8f520f54a773fa824d5d033443bd820536c6d6c60fe492f requires_dist: - darkdetect - easyscience @@ -4052,7 +4087,6 @@ packages: - pixi-kernel - plopp - pooch - - scipp<26.1.0 - build ; extra == 'dev' - copier ; extra == 'dev' - docformatter ; extra == 'dev' @@ -4073,6 +4107,7 @@ packages: - nbqa ; extra == 'dev' - nbstripout ; extra == 'dev' - pre-commit ; extra == 'dev' + - pydoclint ; extra == 'dev' - pytest ; extra == 'dev' - pytest-cov ; extra == 'dev' - pytest-forked ; extra == 'dev' @@ -6996,35 +7031,35 @@ packages: version: 2.4.2 sha256: c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32 requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl name: numpy version: 2.4.2 - sha256: 40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1 + sha256: b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl name: numpy version: 2.4.2 - sha256: 21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a + sha256: cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73 requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl name: numpy version: 2.4.2 - sha256: 7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1 + sha256: 6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7 requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl name: numpy version: 2.4.2 sha256: b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695 requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl name: numpy version: 2.4.2 - sha256: 209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85 + sha256: 805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27 requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl name: numpy version: 2.4.2 - sha256: e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825 + sha256: 209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85 requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: numpy @@ -8564,6 +8599,16 @@ packages: requires_dist: - typing-extensions>=4.14.1 requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl + name: pydoclint + version: 0.8.3 + sha256: 5fc9b82d0d515afce0908cb70e8ff695a68b19042785c248c4f227ad66b4a164 + requires_dist: + - click>=8.1.0 + - docstring-parser-fork>=0.0.12 + - tomli>=2.0.1 ; python_full_version < '3.11' + - flake8>=4 ; extra == 'flake8' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl name: pygments version: 2.19.2 @@ -9471,10 +9516,10 @@ packages: - ruff>=0.12.0 ; extra == 'dev' - cython-lint>=0.12.2 ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl name: scipy version: 1.17.1 - sha256: 35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8 + sha256: c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b requires_dist: - numpy>=1.26.4,<2.7 - pytest>=8.0.0 ; extra == 'test' @@ -9515,10 +9560,10 @@ packages: - ruff>=0.12.0 ; extra == 'dev' - cython-lint>=0.12.2 ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl name: scipy version: 1.17.1 - sha256: d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff + sha256: a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee requires_dist: - numpy>=1.26.4,<2.7 - pytest>=8.0.0 ; extra == 'test' @@ -9559,10 +9604,10 @@ packages: - ruff>=0.12.0 ; extra == 'dev' - cython-lint>=0.12.2 ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl name: scipy version: 1.17.1 - sha256: 41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87 + sha256: d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff requires_dist: - numpy>=1.26.4,<2.7 - pytest>=8.0.0 ; extra == 'test' @@ -9603,10 +9648,10 @@ packages: - ruff>=0.12.0 ; extra == 'dev' - cython-lint>=0.12.2 ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl name: scipy version: 1.17.1 - sha256: fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76 + sha256: 41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87 requires_dist: - numpy>=1.26.4,<2.7 - pytest>=8.0.0 ; extra == 'test' @@ -9647,10 +9692,10 @@ packages: - ruff>=0.12.0 ; extra == 'dev' - cython-lint>=0.12.2 ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl name: scipy version: 1.17.1 - sha256: 1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec + sha256: cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086 requires_dist: - numpy>=1.26.4,<2.7 - pytest>=8.0.0 ; extra == 'test' @@ -9691,10 +9736,10 @@ packages: - ruff>=0.12.0 ; extra == 'dev' - cython-lint>=0.12.2 ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl name: scipy version: 1.17.1 - sha256: e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696 + sha256: 766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd requires_dist: - numpy>=1.26.4,<2.7 - pytest>=8.0.0 ; extra == 'test' diff --git a/pixi.toml b/pixi.toml index 7593fd30f..e35cbaf16 100644 --- a/pixi.toml +++ b/pixi.toml @@ -34,6 +34,18 @@ platforms = ['win-64', 'linux-64', 'osx-64', 'osx-arm64'] # Channels for fetching packages channels = ['conda-forge'] +##################### +# System requirements +##################### + +[system-requirements] + +# Set minimum supported version for macOS to be 14.0 to ensure packages +# like `skipp` that only have wheels for macOS 14.0+ (macosx_14_0_arm64) +# are used instead of building from source. This is a workaround for +# Pixi, see https://github.com/prefix-dev/pixi/issues/5667 +macos = '14.0' + ########## # FEATURES ########## @@ -90,8 +102,9 @@ test = { depends-on = ['unit-tests'] } ########### pyproject-check = 'python -m validate_pyproject pyproject.toml' -docs-format-check = 'docformatter --check src/ docs/docs/tutorials/' -notebook-format-check = 'nbqa ruff docs/docs/tutorials/' +docstring-lint-check = 'pydoclint --quiet src/' +docstring-format-check = 'docformatter --check src/' +notebook-lint-check = 'nbqa ruff docs/docs/tutorials/' py-lint-check = 'ruff check src/ tests/ docs/docs/tutorials/' py-format-check = "ruff format --check src/ tests/ docs/docs/tutorials/" nonpy-format-check = "npx prettier --list-different --config=prettierrc.toml --ignore-unknown ." @@ -104,19 +117,19 @@ check = 'pre-commit run --hook-stage manual --all-files' ########## docs-format-fix = 'docformatter --in-place src/ docs/docs/tutorials/' -notebook-format-fix = 'nbqa ruff --fix docs/docs/tutorials/' +notebook-lint-fix = 'nbqa ruff --fix docs/docs/tutorials/' py-lint-fix = 'ruff check --fix src/ tests/ docs/docs/tutorials/' py-format-fix = "ruff format src/ tests/ docs/docs/tutorials/" nonpy-format-fix = 'npx prettier --write --list-different --config=prettierrc.toml --ignore-unknown .' nonpy-format-fix-modified = "python tools/nonpy_prettier_modified.py --write" -success-message-fix = 'echo "✅ All code auto-formatting steps have been applied."' +success-message-fix = 'echo "✅ All auto-formatting steps completed successfully!"' fix = { depends-on = [ 'py-format-fix', 'docs-format-fix', 'py-lint-fix', 'nonpy-format-fix', - 'notebook-format-fix', + 'notebook-lint-fix', 'success-message-fix', ] } @@ -229,9 +242,9 @@ github-labels = 'python tools/update_github_labels.py' # ⚖️ SPDX License Headers ######################### -spdx-remove = 'python tools/remove_spdx.py src/ tests/' -spdx-add = 'python tools/add_spdx.py src/ tests/' -spdx-check = 'python tools/check_spdx.py src/ tests/' +license-remove = 'python tools/remove_license_headers.py src/ tests/' +license-add = 'python tools/add_license_headers.py src/ tests/' +license-check = 'python tools/check_license_headers.py src/ tests/' #################################### # 🚀 Other Development & Build Tasks diff --git a/pyproject.toml b/pyproject.toml index 6f7d9d21b..18d906078 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,17 +22,16 @@ classifiers = [ ] requires-python = '>=3.11' dependencies = [ - 'easyscience', # The base library of the EasyScience framework - 'pooch', # Data downloader - 'darkdetect', # Detecting dark mode (system-level) - 'plopp', # Plotting library - 'jupyterlab', # Jupyter notebooks - 'pixi-kernel', # Pixi Jupyter kernel - 'ipykernel', # Jupyter kernel (required for running notebooks) - 'ipywidgets', # Widgets (needed for interactive matplotlib backends) - 'ipympl', # Matplotlib Jupyter widget backend (%matplotlib widget) - 'IPython', # Interactive Python shell - 'scipp<26.1.0', # TEMPORARY: ARM macOS fails with higher versions + 'easyscience', # The base library of the EasyScience framework + 'pooch', # Data downloader + 'darkdetect', # Detecting dark mode (system-level) + 'plopp', # Plotting library + 'jupyterlab', # Jupyter notebooks + 'pixi-kernel', # Pixi Jupyter kernel + 'ipykernel', # Jupyter kernel (required for running notebooks) + 'ipywidgets', # Widgets (needed for interactive matplotlib backends) + 'ipympl', # Matplotlib Jupyter widget backend (%matplotlib widget) + 'IPython', # Interactive Python shell ] [project.optional-dependencies] @@ -54,8 +53,9 @@ dev = [ 'versioningit', # Automatic versioning from git tags 'jupytext', # Jupyter notebook text format support 'jupyterquiz', # Quizzes in Jupyter notebooks - 'docformatter', # Code formatter for docstrings - 'interrogate', # Check for missing docstrings + 'pydoclint', # Docstring linter + 'docformatter', # Docstring formatter + 'interrogate', # Docstring coverage checker 'copier', # Template management 'mike', # MkDocs: Versioned documentation support 'mkdocs', # Static site generator @@ -80,12 +80,6 @@ Documentation = 'https://easyscience.github.io/dynamics-lib' # Build system configuration ############################ -# Build system 'hatch' -- Python project manager -# https://hatch.pypa.io/ - -# Versioning system 'versioningit' -- Versioning from git tags -# https://versioningit.readthedocs.io/ - [build-system] build-backend = 'hatchling.build' requires = ['hatchling', 'versioningit'] @@ -94,6 +88,9 @@ requires = ['hatchling', 'versioningit'] # Configuration for hatchling ############################# +# 'hatch' -- Build system for Python +# https://hatch.pypa.io/ + [tool.hatch.build.targets.wheel] packages = ['src/easydynamics'] @@ -107,6 +104,9 @@ source = 'versioningit' # Use versioningit to manage the version # Configuration for versioningit ################################ +# 'versioningit' -- Versioning from git tags +# https://versioningit.readthedocs.io/ + # Versioningit generates versions from git tags, so we don't need to # either specify them statically in pyproject.toml or save them in the # source code. Do not use {distance} in the version format, as it @@ -130,7 +130,7 @@ default-tag = 'v999.0.0' # Configuration for interrogate ################################ -# 'interrogate' -- Check for missing docstrings +# 'interrogate' -- Docstring coverage checker # https://interrogate.readthedocs.io/en/latest/ [tool.interrogate] @@ -142,6 +142,9 @@ verbose = 1 # Configuration for coverage/pytest-cov ####################################### +# 'coverage' -- Code coverage measurement tool +# https://coverage.readthedocs.io/en/latest/ + [tool.coverage.run] branch = true # Measure branch coverage as well source = ['src'] # Limit coverage to the source code directory @@ -155,6 +158,9 @@ fail_under = 0 # Minimum coverage percentage to pass # Configuration for pytest ########################## +# 'pytest' -- Testing framework +# https://docs.pytest.org/en/stable/ + [tool.pytest.ini_options] addopts = '--import-mode=importlib' markers = ['fast: mark test as fast (should be run on every push)'] @@ -173,6 +179,8 @@ indent-width = 4 line-length = 99 preview = true # Enable new rules that are not yet stable, like DOC +# Formatting options for Ruff + [tool.ruff.format] docstring-code-format = true # Whether to format code snippets in docstrings docstring-code-line-length = 72 # Line length for code snippets in docstrings @@ -180,31 +188,102 @@ indent-style = 'space' # PEP 8 recommends using spaces over tabs line-ending = 'lf' # Line endings will be converted to \n quote-style = 'single' # But double quotes in docstrings (PEP 8, PEP 257) -# https://docs.astral.sh/ruff/rules/ +# Linting rules to use with Ruff + [tool.ruff.lint] select = [ - 'E', # General PEP 8 style errors - 'E9', # Runtime errors (e.g., syntax errors, undefined names) - 'F', # Pyflakes-specific checks (e.g., unused variables, imports) - 'F63', # Issues related to invalid escape sequences in strings - 'F7', # Type-related errors (e.g., mismatched argument types, unsupported operations) - 'F82', # Import-related errors (e.g., unresolved imports, reimported modules) - 'I', # Import sorting issues (e.g., unsorted imports) - 'S', # Security-related issues (e.g., use of insecure functions or libraries) - 'W', # General PEP 8 warnings (e.g., lines too long, trailing whitespace) + # Various rules + #'C90', # https://docs.astral.sh/ruff/rules/#mccabe-c90 + 'F', # https://docs.astral.sh/ruff/rules/#pyflakes-f + #'FLY', # https://docs.astral.sh/ruff/rules/#flynt-fly + #'FURB', # https://docs.astral.sh/ruff/rules/#refurb-furb + 'I', # https://docs.astral.sh/ruff/rules/#isort-i + #'N', # https://docs.astral.sh/ruff/rules/#pep8-naming-n + #'NPY', # https://docs.astral.sh/ruff/rules/#numpy-specific-rules-npy + #'PGH', # https://docs.astral.sh/ruff/rules/#pygrep-hooks-pgh + #'PERF', # https://docs.astral.sh/ruff/rules/#perflint-perf + #'RUF', # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf + #'TRY', # https://docs.astral.sh/ruff/rules/#tryceratops-try + #'UP', # https://docs.astral.sh/ruff/rules/#pyupgrade-up + # pycodestyle (E, W) rules + 'E', # https://docs.astral.sh/ruff/rules/#error-e + 'W', # https://docs.astral.sh/ruff/rules/#warning-w + # Pylint (PL) rules + #'PLC', # https://docs.astral.sh/ruff/rules/#convention-plc + #'PLE', # https://docs.astral.sh/ruff/rules/#error-ple + #'PLR', # https://docs.astral.sh/ruff/rules/#refactor-plr + #'PLW', # https://docs.astral.sh/ruff/rules/#warning-plw + # flake8 rules + #'A', # https://docs.astral.sh/ruff/rules/#flake8-builtins-a + 'ANN', # https://docs.astral.sh/ruff/rules/#flake8-annotations-ann + #'ARG', # https://docs.astral.sh/ruff/rules/#flake8-unused-arguments-arg + #'ASYNC', # https://docs.astral.sh/ruff/rules/#flake8-async-async + #'B', # https://docs.astral.sh/ruff/rules/#flake8-bugbear-b + #'BLE', # https://docs.astral.sh/ruff/rules/#flake8-blind-except-ble + #'C4', # https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 + #'COM', # https://docs.astral.sh/ruff/rules/#flake8-commas-com + #'DTZ', # https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz + #'EM', # https://docs.astral.sh/ruff/rules/#flake8-errmsg-em + #'FA', # https://docs.astral.sh/ruff/rules/#flake8-future-annotations-fa + #'FBT', # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt + #'FIX', # https://docs.astral.sh/ruff/rules/#flake8-fixme-fix + #'G', # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g + #'ICN', # https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn + #'INP', # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp + #'ISC', # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc + #'LOG', # https://docs.astral.sh/ruff/rules/#flake8-logging-log + #'PIE', # https://docs.astral.sh/ruff/rules/#flake8-pie-pie + #'PT', # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt + #'PTH', # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth + #'PYI', # https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi + #'RET', # https://docs.astral.sh/ruff/rules/#flake8-return-ret + #'RSE', # https://docs.astral.sh/ruff/rules/#flake8-raise-rse + 'S', # https://docs.astral.sh/ruff/rules/#flake8-bandit-s + #'SIM', # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim + #'SLF', # https://docs.astral.sh/ruff/rules/#flake8-self-slf + #'SLOT', # https://docs.astral.sh/ruff/rules/#flake8-slots-slot + #'T20', # https://docs.astral.sh/ruff/rules/#flake8-print-t20 + #'TC', # https://docs.astral.sh/ruff/rules/#flake8-type-checking-tc + #'TD', # https://docs.astral.sh/ruff/rules/#flake8-todos-td + #'TID', # https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid +] + +# Exceptions to the linting rules + +# Ignore specific rules globally +ignore = [ + 'COM812', # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ + # The following covered by [tool.pydoclint] section below + 'D', # https://docs.astral.sh/ruff/rules/#pydocstyle-d + 'DOC', # https://docs.astral.sh/ruff/rules/#pydoclint-doc ] +# Ignore specific rules in certain files or directories [tool.ruff.lint.per-file-ignores] -'tests/**/*.py' = ['S101'] # Allow assert statements in tests +'tests/**' = [ + 'ANN', # https://docs.astral.sh/ruff/rules/#flake8-annotations-ann + 'DOC', # https://docs.astral.sh/ruff/rules/#pydoclint-doc + 'INP001', # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ + 'S101', # https://docs.astral.sh/ruff/rules/assert/ +] +'docs/**' = [ + 'INP001', # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ + 'T201', # https://docs.astral.sh/ruff/rules/print/ +] + +# Specific options for certain rules [tool.ruff.lint.flake8-tidy-imports] +# Disallow all relative imports ban-relative-imports = 'all' [tool.ruff.lint.isort] +# Forces all from imports to appear on their own line force-single-line = true [tool.ruff.lint.mccabe] -max-complexity = 10 # default is 10 +# Cyclomatic complexity threshold (default is 10) +max-complexity = 10 [tool.ruff.lint.pycodestyle] # PEP 8 line length guidance: @@ -222,8 +301,25 @@ max-line-length = 99 # as defined in the [tool.ruff.format] section above. max-doc-length = 99 -[tool.ruff.lint.pydocstyle] -convention = 'google' +############################# +# Configuration for pydoclint +############################# + +# 'pydoclint' -- Docstring linter, a faster alternative to +# 'darglint' or 'darglint2'. +# https://pypi.org/project/pydoclint/ + +# This is a more advanced docstring linter compared to Ruff's built-in +# docstring check rules D or DOC. For example, among many other things, +# it can check that arguments in the docstring, which are used by MkDocs +# and IDEs to render parameter documentation, remain synchronized with +# the parameter declarations in the code (in function's signature). + +[tool.pydoclint] +style = "google" +check-style-mismatch = true +check-arg-defaults = true +allow-init-docstring = true ################################ # Configuration for docformatter diff --git a/src/easydynamics/analysis/analysis.py b/src/easydynamics/analysis/analysis.py index acd122c41..483e214fb 100644 --- a/src/easydynamics/analysis/analysis.py +++ b/src/easydynamics/analysis/analysis.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +from typing import Any + import numpy as np import scipp as sc from easyscience.fitting.minimizers.utils import FitResults @@ -19,67 +21,37 @@ class Analysis(AnalysisBase): """For analysing two-dimensional data, i.e. intensity as function of - energy and Q. Supports independent fits of each Q value and - simultaneous fits of all Q. - - Args: - display_name (str): Display name of the analysis. - unique_name (str or None): Unique name of the analysis. If None, - a unique name is automatically generated. - experiment (Experiment | None): The Experiment associated with - this Analysis. If None, a default Experiment is created. - sample_model (SampleModel | None): The SampleModel associated - with this Analysis. If None, a default SampleModel is - created. - instrument_model (InstrumentModel | None): The InstrumentModel - associated with this Analysis. If None, a default - InstrumentModel is created. - extra_parameters (Parameter | list[Parameter] | None): Extra - parameters to be included in the analysis for advanced - users. If None, no extra parameters are added. - - Attributes: - experiment (Experiment): The Experiment associated with this - Analysis. - sample_model (SampleModel): The SampleModel associated with this - Analysis. - instrument_model (InstrumentModel): The InstrumentModel - associated with this Analysis. - Q (sc.Variable | None): The Q values from the associated - Experiment, if available. - energy (sc.Variable | None): The energy values from the - associated Experiment, if available. - temperature (Parameter | None): The temperature from the - associated SampleModel, if available. - extra_parameters (list[Parameter]): The extra parameters - included in this Analysis. + energy and Q. + + Supports independent fits of each Q value and simultaneous fits of + all Q. """ def __init__( self, - display_name: str = 'MyAnalysis', + display_name: str | None = 'MyAnalysis', unique_name: str | None = None, experiment: Experiment | None = None, sample_model: SampleModel | None = None, instrument_model: InstrumentModel | None = None, extra_parameters: Parameter | list[Parameter] | None = None, - ): + ) -> None: """Initialize an Analysis object. Args: - display_name (str): Display name of the analysis. - unique_name (str or None): Unique name of the analysis. If + display_name (str | None, default='MyAnalysis'): Display name of the analysis. + unique_name (str | None, default=None): Unique name of the analysis. If None, a unique name is automatically generated. - experiment (Experiment | None): The Experiment associated + experiment (Experiment | None, default=None): The Experiment associated with this Analysis. If None, a default Experiment is created. - sample_model (SampleModel | None): The SampleModel + sample_model (SampleModel | None, default=None): The SampleModel associated with this Analysis. If None, a default SampleModel is created. - instrument_model (InstrumentModel | None): The + instrument_model (InstrumentModel | None, default=None): The InstrumentModel associated with this Analysis. If None, a default InstrumentModel is created. - extra_parameters (Parameter | list[Parameter] | None): Extra + extra_parameters (Parameter | list[Parameter] | None, default=None): Extra parameters to be included in the analysis for advanced users. If None, no extra parameters are added. """ @@ -133,6 +105,10 @@ def analysis_list(self, value: list[Analysis1d]) -> None: To change the analysis list, modify the experiment, sample model, or instrument model. + Args: + value (list[Analysis1d]): The new list of Analysis1d objects. This + argument is ignored, as analysis_list is read-only. + Raises: AttributeError: Always raised, since analysis_list is read-only. @@ -156,17 +132,17 @@ def calculate( None, calculate for all Q indices and return a list of arrays. Args: - Q_index (int or None): Index of the Q value to calculate + Q_index (int | None, default=None): Index of the Q value to calculate for. If None, calculate for all Q values. + energy (sc.Variable | None, default=None): The energy values to use for + calculating the model. If None, uses the energy from the + experiment. Returns: list[np.ndarray] | np.ndarray: If Q_index is None, returns a list of numpy arrays, one for each Q index. If Q_index is an integer, returns a single numpy array for that Q index. - - Raises: - IndexError: If Q_index is not None and is out of bounds. """ if energy is None: energy = self.energy @@ -185,24 +161,22 @@ def fit( """Fit the model to the experimental data. Args: - fit_method (str): Method to use for fitting. Options are + fit_method (str, default="independent"): Method to use for fitting. Options are "independent" (fit each Q index independently, one after the other) or "simultaneous" (fit all Q indices simultaneously). Default is "independent". - Q_index (int or None): If fit_method is "independent", + Q_index (int | None, default=None): If fit_method is "independent", specify which Q index to fit. If None, fit all Q indices independently. Ignored if fit_method is "simultaneous". Default is None. Returns: - FitResults: a list of FitResults if fitting independently, + FitResults | list[FitResults]: a list of FitResults if fitting independently, or a single FitResults object if fitting simultaneously. Raises: ValueError: If fit_method is not "independent" or - "simultaneous" - IndexError: If fit_method is "independent" and Q_index is - out of bounds. + "simultaneous" or if there are no Q values available for fitting. """ if self.Q is None: @@ -228,7 +202,7 @@ def plot_data_and_model( plot_components: bool = True, add_background: bool = True, energy: sc.Variable | None = None, - **kwargs, + **kwargs: dict[str, Any], ) -> InteractiveFigure: """Plot the experimental data and the model prediction. Optionally also plot the individual components of the model. @@ -236,14 +210,17 @@ def plot_data_and_model( Uses Plopp for plotting: https://scipp.github.io/plopp/ Args: - Q_index (int or None): Index of the Q value to plot. If + Q_index (int | None, default=None): Index of the Q value to plot. If None, plot all Q values. Default is None. - plot_components (bool): Whether to plot the individual + plot_components (bool, default=True): Whether to plot the individual components. Default is True. - add_background (bool): Whether to add background components + add_background (bool, default=True): Whether to add background components to the sample model components when plotting. Default is True. - **kwargs (Any): Additional keyword arguments passed to plopp + energy (sc.Variable | None, default=None): The energy values to use for + calculating the model. If None, uses the energy from the + experiment. + **kwargs (dict[str, Any]): Additional keyword arguments passed to plopp for customizing the plot. Raises: @@ -329,8 +306,8 @@ def parameters_to_dataset(self) -> sc.Dataset: Returns: sc.Dataset: A dataset where each entry is a parameter, with - dimensions "Q" and values corresponding to the parameter - values. + dimensions "Q" and values corresponding to the parameter + values. Raises: UnitError: If there are inconsistent units for the same @@ -390,20 +367,24 @@ def parameters_to_dataset(self) -> sc.Dataset: def plot_parameters( self, names: str | list[str] | None = None, - **kwargs, + **kwargs: dict[str, Any], ) -> InteractiveFigure: """Plot fitted parameters as a function of Q. Args: - names (str | list[str] | None): Name(s) of the parameter(s) + names (str | list[str] | None, default=None): Name(s) of the parameter(s) to plot. If None, plots all parameters. - kwargs (Any): Additional keyword arguments passed to + **kwargs (dict[str, Any]): Additional keyword arguments passed to plopp.slicer for customizing the plot (e.g., title, linestyle, marker, color). Returns: InteractiveFigure: A Plopp InteractiveFigure containing the plot of the parameters. + + Raises: + TypeError: If names is not a string, list of strings, or None. + ValueError: If any of the specified parameter names are not found in the dataset. """ ds = self.parameters_to_dataset() @@ -550,6 +531,11 @@ def get_fit_functions(self) -> list[callable]: def _create_model_array(self, energy: sc.Variable | None = None) -> sc.DataArray: """Create a scipp array for the model. + Args: + energy (sc.Variable | None, default=None): The energy values to use for + calculating the model. If None, uses the energy from the + experiment. + Returns: sc.DataArray: A DataArray containing the model values, with dimensions "Q" and "energy". @@ -572,9 +558,12 @@ def _create_components_dataset( of the model for plotting. Args: - add_background (bool): Whether to add background components + add_background (bool, default=True): Whether to add background components to the sample model components when creating the - dataset. Default is True. + dataset. + energy (sc.Variable | None, default=None): The energy values to use for + calculating the components. If None, uses the energy from + the experiment. Raises: TypeError: If add_background is not True or False. diff --git a/src/easydynamics/analysis/analysis1d.py b/src/easydynamics/analysis/analysis1d.py index 0fdaa9cec..d2802a4a3 100644 --- a/src/easydynamics/analysis/analysis1d.py +++ b/src/easydynamics/analysis/analysis1d.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +from typing import Any + import numpy as np import scipp as sc from easyscience.fitting.fitter import Fitter as EasyScienceFitter @@ -20,75 +22,41 @@ class Analysis1d(AnalysisBase): """For analysing one-dimensional data, i.e. intensity as function of - energy for a single Q index. Is used primarily in the Analysis - class, but can also be used on its own for simpler analyses. - - Args: - display_name (str): Display name of the analysis. - unique_name (str or None): Unique name of the analysis. If None, - a unique name is automatically generated. - experiment (Experiment | None): The Experiment associated with - this Analysis. If None, a default Experiment is created. - sample_model (SampleModel | None): The SampleModel associated - with this Analysis. If None, a default SampleModel is - created. - instrument_model (InstrumentModel | None): The InstrumentModel - associated with this Analysis. If None, a default - InstrumentModel is created. - Q_index (int | None): The Q index to analyze. If None, the - analysis will not be able to calculate or fit until a - Q index is set. - extra_parameters (Parameter | list[Parameter] | None): Extra - parameters to be included in the analysis for advanced - users. If None, no extra parameters are added. - - Attributes: - experiment (Experiment): The Experiment associated with this - Analysis. - sample_model (SampleModel): The SampleModel associated with this - Analysis. - instrument_model (InstrumentModel): The InstrumentModel - associated with this Analysis. - Q (sc.Variable | None): The Q values from the associated - Experiment, if available. - energy (sc.Variable | None): The energy values from the - associated Experiment, if available. - temperature (Parameter | None): The temperature from the - associated SampleModel, if available. - Q_index (int | None): The Q index being analyzed. - extra_parameters (list[Parameter]): The extra parameters - included in this Analysis. + energy for a single Q index. + + Is used primarily in the Analysis class, but can also be used on its + own for simpler analyses. """ def __init__( self, - display_name: str = 'MyAnalysis', + display_name: str | None = 'MyAnalysis', unique_name: str | None = None, experiment: Experiment | None = None, sample_model: SampleModel | None = None, instrument_model: InstrumentModel | None = None, Q_index: int | None = None, extra_parameters: Parameter | list[Parameter] | None = None, - ): + ) -> None: """Initialize a Analysis1d. Args: - display_name (str): Display name of the analysis. - unique_name (str or None): Unique name of the analysis. If + display_name (str | None, default='MyAnalysis'): Display name of the analysis. + unique_name (str | None, default=None): Unique name of the analysis. If None, a unique name is automatically generated. - experiment (Experiment | None): The Experiment associated + experiment (Experiment | None, default=None): The Experiment associated with this Analysis. If None, a default Experiment is created. - sample_model (SampleModel | None): The SampleModel + sample_model (SampleModel | None, default=None): The SampleModel associated with this Analysis. If None, a default SampleModel is created. - instrument_model (InstrumentModel | None): The + instrument_model (InstrumentModel | None, default=None): The InstrumentModel associated with this Analysis. If None, a default InstrumentModel is created. - Q_index (int | None): The Q index to analyze. If None, the + Q_index (int | None, default=None): The Q index to analyze. If None, the analysis will not be able to calculate or fit until a Q index is set. - extra_parameters (Parameter | list[Parameter] | None): Extra + extra_parameters (Parameter | list[Parameter] | None, default=None): Extra parameters to be included in the analysis for advanced users. If None, no extra parameters are added. """ @@ -134,7 +102,7 @@ def Q_index(self, value: int | None) -> None: """Set the Q index for single Q analysis. Args: - index (int | None): The Q index. + value (int | None): The Q index. """ self._Q_index = self._verify_Q_index(value) @@ -149,7 +117,7 @@ def calculate(self, energy: sc.Variable | None = None) -> np.ndarray: sure the convolver is up to date before calculating. Args: - energy (sc.Variable | None): Optional energy grid to use for + energy (sc.Variable | None, default=None): Optional energy grid to use for calculation. If None, the energy grid from the experiment is used. @@ -166,7 +134,7 @@ def _calculate(self, energy: sc.Variable | None = None) -> np.ndarray: not check if the convolver is up to date. Args: - energy (sc.Variable | None): Optional energy grid to use for + energy (sc.Variable | None, default=None): Optional energy grid to use for calculation. If None, the energy grid from the experiment is used. @@ -191,14 +159,11 @@ def fit(self) -> FitResults: parameter optimization for performance reasons. Returns: - FitResult: The result of the fit. + FitResults: The result of the fit. Raises: ValueError: If no experiment is associated with this Analysis. - - Returns: - FitResults: The result of the fit. """ if self._experiment is None: raise ValueError('No experiment is associated with this Analysis.') @@ -220,7 +185,9 @@ def fit(self) -> FitResults: return fit_result - def as_fit_function(self, x=None, **kwargs) -> callable: + def as_fit_function( + self, x: np.ndarray | sc.Variable | None = None, **kwargs: dict[str, Any] + ) -> callable: """Return self._calculate as a fit function. The EasyScience fitter requires x as input, but @@ -229,13 +196,20 @@ def as_fit_function(self, x=None, **kwargs) -> callable: calculated model. Args: - x (Any): Ignored. The energy grid is taken from the - experiment. - kwargs (dict): Ignored. Included for compatibility with the + x (np.ndarray | sc.Variable | None, default=None): Ignored. + The energy grid is taken from the experiment. + **kwargs (dict[str, Any]): Ignored. Included for compatibility with the EasyScience fitter. + + Returns: + callable: A function that can be used as a fit function in the + EasyScience fitter, which returns the calculated model. """ - def fit_function(x, **kwargs): + def fit_function( + x: np.ndarray | sc.Variable | None = None, + **kwargs: dict[str, Any], + ) -> np.ndarray: return self._calculate() return fit_function @@ -260,7 +234,7 @@ def plot_data_and_model( plot_components: bool = True, add_background: bool = True, energy: sc.Variable | None = None, - **kwargs, + **kwargs: dict[str, Any], ) -> InteractiveFigure: """Plot the experimental data and the model prediction for the chosen Q index. Optionally also plot the individual components @@ -269,18 +243,21 @@ def plot_data_and_model( Uses Plopp for plotting: https://scipp.github.io/plopp/ Args: - plot_components (bool): Whether to plot the individual - components of the model. Default is True. - add_background (bool): Whether to add the background to the + plot_components (bool, default=True): Whether to plot the individual + components of the model. + add_background (bool, default=True): Whether to add the background to the model prediction when plotting individual components. - energy (sc.Variable | None): Optional energy grid to use for + energy (sc.Variable | None, default=None): Optional energy grid to use for plotting. If None, the energy grid from the experiment is used. - kwargs (dict): Keyword arguments to pass to the plotting + **kwargs (dict[str, Any]): Keyword arguments to pass to the plotting function. Returns: InteractiveFigure: A plot of the data and model. + + Raises: + ValueError: If no data is available to plot. """ import plopp as pp @@ -366,6 +343,9 @@ def _verify_energy(self, energy: sc.Variable | None) -> sc.Variable | None: Returns: sc.Variable | None: The verified energy, or None if no energy is provided. + + Raises: + TypeError: If energy is not a sc.Variable or None. """ if energy is not None and not isinstance(energy, sc.Variable): @@ -375,7 +355,7 @@ def _verify_energy(self, energy: sc.Variable | None) -> sc.Variable | None: def _calculate_energy_with_offset( self, energy: sc.Variable, - energy_offset: sc.Variable, + energy_offset: Parameter, ) -> sc.Variable: """Calculate the energy grid with the energy offset applied. @@ -431,21 +411,17 @@ def _evaluate_components( Args: components (ComponentCollection | ModelComponent): The components to evaluate. - convolver (Convolution | None): An optional Convolution + convolver (Convolution | None, default=None): An optional Convolution object to use for convolution. If None, a new Convolution object will be created if convolve is True. - convolve (bool): Whether to perform convolution with the + convolve (bool, default=True): Whether to perform convolution with the resolution. Default is True. - energy (sc.Variable | None): Optional energy grid to use for + energy (sc.Variable | None, default=None): Optional energy grid to use for evaluation. If None, the energy grid from the experiment is used. Returns: np.ndarray: The evaluated contribution of the components. - - Raises: - UnitError: If the energy and energy offset have incompatible - units. """ Q_index = self._require_Q_index() @@ -490,13 +466,16 @@ def _evaluate_components( ) return conv.convolution() - def _evaluate_sample(self, energy: sc.Variable | None = None) -> np.ndarray: + def _evaluate_sample( + self, + energy: sc.Variable | None = None, + ) -> np.ndarray: """Evaluate the sample contribution for a given Q index. Assumes that self._convolver is up to date. Args: - energy (sc.Variable | None): Optional energy grid to use for + energy (sc.Variable | None, default=None): Optional energy grid to use for evaluation. If None, the energy grid from the experiment is used. @@ -522,7 +501,7 @@ def _evaluate_sample_component( Args: component (ModelComponent): The sample component to evaluate. - energy (sc.Variable | None): Optional energy grid to use for + energy (sc.Variable | None, default=None): Optional energy grid to use for evaluation. If None, the energy grid from the experiment is used. @@ -540,7 +519,7 @@ def _evaluate_background(self, energy: sc.Variable | None = None) -> np.ndarray: """Evaluate the background contribution for the chosen Q index. Args: - energy (sc.Variable | None): Optional energy grid to use for + energy (sc.Variable | None, default=None): Optional energy grid to use for evaluation. If None, the energy grid from the experiment is used. @@ -569,7 +548,7 @@ def _evaluate_background_component( Args: component (ModelComponent): The background component to evaluate. - energy (sc.Variable | None): Optional energy grid to use for + energy (sc.Variable | None, default=None): Optional energy grid to use for evaluation. If None, the energy grid from the experiment is used. @@ -593,7 +572,7 @@ def _create_convolver( available, return None. Args: - energy (sc.Variable | None): Optional energy grid to use for + energy (sc.Variable | None, default=None): Optional energy grid to use for convolution. If None, the energy grid from the experiment is used. @@ -640,10 +619,10 @@ def _create_component_scipp_array( background if it is not None. Args: - component (ModelComponent): The component to evaluate - background (np.ndarray | None): Optional background to add + component (ModelComponent): The component to evaluate. + background (np.ndarray | None, default=None): Optional background to add to the component. - energy (sc.Variable | None): Optional energy grid to use for + energy (sc.Variable | None, default=None): Optional energy grid to use for evaluation. If None, the energy grid from the experiment is used. @@ -665,7 +644,7 @@ def _create_background_component_scipp_array( Args: component (ModelComponent): The component to evaluate. - energy (sc.Variable | None): Optional energy grid to use for + energy (sc.Variable | None, default=None): Optional energy grid to use for evaluation. If None, the energy grid from the experiment is used. @@ -684,7 +663,7 @@ def _create_sample_scipp_array(self, energy: sc.Variable | None = None) -> sc.Da background. Args: - energy (sc.Variable | None): Optional energy grid to use for + energy (sc.Variable | None, default=None): Optional energy grid to use for evaluation. If None, the energy grid from the experiment is used. @@ -704,8 +683,8 @@ def _create_components_dataset_single_Q( components. Args: - add_background (bool): Whether to add background components. - energy (sc.Variable | None): Optional energy grid to use for + add_background (bool, default=True): Whether to add background components. + energy (sc.Variable | None, default=None): Optional energy grid to use for evaluation. If None, the energy grid from the experiment is used. @@ -747,6 +726,9 @@ def _to_scipp_array( Args: values (np.ndarray): The values to convert. + energy (sc.Variable | None, default=None): Optional energy grid to use for the + energy coordinate. If None, the energy grid from the + experiment is used. Returns: sc.DataArray: The converted sc.DataArray. diff --git a/src/easydynamics/analysis/analysis_base.py b/src/easydynamics/analysis/analysis_base.py index 0c1ab1714..b5daa54db 100644 --- a/src/easydynamics/analysis/analysis_base.py +++ b/src/easydynamics/analysis/analysis_base.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +import numpy as np import scipp as sc from easyscience.base_classes.model_base import ModelBase as EasyScienceModelBase from easyscience.variable import Parameter @@ -19,75 +20,42 @@ class AnalysisBase(EasyScienceModelBase): SampleModel contains the model for the sample, and the InstrumentModel contains the model for the instrument, including background and resolution - - Args: - display_name (str): Display name of the analysis. - unique_name (str or None): Unique name of the analysis. If None, - a unique name is automatically generated. - experiment (Experiment | None): The Experiment associated with - this Analysis. If None, a default Experiment is created. - sample_model (SampleModel | None): The SampleModel associated - with this Analysis. If None, a default SampleModel is - created. - instrument_model (InstrumentModel | None): The InstrumentModel - associated with this Analysis. If None, a default - InstrumentModel is created. - extra_parameters (Parameter | list[Parameter] | None): Extra - parameters to be included in the analysis for advanced - users. If None, no extra parameters are added. - - Attributes: - experiment (Experiment): The Experiment associated with this - Analysis. - sample_model (SampleModel): The SampleModel associated with this - Analysis. - instrument_model (InstrumentModel): The InstrumentModel - associated with this Analysis. - Q (sc.Variable | None): The Q values from the associated - Experiment, if available. - energy (sc.Variable | None): The energy values from the - associated Experiment, if available. - temperature (Parameter | None): The temperature from the - associated SampleModel, if available. - extra_parameters (list[Parameter]): The extra parameters - included in this Analysis. """ def __init__( self, - display_name: str = 'MyAnalysis', + display_name: str | None = 'MyAnalysis', unique_name: str | None = None, experiment: Experiment | None = None, sample_model: SampleModel | None = None, instrument_model: InstrumentModel | None = None, extra_parameters: Parameter | list[Parameter] | None = None, - ): + ) -> None: """Initialize the AnalysisBase. Args: - display_name (str): Display name of the analysis. - unique_name (str or None): Unique name of the analysis. If + display_name (str | None, default='MyAnalysis'): Display name of the analysis. + unique_name (str | None, default=None): Unique name of the analysis. If None, a unique name is automatically generated. - experiment (Experiment | None): The Experiment associated + experiment (Experiment | None, default=None): The Experiment associated with this Analysis. If None, a default Experiment is created. - sample_model (SampleModel | None): The SampleModel + sample_model (SampleModel | None, default=None): The SampleModel associated with this Analysis. If None, a default SampleModel is created. - instrument_model (InstrumentModel | None): The + instrument_model (InstrumentModel | None, default=None): The InstrumentModel associated with this Analysis. If None, a default InstrumentModel is created. - extra_parameters (Parameter | list[Parameter] | None): Extra + extra_parameters (Parameter | list[Parameter] | None, default=None): Extra parameters to be included in the analysis for advanced users. If None, no extra parameters are added. Raises: - TypeError: If experiment is not an Experiment or None. - TypeError: If sample_model is not a SampleModel or None. - TypeError: If instrument_model is not an InstrumentModel or - None. - TypeError: If extra_parameters is not a Parameter, a list of - Parameters, or None. + TypeError: If experiment is not an Experiment or None or + if sample_model is not a SampleModel or None or if + instrument_model is not an InstrumentModel or None or if + extra_parameters is not a Parameter, a list of Parameters, + or None. """ super().__init__(display_name=display_name, unique_name=unique_name) @@ -145,6 +113,9 @@ def experiment(self) -> Experiment: def experiment(self, value: Experiment) -> None: """Set the Experiment for this Analysis. + Args: + value (Experiment): The Experiment to set for this Analysis. + Raises: TypeError: if value is not an Experiment. """ @@ -168,6 +139,9 @@ def sample_model(self) -> SampleModel: def sample_model(self, value: SampleModel) -> None: """Set the SampleModel for this Analysis. + Args: + value (SampleModel): The SampleModel to set for this Analysis. + Raises: TypeError: if value is not a SampleModel. """ @@ -190,6 +164,10 @@ def instrument_model(self) -> InstrumentModel: def instrument_model(self, value: InstrumentModel) -> None: """Set the InstrumentModel for this Analysis. + Args: + value (InstrumentModel): The InstrumentModel to set for this + Analysis. + Raises: TypeError: if value is not an InstrumentModel. """ @@ -204,17 +182,20 @@ def Q(self) -> sc.Variable | None: available. Returns: - sc.Variable: The Q values from the associated Experiment, - if available. - None: If the Experiment does not have any data. + sc.Variable | None: The Q values from the associated Experiment, + if available, and None if not. """ return self.experiment.Q @Q.setter - def Q(self, value) -> None: + def Q(self, value: sc.Variable) -> None: """Q cannot be set, as it is a read-only property derived from the Experiment. + Args: + value (sc.Variable): The Q values to set. This argument is + ignored, as Q is a read-only property. + Raises: AttributeError: If trying to set Q. """ @@ -226,18 +207,21 @@ def energy(self) -> sc.Variable | None: available. Returns: - sc.Variable: The energy values from the associated - Experiment, if available. - None: If the Experiment does not have any data. + sc.Variable | None: The energy values from the associated + Experiment, if available, and None if not. """ return self.experiment.energy @energy.setter - def energy(self, value) -> None: + def energy(self, value: sc.Variable) -> None: """Energy cannot be set, as it is a read-only property derived from the Experiment. + Args: + value (sc.Variable): The energy values to set. This argument is + ignored, as energy is a read-only property. + Raises: AttributeError: If trying to set energy. """ @@ -250,18 +234,21 @@ def temperature(self) -> Parameter | None: available. Returns: - Parameter: The temperature from the associated SampleModel, - if available. - None: If the SampleModel does not have a temperature. + Parameter | None: The temperature from the associated SampleModel, + if available, and None if not. """ return self.sample_model.temperature @temperature.setter - def temperature(self, value) -> None: + def temperature(self, value: np.ndarray | Parameter) -> None: """Temperature cannot be set, as it is a read-only property derived from the SampleModel. + Args: + value (np.ndarray | Parameter): The temperature to set. This argument is + ignored, as temperature is a read-only property. + Raises: AttributeError: If trying to set temperature. """ @@ -288,7 +275,7 @@ def extra_parameters(self, value: Parameter | list[Parameter]) -> None: Raises: TypeError: If value is not a Parameter, a list of - Parameters, or None. + Parameters, or None. """ if isinstance(value, Parameter): self._extra_parameters = [value] diff --git a/src/easydynamics/convolution/analytical_convolution.py b/src/easydynamics/convolution/analytical_convolution.py index 2026c461e..761f1cec4 100644 --- a/src/easydynamics/convolution/analytical_convolution.py +++ b/src/easydynamics/convolution/analytical_convolution.py @@ -20,20 +20,8 @@ class AnalyticalConvolution(ConvolutionBase): """Analytical convolution of a ModelComponent or ComponentCollection with a ResolutionModel. - Possible analytical convolutions are any combination of - delta functions, Gaussians, Lorentzians and Voigt profiles. - - Args: - energy (np.ndarray | sc.Variable): 1D array of energy values - where the convolution is evaluated. - sample_components (ComponentCollection | ModelComponent): The - sample model to be convolved. - resolution_components (ComponentCollection | ModelComponent): - The resolution model to convolve with. - energy_offset (Numeric | Parameter, optional): An offset to - shift the energy values by. Default is 0.0. - energy_unit (str | sc.Unit, optional): The unit of the energy. - Default is 'meV'. + Possible analytical convolutions are any combination of delta + functions, Gaussians, Lorentzians and Voigt profiles. """ # Mapping of supported component type pairs to convolution methods. @@ -54,20 +42,20 @@ def __init__( sample_components: ComponentCollection | ModelComponent | None = None, resolution_components: ComponentCollection | ModelComponent | None = None, energy_offset: Numeric | Parameter = 0.0, - ): + ) -> None: """Initialize an AnalyticalConvolution. Args: energy (np.ndarray | sc.Variable): 1D array of energy values where the convolution is evaluated. - sample_components (ComponentCollection | ModelComponent): + energy_unit (str | sc.Unit, default='meV'): The unit of the + energy. + sample_components (ComponentCollection | ModelComponent | None, default=None): The sample model to be convolved. - resolution_components (ComponentCollection | ModelComponent): + resolution_components (ComponentCollection | ModelComponent | None, default=None): The resolution model to convolve with. - energy_offset (Numeric | Parameter, optional): An offset to - shift the energy values by. Default is 0.0. - energy_unit (str | sc.Unit, optional): The unit of the - energy. Default is 'meV'. + energy_offset (Numeric | Parameter, default=0.0): An offset to + shift the energy values by. """ super().__init__( energy=energy, @@ -88,12 +76,6 @@ def convolution( Returns: np.ndarray: The convolution of the sample_components and resolution_components values evaluated at self.energy. - - Raises: - ValueError: If resolution_components contains delta - functions. - ValueError: If component pair cannot be handled - analytically. """ sample_components = self.sample_components.components @@ -205,7 +187,7 @@ def _convolute_delta_any( self, sample_component: DeltaFunction, resolution_components: ComponentCollection | ModelComponent, - ): + ) -> np.ndarray: """Convolution of delta function with any ModelComponent or ComponentCollection results in the same component or ComponentCollection shifted by the delta center. The areas are diff --git a/src/easydynamics/convolution/convolution.py b/src/easydynamics/convolution/convolution.py index 14df08f49..7cd742f5e 100644 --- a/src/easydynamics/convolution/convolution.py +++ b/src/easydynamics/convolution/convolution.py @@ -23,58 +23,14 @@ class Convolution(NumericalConvolutionBase): ComponentCollections with ResolutionComponents. Supports analytical convolution for pairs of analytical model - components (DeltaFunction, Gaussian, Lorentzian, Voigt), while - using numerical convolution for other components. - If temperature is provided, detailed balance correction is applied - to the sample model. In this case, all convolutions are handled - numerically. + components (DeltaFunction, Gaussian, Lorentzian, Voigt), while using + numerical convolution for other components. If temperature is + provided, detailed balance correction is applied to the sample + model. In this case, all convolutions are handled numerically. Includes a setting to normalize the detailed balance correction. Includes optional upsampling and extended range to improve accuracy of the numerical convolutions. Also warns about numerical instabilities if peaks are very wide or very narrow. - - Args: - energy (np.ndarray | scipp.Variable): 1D array of energy values - where the convolution is evaluated. - sample_components (ComponentCollection | ModelComponent): The - sample components to be convolved. - resolution_components (ComponentCollection | ModelComponent): - The resolution components to convolve with. - upsample_factor (int | None): The factor by which to upsample - the input data before convolution. Default is 5. - extension_factor (float | None): The factor by which to - extend the input data range before convolution. Default is - 0.2. - temperature (Parameter | float | None): The - temperature to use for detailed balance correction. Default - is None. - temperature_unit (str | sc.Unit | None): The unit of the - temperature parameter. Default is 'K'. - energy_unit (str | sc.Unit | None): The unit of the energy. - Default is 'meV'. - normalize_detailed_balance (bool | None): Whether to - normalize the detailed balance correction. Default is True. - - Attributes: - energy (scipp.Variable): 1D array of energy values where the - convolution is evaluated. - sample_components (ComponentCollection): The sample components - to be convolved. - resolution_components (ComponentCollection): The resolution - components to convolve with. - energy_offset (Parameter): Energy offset to apply to the energy - values before convolution. - upsample_factor (int): The factor by which to upsample the input - data before convolution. - extension_factor (float): The factor by which to extend the - input data range before convolution. - temperature (Parameter | None): The temperature to use for - detailed balance correction. - temperature_unit (str | sc.Unit): The unit of the temperature - parameter. - energy_unit (str | sc.Unit): The unit of the energy. - normalize_detailed_balance (bool): Whether to normalize the - detailed balance correction. """ # When these attributes are changed, the convolution plan @@ -98,36 +54,38 @@ def __init__( sample_components: ComponentCollection | ModelComponent, resolution_components: ComponentCollection | ModelComponent, energy_offset: Numeric | Parameter = 0.0, - upsample_factor: Numeric = 5, - extension_factor: Numeric = 0.2, + upsample_factor: Numeric | None = 5, + extension_factor: Numeric | None = 0.2, temperature: Parameter | Numeric | None = None, temperature_unit: str | sc.Unit = 'K', energy_unit: str | sc.Unit = 'meV', normalize_detailed_balance: bool = True, - ): + ) -> None: """Initialize the Convolution class. Args: - energy (np.ndarray | scipp.Variable): 1D array of energy + energy (np.ndarray | sc.Variable): 1D array of energy values where the convolution is evaluated. sample_components (ComponentCollection | ModelComponent): The sample components to be convolved. resolution_components (ComponentCollection | ModelComponent): The resolution components to convolve with. - upsample_factor (int | None): The factor by which to + energy_offset (Numeric | Parameter, default=0.0): An energy + offset to apply to the energy values before convolution. + upsample_factor (Numeric | None, default=5): The factor by which to upsample the input data before convolution. Default is 5. - extension_factor (float | None): The factor by which to + extension_factor (Numeric | None, default=0.2): The factor by which to extend the input data range before convolution. Default is 0.2. - temperature (Parameter | float | None): The + temperature (Parameter | Numeric | None, default=None): The temperature to use for detailed balance correction. Default is None. - temperature_unit (str | sc.Unit | None): The unit of the + temperature_unit (str | sc.Unit, default='K'): The unit of the temperature parameter. Default is 'K'. - energy_unit (str | sc.Unit | None): The unit of the energy. + energy_unit (str | sc.Unit, default='meV'): The unit of the energy. Default is 'meV'. - normalize_detailed_balance (bool | None): Whether to + normalize_detailed_balance (bool, default=True): Whether to normalize the detailed balance correction. Default is True. """ @@ -216,6 +174,10 @@ def _check_if_pair_is_analytic( Returns: bool: True if the component pair can be handled analytically, False otherwise. + + Raises: + TypeError: If either component is not a ModelComponent, or if + the resolution component is a DeltaFunction. """ if not isinstance(sample_component, ModelComponent): @@ -323,12 +285,16 @@ def _set_convolvers(self) -> None: self._numerical_convolver = None # Update some setters so the internal sample models are updated - def __setattr__(self, name, value) -> None: + def __setattr__(self, name: str, value: any) -> None: """Custom setattr to invalidate convolution plan on relevant attribute changes, and build a new plan. The new plan is only built after initialization (when _reactions_enabled is True) to avoid issues during __init__. + + Args: + name (str): The name of the attribute to set. + value (any): The value to set the attribute to. """ super().__setattr__(name, value) diff --git a/src/easydynamics/convolution/convolution_base.py b/src/easydynamics/convolution/convolution_base.py index bd7638994..1ace459c0 100644 --- a/src/easydynamics/convolution/convolution_base.py +++ b/src/easydynamics/convolution/convolution_base.py @@ -11,64 +11,39 @@ class ConvolutionBase: - """Base class for convolutions of sample and resolution models. This - base class has no convolution functionality. - - Args: - energy (np.ndarray | scipp.Variable): 1D array of energy values - where the convolution is evaluated. - sample_components (ComponentCollection | ModelComponent): The - sample model to be convolved. - resolution_components (ComponentCollection | ModelComponent): - The resolution model to convolve with. - energy_unit (str | sc.Unit, optional): The unit of the energy. - Default is 'meV'. - energy_offset (Numeric | Parameter, optional): The energy offset - applied to the convolution. Default is 0.0. - - Attributes: - energy (scipp.Variable): 1D array of energy values where the - convolution is evaluated - sample_components (ComponentCollection | ModelComponent): The - sample model to be convolved. - resolution_components (ComponentCollection | ModelComponent): - The resolution model to convolve with. - energy_unit (str | sc.Unit): The unit of the energy. - energy_offset (Parameter): The energy offset applied to the - convolution. + """Base class for convolutions of sample and resolution models. + + This base class has no convolution functionality. """ def __init__( self, energy: np.ndarray | sc.Variable, - sample_components: ComponentCollection | ModelComponent = None, - resolution_components: ComponentCollection | ModelComponent = None, + sample_components: ComponentCollection | ModelComponent | None = None, + resolution_components: ComponentCollection | ModelComponent | None = None, energy_unit: str | sc.Unit = 'meV', energy_offset: Numeric | Parameter = 0.0, - ): + ) -> None: """Initialize the ConvolutionBase. Args: - energy (np.ndarray | scipp.Variable): 1D array of energy + energy (np.ndarray | sc.Variable): 1D array of energy values where the convolution is evaluated. - sample_components (ComponentCollection | ModelComponent): + sample_components (ComponentCollection | ModelComponent | None, default=None): The sample model to be convolved. - resolution_components (ComponentCollection | ModelComponent): + resolution_components (ComponentCollection | ModelComponent | None, default=None): The resolution model to convolve with. - energy_unit (str | sc.Unit, optional): The unit of the - energy. Default is 'meV'. - energy_offset (Numeric | Parameter, optional): The energy + energy_unit (str | sc.Unit, default='meV'): The unit of the + energy. + energy_offset (Numeric | Parameter, default=0.0): The energy offset applied to the convolution. Default is 0.0. Raises: TypeError: If energy is not a numpy ndarray or a scipp - Variable. - TypeError: If energy_unit is not a string or scipp unit. - TypeError: If energy_offset is not a number or a Parameter. - TypeError: If sample_components is not a ComponentCollection - or ModelComponent. - TypeError: If resolution_components is not a - ComponentCollection or ModelComponent. + Variable or if energy_unit is not a string or scipp unit, or if + energy_offset is not a number or a Parameter, or if + sample_components is not a ComponentCollection or ModelComponent, or if + resolution_components is not a ComponentCollection or ModelComponent. """ if isinstance(energy, Numeric): energy = np.array([float(energy)]) @@ -130,8 +105,8 @@ def energy_offset(self, energy_offset: Numeric | Parameter) -> None: """Set the energy offset. Args: - energy_offset : Number or Parameter - The energy offset to apply to the convolution. + energy_offset (Numeric | Parameter): The energy offset to + apply to the convolution. Raises: TypeError: If energy_offset is not a number or a Parameter. @@ -157,12 +132,12 @@ def energy_with_offset(self) -> sc.Variable: return energy_with_offset @energy_with_offset.setter - def energy_with_offset(self, value) -> None: + def energy_with_offset(self, value: sc.Variable) -> None: """Energy with offset is a read-only property derived from energy and energy_offset. Args: - value: The value to set (ignored). + value (sc.Variable): The value to set (ignored). Raises: AttributeError: Always raised since energy_with_offset is @@ -187,8 +162,8 @@ def energy(self) -> sc.Variable: def energy(self, energy: np.ndarray | sc.Variable) -> None: """Set the energy. - Args: - energy (np.ndarray | scipp.Variable): 1D array of energy + Args: + energy (np.ndarray | sc.Variable): 1D array of energy values where the convolution is evaluated. Raises: @@ -235,7 +210,7 @@ def convert_energy_unit(self, energy_unit: str | sc.Unit) -> None: Raises: TypeError: If energy_unit is not a string or scipp unit. - UnitError: If energy cannot be converted to the specified + Exception: If energy cannot be converted to the specified unit. """ if not isinstance(energy_unit, (str, sc.Unit)): @@ -262,7 +237,7 @@ def sample_components(self) -> ComponentCollection | ModelComponent: """Get the sample model. Returns: - ComponentCollection or ModelComponent: The sample model to + ComponentCollection | ModelComponent: The sample model to be convolved. """ return self._sample_components @@ -272,7 +247,7 @@ def sample_components(self, sample_components: ComponentCollection | ModelCompon """Set the sample model. Args: - sample_components : ComponentCollection or ModelComponent + sample_components (ComponentCollection | ModelComponent): The sample model to be convolved. Raises: @@ -293,7 +268,7 @@ def resolution_components(self) -> ComponentCollection | ModelComponent: """Get the resolution model. Returns: - ComponentCollection or ModelComponent: The resolution model + ComponentCollection | ModelComponent: The resolution model to be convolved. """ return self._resolution_components @@ -305,9 +280,9 @@ def resolution_components( """Set the resolution model. Args: - resolution_components (ComponentCollection | ModelComponent) - : The resolution model to be convolved. Can be a - ComponentCollection or a single ModelComponent + resolution_components (ComponentCollection | ModelComponent): + The resolution model to be convolved. Can be a + ComponentCollection or a single ModelComponent Raises: TypeError: If resolution_components is not a diff --git a/src/easydynamics/convolution/numerical_convolution.py b/src/easydynamics/convolution/numerical_convolution.py index 79cc033d0..b2f45b450 100644 --- a/src/easydynamics/convolution/numerical_convolution.py +++ b/src/easydynamics/convolution/numerical_convolution.py @@ -15,53 +15,12 @@ class NumericalConvolution(NumericalConvolutionBase): """Numerical convolution of a ComponentCollection with a - ComponentCollection using FFT. Includes optional upsampling and - extended range to improve accuracy. Warns about very wide or very - narrow peaks in the models. If temperature is provided, detailed - balance correction is applied to the sample model. - - Args: - energy (np.ndarray | sc.Variable): 1D array of energy values - where the convolution is evaluated. - sample_components (ComponentCollection | ModelComponent): The - sample model to be convolved. - resolution_components (ComponentCollection | ModelComponent): - The resolution model to convolve with. - upsample_factor (int, optional): The factor by which to upsample - the input data before convolution. Default is 5. - extension_factor (float, optional): The factor by which to - extend the input data range before convolution. Default is - 0.2. - temperature (Parameter | float | None, optional): The - temperature to use for detailed balance correction. Default - is None. - temperature_unit (str | sc.Unit, optional): The unit of the - temperature parameter. Default is 'K'. - energy_unit (str | sc.Unit, optional): The unit of the energy. - Default is 'meV'. - normalize_detailed_balance (bool, optional): Whether to - normalize the detailed balance correction. Default is True. - - Attributes: - energy (np.ndarray | sc.Variable): The energy values where the - convolution is evaluated. - sample_components (ComponentCollection | ModelComponent): The - sample model to be convolved. - resolution_components (ComponentCollection | ModelComponent): - The resolution model to convolve with. - energy_offset (Parameter): The energy offset to apply to the - sample model before convolution. - upsample_factor (int): The factor by which to upsample the input - data before convolution. - extension_factor (float): The factor by which to extend the - input data range before convolution. - temperature (Parameter | float | None): The temperature to use - for detailed balance correction. - temperature_unit (str | sc.Unit): The unit of the temperature - parameter. - energy_unit (str | sc.Unit): The unit of the energy. - normalize_detailed_balance (bool): Whether to normalize the - detailed balance correction. + ComponentCollection using FFT. + + Includes optional upsampling and extended range to improve accuracy. + Warns about very wide or very narrow peaks in the models. If + temperature is provided, detailed balance correction is applied to + the sample model. """ def __init__( @@ -70,13 +29,13 @@ def __init__( sample_components: ComponentCollection | ModelComponent, resolution_components: ComponentCollection | ModelComponent, energy_offset: Numeric | Parameter = 0.0, - upsample_factor: Numeric = 5, - extension_factor: Numeric = 0.2, + upsample_factor: Numeric | None = 5, + extension_factor: Numeric | None = 0.2, temperature: Parameter | Numeric | None = None, temperature_unit: str | sc.Unit = 'K', energy_unit: str | sc.Unit = 'meV', normalize_detailed_balance: bool = True, - ): + ) -> None: """Initialize the NumericalConvolution object. Args: @@ -86,32 +45,22 @@ def __init__( The sample model to be convolved. resolution_components (ComponentCollection | ModelComponent): The resolution model to convolve with. - upsample_factor (int, optional): The factor by which to - upsample the input data before convolution. Default is - 5. - extension_factor (float, optional): The factor by which to - extend the input data range before convolution. Default - is 0.2. - temperature (Parameter | float | None, optional): The + energy_offset (Numeric | Parameter, default=0.0): An energy + offset to apply to the energy values before convolution. + upsample_factor (Numeric | None, default=5): The factor by which to + upsample the input data before convolution. + extension_factor (Numeric | None, default=0.2): The factor by which to + extend the input data range before convolution. + temperature (Parameter | Numeric | None, default=None): The temperature to use for detailed balance correction. Default is None. - temperature_unit (str | sc.Unit, optional): The unit of the - temperature parameter. Default is 'K'. - energy_unit (str | sc.Unit, optional): The unit of the + temperature_unit (str | sc.Unit, default='K'): The unit of the + temperature parameter. + energy_unit (str | sc.Unit, default='meV'): The unit of the energy. Default is 'meV'. - normalize_detailed_balance (bool, optional): Whether to + normalize_detailed_balance (bool, default=True): Whether to normalize the detailed balance correction. Default is True. - - Raises: - TypeError: If temperature is not None, a number, or a - Parameter. - TypeError: If temperature_unit is not a string or sc.Unit. - TypeError: If upsample_factor is not a number or None. - ValueError: If upsample_factor is not greater than 1. - TypeError: If extension_factor is not a number. - ValueError: If extension_factor is negative. - TypeError: If normalize_detailed_balance is not a bool. """ super().__init__( energy=energy, @@ -134,8 +83,7 @@ def convolution( correction if temperature is provided. Returns: - np.ndarray - The convolved values evaluated at energy. + np.ndarray: The convolved values evaluated at energy. """ # Give warnings if peaks are very wide or very narrow diff --git a/src/easydynamics/convolution/numerical_convolution_base.py b/src/easydynamics/convolution/numerical_convolution_base.py index e5e37a54a..54ccac6dc 100644 --- a/src/easydynamics/convolution/numerical_convolution_base.py +++ b/src/easydynamics/convolution/numerical_convolution_base.py @@ -14,8 +14,7 @@ from easydynamics.convolution.energy_grid import EnergyGrid from easydynamics.sample_model.component_collection import ComponentCollection from easydynamics.sample_model.components.model_component import ModelComponent - -Numerical = float | int +from easydynamics.utils.utils import Numeric # The thresholds are illustrated in # performance_tests/convolution/convolution_width_thresholds.ipynb @@ -29,49 +28,11 @@ class NumericalConvolutionBase(ConvolutionBase): """Base class for numerical convolutions of sample and resolution - models. Provides methods to handle upsampling, extension, and - detailed balance correction. This base class has no convolution - functionality. + models. - Args: - energy (np.ndarray | sc.Variable): 1D array of energy values - where the convolution is evaluated. - sample_components (ComponentCollection | ModelComponent): The - components to be convolved. - resolution_components (ComponentCollection | ModelComponent): - The resolution components to convolve with. - upsample_factor (int | None): The factor by which to upsample - the input data before convolution. Default is 5. - extension_factor (float | None): The factor by which to extend - the input data range before convolution. Default is 0.2. - temperature (Parameter | float | None): The temperature to use - for detailed balance correction. Default is None. - temperature_unit (str | sc.Unit): The unit of the temperature - parameter. Default is 'K'. - energy_unit (str | sc.Unit): The unit of the energy. Default is - 'meV'. - normalize_detailed_balance (bool): Whether to normalize the - detailed balance correction. Default is True. - - Attributes: - energy (np.ndarray | sc.Variable): 1D array of energy values - where the convolution is evaluated. - sample_components (ComponentCollection | ModelComponent): The - components to be convolved. - resolution_components (ComponentCollection | ModelComponent): - The resolution components to convolve with. - upsample_factor (int | None): The factor by which to upsample - the input data before convolution. - extension_factor (float | None): The factor by which to extend - the input data range before convolution. - temperature (Parameter | None): The temperature parameter for - detailed balance correction, or None if detailed balance is - disabled. - temperature_unit (str | sc.Unit): The unit of the temperature - parameter. - energy_unit (str | sc.Unit): The unit of the energy. - normalize_detailed_balance (bool): Whether to normalize the - detailed balance correction. + Provides methods to handle upsampling, extension, and detailed + balance correction. This base class has no convolution + functionality. """ def __init__( @@ -79,14 +40,14 @@ def __init__( energy: np.ndarray | sc.Variable, sample_components: ComponentCollection | ModelComponent, resolution_components: ComponentCollection | ModelComponent, - energy_offset: Numerical | Parameter = 0.0, - upsample_factor: Numerical = 5, - extension_factor: float = 0.2, - temperature: Parameter | float | None = None, + energy_offset: Numeric | Parameter = 0.0, + upsample_factor: Numeric | None = 5, + extension_factor: Numeric | None = 0.2, + temperature: Parameter | Numeric | None = None, temperature_unit: str | sc.Unit = 'K', energy_unit: str | sc.Unit = 'meV', normalize_detailed_balance: bool = True, - ): + ) -> None: """Initialize the NumericalConvolutionBase. Args: @@ -96,30 +57,25 @@ def __init__( The components to be convolved. resolution_components (ComponentCollection | ModelComponent): The resolution components to convolve with. - upsample_factor (int | None): The factor by which to - upsample the input data before convolution. Default is - 5. - extension_factor (float | None): The factor by which to - extend the input data range before convolution. Default - is 0.2. - temperature (Parameter | float | None): The temperature to - use for detailed balance correction. Default is None. - temperature_unit (str | sc.Unit): The unit of the - temperature parameter. Default is 'K'. - energy_unit (str | sc.Unit): The unit of the energy. Default - is 'meV'. - normalize_detailed_balance (bool): Whether to normalize the - detailed balance correction. Default is True. + energy_offset (Numeric | Parameter, default=0.0): An energy + offset to apply to the energy values before convolution. + upsample_factor (Numeric | None, default=5): The factor by which to + upsample the input data before convolution. + extension_factor (Numeric | None, default=0.2): The factor by which to + extend the input data range before convolution. + temperature (Parameter | Numeric | None, default=None): The temperature to + use for detailed balance correction. + temperature_unit (str | sc.Unit, default='K'): The unit of the + temperature parameter. + energy_unit (str | sc.Unit, default='meV'): The unit of the energy. + normalize_detailed_balance (bool, default=True): Whether to normalize the + detailed balance correction. Raises: TypeError: If temperature is not None, a number, or a - Parameter. - TypeError: If temperature_unit is not a string or sc.Unit. - TypeError: If upsample_factor is not a number or None. - ValueError: If upsample_factor is not greater than 1. - TypeError: If extension_factor is not a number. - ValueError: If extension_factor is negative. - TypeError: If normalize_detailed_balance is not a bool. + Parameter, or if temperature_unit is not a string or sc.Unit, or if + upsample_factor is not a number or None, or if extension_factor + is not a number, or if normalize_detailed_balance is not a bool. """ super().__init__( energy=energy, @@ -129,7 +85,7 @@ def __init__( energy_offset=energy_offset, ) - if temperature is not None and not isinstance(temperature, (Numerical, Parameter)): + if temperature is not None and not isinstance(temperature, (Numeric, Parameter)): raise TypeError('Temperature must be None, a number or a Parameter.') if not isinstance(temperature_unit, (str, sc.Unit)): @@ -160,21 +116,21 @@ def energy(self, energy: np.ndarray) -> None: self._energy_grid = self._create_energy_grid() @property - def upsample_factor(self) -> Numerical: + def upsample_factor(self) -> Numeric | None: """Get the upsample factor. Returns: - Numerical: The upsample factor. + Numeric | None: The upsample factor. """ return self._upsample_factor @upsample_factor.setter - def upsample_factor(self, factor: Numerical) -> None: + def upsample_factor(self, factor: Numeric | None) -> None: """Set the upsample factor and recreate the dense grid. Args: - factor (Numerical): The new upsample factor. + factor (Numeric | None): The new upsample factor. Raises: TypeError: If factor is not a number or None. @@ -185,7 +141,7 @@ def upsample_factor(self, factor: Numerical) -> None: self._energy_grid = self._create_energy_grid() return - if not isinstance(factor, Numerical): + if not isinstance(factor, Numeric): raise TypeError('Upsample factor must be a numerical value or None.') factor = float(factor) if factor <= 1.0: @@ -212,7 +168,7 @@ def extension_factor(self) -> float: return self._extension_factor @extension_factor.setter - def extension_factor(self, factor: Numerical) -> None: + def extension_factor(self, factor: Numeric) -> None: """ Set the extension factor and recreate the dense grid. The extension factor determines how much the energy range is @@ -221,18 +177,19 @@ def extension_factor(self, factor: Numerical) -> None: on each side. Args: - factor (Numerical): The new extension factor. + factor (Numeric): The new extension factor. Raises: TypeError: If factor is not a number. + ValueError: If factor is negative. """ - if not isinstance(factor, Numerical): + if not isinstance(factor, Numeric): raise TypeError('Extension factor must be a number.') if factor < 0.0: raise ValueError('Extension factor must be non-negative.') - self._extension_factor = factor + self._extension_factor = float(factor) # Recreate dense grid when extension factor is updated self._energy_grid = self._create_energy_grid() @@ -248,24 +205,24 @@ def temperature(self) -> Optional[Parameter]: return self._temperature @temperature.setter - def temperature(self, temp: Parameter | float | None) -> None: + def temperature(self, temp: Parameter | Numeric | None) -> None: """Set the temperature. If None, disables detailed balance correction and removes the temperature parameter. Args: - temp (Parameter | float | None): The temperature to set. + temp (Parameter | Numeric | None): The temperature to set. The unit will be the same as the existing temperature parameter if it exists, otherwise 'K'. Raises: - TypeError: If temp is not a float, Parameter, or None. + TypeError: If temp is not a Numeric, Parameter, or None. """ if temp is None: self._temperature = None - elif isinstance(temp, Numerical): + elif isinstance(temp, Numeric): if self._temperature is not None: self._temperature.value = float(temp) else: @@ -300,7 +257,7 @@ def normalize_detailed_balance(self, normalize: bool) -> None: Args: normalize (bool): Whether to normalize the detailed balance - factor. + factor. Raises: TypeError: If normalize is not a bool. @@ -324,6 +281,10 @@ def _create_energy_grid( Returns: EnergyGrid: The dense grid created by upsampling and extending energy. + + Raises: + ValueError: If energy array is not uniformly spaced when + upsample_factor is None, or if energy array has less than 2 points. """ if self.upsample_factor is None: # Check if the array is uniformly spaced. diff --git a/src/easydynamics/experiment/experiment.py b/src/easydynamics/experiment/experiment.py index 06ecd2663..9e087a6d6 100644 --- a/src/easydynamics/experiment/experiment.py +++ b/src/easydynamics/experiment/experiment.py @@ -19,43 +19,26 @@ class Experiment(NewBase): This is a minimal implementation that will be extended in the future. - - Args: - display_name (str): Display name of the experiment. - unique_name (str | None): Unique name of the experiment. If - None, a unique name will be generated. - data (sc.DataArray | str | None): Dataset associated with the - experiment. Can be a sc.DataArray or a filename string to - load from. If None, no data is loaded. - - Attributes: - data (sc.DataArray | None): Dataset associated with the - experiment. - binned_data (sc.DataArray | None): Binned dataset associated - with the experiment. This is derived from `data` and is - updated whenever `data` is set. """ def __init__( self, - display_name: str = 'MyExperiment', + display_name: str | None = 'MyExperiment', unique_name: str | None = None, data: sc.DataArray | str | None = None, - ): + ) -> None: """Initialize the Experiment object. Args: - display_name (str): Display name of the experiment. - unique_name (str | None): Unique name of the experiment. If + display_name (str | None, default="MyExperiment"): Display name of the experiment. + unique_name (str | None, default=None): Unique name of the experiment. If None, a unique name will be generated. - data (sc.DataArray | str | None): Dataset associated with + data (sc.DataArray | str | None, default=None): Dataset associated with the experiment. Can be a sc.DataArray or a filename string to load from. If None, no data is loaded. Raises: TypeError: If data is not a sc.DataArray, a string, or None. - ValueError: If the loaded data is missing required - coordinates. """ super().__init__( display_name=display_name, @@ -102,7 +85,6 @@ def data(self, value: sc.DataArray) -> None: Raises: TypeError: If the value is not a sc.DataArray. - ValueError: If the dataset is missing required coordinates. """ if not isinstance(value, sc.DataArray): raise TypeError(f'Data must be a sc.DataArray, not {type(value).__name__}') @@ -129,7 +111,7 @@ def binned_data(self, value: sc.DataArray) -> None: Args: value (sc.DataArray): The new binned dataset to associate - with this experiment (ignored) + with this experiment (ignored) Raises: AttributeError: Always, since binned_data is read-only. @@ -197,6 +179,9 @@ def get_masked_energy(self, Q_index: int) -> sc.Variable | None: Returns: sc.Variable | None: The masked energy values from the dataset, or None if no data is loaded. + + Raises: + IndexError: If Q_index is not a valid index for the Q values. """ if self._binned_data is None: return None @@ -219,19 +204,17 @@ def get_masked_energy(self, Q_index: int) -> sc.Variable | None: # Handle data ########### - def load_hdf5(self, filename: str, display_name: str | None = None): + def load_hdf5(self, filename: str, display_name: str | None = None) -> None: """Load data from an HDF5 file. Args: filename (str ): Path to the HDF5 file. - display_name (str | None): Optional display name for the + display_name (str | None, default=None): Optional display name for the experiment. Raises: TypeError: If filename is not a string or if display_name is - not a string or None. - ValueError: If the loaded data is missing required - coordinates. + not a string or None or if the loaded data is not a sc.DataArray. """ if not isinstance(filename, str): raise TypeError(f'Filename must be a string, not {type(filename).__name__}') @@ -251,11 +234,11 @@ def load_hdf5(self, filename: str, display_name: str | None = None): self._validate_coordinates(loaded_data) self.data = loaded_data - def save_hdf5(self, filename: str | None = None): + def save_hdf5(self, filename: str | None = None) -> None: """Save the dataset to HDF5. Args: - filename (str | None): Path to the output HDF5 file. + filename (str | None, default=None): Path to the output HDF5 file. If None, the file will be named after the unique_name of the experiment with a .h5 extension. @@ -279,7 +262,7 @@ def save_hdf5(self, filename: str | None = None): sc_save_hdf5(self._data, filename) - def remove_data(self): + def remove_data(self) -> None: """Remove the dataset from the experiment.""" self._data = None self._binned_data = None @@ -295,6 +278,7 @@ def rebin(self, dimensions: dict[str, int | sc.Variable]) -> None: Raises: TypeError: If dimensions is not a dictionary or if keys/values are of incorrect types. + ValueError: If there is no data to rebin. KeyError: If a specified dimension is not in the dataset. """ @@ -337,12 +321,12 @@ def rebin(self, dimensions: dict[str, int | sc.Variable]) -> None: # other methods ########### - def plot_data(self, slicer=False, **kwargs) -> None: + def plot_data(self, slicer: bool = False, **kwargs: dict) -> None: """Plot the dataset using plopp: https://scipp.github.io/plopp/ Args: - slicer (bool): If True, use plopp's slicer instead of plot. - **kwargs (Any): Additional keyword arguments to pass to plopp. + slicer (bool, default=False): If True, use plopp's slicer instead of plot. + **kwargs (dict): Additional keyword arguments to pass to plopp. Raises: ValueError: If there is no data to plot. @@ -384,6 +368,7 @@ def _validate_coordinates(data: sc.DataArray) -> None: data (sc.DataArray): The data to validate. Raises: + TypeError: If data is not a sc.DataArray. ValueError: If required coordinates are missing. """ if not isinstance(data, sc.DataArray): diff --git a/src/easydynamics/sample_model/background_model.py b/src/easydynamics/sample_model/background_model.py index 420fce403..2f4645f5a 100644 --- a/src/easydynamics/sample_model/background_model.py +++ b/src/easydynamics/sample_model/background_model.py @@ -13,49 +13,28 @@ class BackgroundModel(ModelBase): """BackgroundModel represents a model of the background in an experiment at various Q. - - Args: - display_name (str): Display name of the model. - unique_name (str | None): Unique name of the model. If None, a - unique name will be generated. - unit (str | sc.Unit | None): Unit of the model. Defaults to - "meV". - components (ModelComponent | ComponentCollection | None): - Template components of the model. If None, no components - are added. These components are copied into - ComponentCollections for each Q value. - Q (Q_type | None): Q values for the model. If None, Q is not - set. - - Attributes: - unit (str | sc.Unit): Unit of the model. - components (list[ModelComponent]): List of ModelComponents in - the model. - Q (np.ndarray | Numeric | list | ArrayLike | sc.Variable | None): - Q values of the model. """ def __init__( self, - display_name: str = 'MyBackgroundModel', + display_name: str | None = 'MyBackgroundModel', unique_name: str | None = None, unit: str | sc.Unit = 'meV', - components: ComponentCollection | ModelComponent | None = None, + components: ModelComponent | ComponentCollection | None = None, Q: Q_type | None = None, - ): + ) -> None: """Initialize the BackgroundModel. Args: - display_name (str): Display name of the model. - unique_name (str | None): Unique name of the model. If None, + display_name (str | None, default='MyBackgroundModel'): Display name of the model. + unique_name (str | None, default=None): Unique name of the model. If None, a unique name will be generated. - unit (str | sc.Unit | None): Unit of the model. Defaults to - "meV". - components (ModelComponent | ComponentCollection | None): + unit (str | sc.Unit, default='meV'): Unit of the model. + components (ModelComponent | ComponentCollection | None, default=None): Template components of the model. If None, no components are added. These components are copied into ComponentCollections for each Q value. - Q (Q_type | None): Q values for the model. If None, Q is not + Q (Q_type | None, default=None): Q values for the model. If None, Q is not set. """ super().__init__( diff --git a/src/easydynamics/sample_model/component_collection.py b/src/easydynamics/sample_model/component_collection.py index 90ae96194..24548bf50 100644 --- a/src/easydynamics/sample_model/component_collection.py +++ b/src/easydynamics/sample_model/component_collection.py @@ -20,46 +20,29 @@ class ComponentCollection(ModelBase): """Collection of model components representing a sample, background or resolution model. - - Args: - unit (str | sc.Unit): Unit of the sample model. Defaults to - "meV". - display_name (str): Display name of the sample model. - unique_name (str | None): Unique name of the sample model. - If None, a unique_name is automatically generated. - components (List[ModelComponent] | None): Initial model - components to add to the ComponentCollection. - - Attributes: - components (List[ModelComponent]): List of model components in - the collection. - unit (str | sc.Unit): Unit of the sample model. - display_name (str): Display name of the sample model. - unique_name (str): Unique name of the sample model. """ def __init__( self, unit: str | sc.Unit = 'meV', - display_name: str = 'MyComponentCollection', + display_name: str | None = 'MyComponentCollection', unique_name: str | None = None, - components: List[ModelComponent] | None = None, - ): + components: list[ModelComponent] | None = None, + ) -> None: """Initialize a new ComponentCollection. Args: - unit (str | sc.Unit | None): Unit of the sample model. - Defaults to "meV". - display_name (str | None): Display name of the sample model. - unique_name (str | None): Unique name of the sample model. - Defaults to None. - components (List[ModelComponent] | None): Initial model - components to add to the ComponentCollection. + unit (str | sc.Unit, default='meV'): Unit of the collection. + display_name (str | None, default="MyComponentCollection"): Display + name of the collection + unique_name (str | None, default=None): Unique name of the collection + Defaults to None. + components (list[ModelComponent] | None, default=None): Initial model + components to add to the ComponentCollection. Raises: TypeError: If unit is not a string or sc.Unit, or if components is not a list of ModelComponent. - ValueError: If components contains duplicate unique names. """ super().__init__(display_name=display_name, unique_name=unique_name) @@ -87,17 +70,17 @@ def components(self) -> list[ModelComponent]: """Get the list of components in the collection. Returns: - List[ModelComponent]: The components in the collection. + list[ModelComponent]: The components in the collection. """ return list(self._components) @components.setter - def components(self, components: List[ModelComponent]) -> None: + def components(self, components: list[ModelComponent]) -> None: """Set the list of components in the collection. Args: - components (List[ModelComponent]): The new list of + components (list[ModelComponent]): The new list of components. Raises: @@ -147,7 +130,7 @@ def unit(self) -> str | sc.Unit | None: Returns: str | sc.Unit | None: The unit of the ComponentCollection, - which is the same as the unit of its components. + which is the same as the unit of its components. """ return self._unit @@ -178,10 +161,13 @@ def convert_unit(self, unit: str | sc.Unit) -> None: Raises: TypeError: If unit is not a string or sc.Unit. - UnitError: If any component cannot be converted to the + Exception: If any component cannot be converted to the specified unit. """ + if not isinstance(unit, (str, sc.Unit)): + raise TypeError(f'Unit must be a string or sc.Unit, got {type(unit).__name__}') + old_unit = self._unit try: @@ -206,9 +192,9 @@ def append_component(self, component: ModelComponent | 'ComponentCollection') -> ComponentCollection to this ComponentCollection. Args: - component (ModelComponent | ComponentCollection): The component - to append. If a ComponentCollection is provided, all of its - components will be appended. + component (ModelComponent | "ComponentCollection"): The component + to append. If a ComponentCollection is provided, all of its + components will be appended. Raises: TypeError: If component is not a ModelComponent or @@ -240,6 +226,7 @@ def remove_component(self, unique_name: str) -> None: Args: unique_name (str): Unique name of the component to remove. + Raises: TypeError: If unique_name is not a string. KeyError: If no component with the given unique name exists @@ -262,10 +249,23 @@ def remove_component(self, unique_name: str) -> None: @property def components(self) -> list[ModelComponent]: + """Get the list of components in the collection. + + Returns: + list[ModelComponent]: The components in the collection. + """ return list(self._components) @components.setter - def components(self, components: List[ModelComponent]) -> None: + def components(self, components: list[ModelComponent]) -> None: + """Set the components in the collection. + + Args: + components (list[ModelComponent]): The new components in the collection + + Raises: + TypeError: If components is not a list of ModelComponent + """ if not isinstance(components, list): raise TypeError('components must be a list of ModelComponent instances.') for comp in components: @@ -279,10 +279,24 @@ def components(self, components: List[ModelComponent]) -> None: @property def is_empty(self) -> bool: + """Returns True if the collection has no components, otherwise + False. + + Returns: + bool: True if the collection has no components, otherwise False + """ return not self._components @is_empty.setter def is_empty(self, value: bool) -> None: + """is_empty is read-only. + + Args: + value (bool): ignored. + + Raises: + AttributeError: Always raised since is_empty is read-only + """ raise AttributeError( 'is_empty is a read-only property that indicates ' 'whether the collection has components.' @@ -293,7 +307,7 @@ def list_component_names(self) -> List[str]: Returns: List[str]: List of unique names of the components in the - collection. + collection. """ return [component.unique_name for component in self._components] @@ -307,8 +321,8 @@ def normalize_area(self) -> None: is useful for convolutions. Raises: - ValueError: If there are no components in the model. - ValueError: If the total area is zero or not finite, which + ValueError: If there are no components in the model or + if the total area is zero or not finite, which would prevent normalization. """ if not self.components: @@ -345,7 +359,7 @@ def get_all_variables(self) -> list[DescriptorBase]: """Get all parameters from the model component. Returns: - List[Parameter]: List of parameters in the component. + list[DescriptorBase]: List of parameters in the component. """ return [var for component in self.components for var in component.get_all_variables()] @@ -354,10 +368,10 @@ def evaluate(self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray) """Evaluate the sum of all components. Args: - x (Number, list, np.ndarray, sc.Variable, or sc.DataArray): + x (Numeric | list | np.ndarray | sc.Variable | sc.DataArray): Energy axis. - Returns + Returns: np.ndarray: Evaluated model values. """ @@ -373,7 +387,7 @@ def evaluate_component( """Evaluate a single component by name. Args: - x (Number, list, np.ndarray, sc.Variable, or sc.DataArray): + x (Numeric | list | np.ndarray | sc.Variable | sc.DataArray): Energy axis. unique_name (str): Component unique name. @@ -422,7 +436,7 @@ def __contains__(self, item: str | ModelComponent) -> bool: in the ComponentCollection. Args: - item (str or ModelComponent): The component name or instance + item (str | ModelComponent): The component name or instance to check for. Returns: diff --git a/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py b/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py index bf1007e6c..81a766f74 100644 --- a/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py +++ b/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py @@ -23,29 +23,6 @@ class DampedHarmonicOscillator(CreateParametersMixin, ModelComponent): $$ where $A$ is the area, $x_0$ is the center, and $\gamma$ is the width. - - - Args: - area (Int | float): Area under the curve. - center (Int | float): Resonance frequency, approximately the - peak position. - width (Int | float): Damping constant, approximately the - half width at half max (HWHM) of the peaks. - unit (str | sc.Unit): Unit of the parameters. - Defaults to "meV". - display_name (str | None): Display name of the component. - unique_name (str | None): Unique name of the component. - If None, a unique_name is automatically generated. - - Attributes: - area (Parameter): Area under the curve. - center (Parameter): Resonance frequency, approximately the - peak position. - width (Parameter): Damping constant, approximately the - half width at half max (HWHM) of the peaks. - unit (str | sc.Unit): Unit of the parameters. - display_name (str | None): Display name of the component. - unique_name (str | None): Unique name of the component. """ def __init__( @@ -56,25 +33,20 @@ def __init__( unit: str | sc.Unit = 'meV', display_name: str | None = 'DampedHarmonicOscillator', unique_name: str | None = None, - ): + ) -> None: """Initialize the Damped Harmonic Oscillator. Args: - area (Int | float): Area under the curve. - center (Int | float): Resonance frequency, approximately the + area (Numeric | Parameter, default=1.0): Area under the curve. + center (Numeric | Parameter, default=1.0): Resonance frequency, approximately the peak position. - width (Int | float): Damping constant, approximately the + width (Numeric | Parameter, default=1.0): Damping constant, approximately the half width at half max (HWHM) of the peaks. - unit (str | sc.Unit): Unit of the parameters. - Defaults to "meV". - display_name (str | None): Display name of the component. - unique_name (str | None): Unique name of the component. + unit (str | sc.Unit, default='meV'): Unit of the parameters. + display_name (str | None, default='DampedHarmonicOscillator'): + Display name of the component. + unique_name (str | None, default=None): Unique name of the component. If None, a unique_name is automatically generated. - - Raises: - TypeError: If any of the parameters are not numbers or - Parameters. - ValueError: If center or width are not positive. """ super().__init__( diff --git a/src/easydynamics/sample_model/components/delta_function.py b/src/easydynamics/sample_model/components/delta_function.py index 5a06a26f9..4dbf80444 100644 --- a/src/easydynamics/sample_model/components/delta_function.py +++ b/src/easydynamics/sample_model/components/delta_function.py @@ -22,49 +22,26 @@ class DeltaFunction(CreateParametersMixin, ModelComponent): as an identity. This is handled by the Convolution method. If the center is not provided, it will be centered at 0 and fixed, which is typically what you want in QENS. - - Args: - center (Int | float | None): Center of the delta function. If - None, defaults to 0 and is fixed. - area (Int | float): Total area under the curve. - unit (str | sc.Unit): Unit of the parameters. - Defaults to "meV". - display_name (str | None): Name of the component. - unique_name (str | None): Unique name of the component. - If None, a unique_name is automatically generated. - - Attributes: - center (Parameter): Center of the delta function. - area (Parameter): Total area under the curve. - unit (str | sc.Unit): Unit of the parameters. - display_name (str | None): Name of the component. - unique_name (str | None): Unique name of the component. """ def __init__( self, - center: None | Numeric | Parameter = None, + center: Numeric | Parameter | None = None, area: Numeric | Parameter = 1.0, unit: str | sc.Unit = 'meV', display_name: str | None = 'DeltaFunction', unique_name: str | None = None, - ): + ) -> None: """Initialize the Delta function. Args: - center (Int | float | None): Center of the delta function. + center (Numeric | Parameter | None, default=None): Center of the delta function. If None, defaults to 0 and is fixed. - area (Int | float): Total area under the curve. - unit (str | sc.Unit): Unit of the parameters. - Defaults to "meV". - display_name (str | None): Name of the component. - unique_name (str | None): Unique name of the component. + area (Numeric | Parameter, default=1.0): Total area under the curve. + unit (str | sc.Unit, default='meV'): Unit of the parameters. + display_name (str | None, default='DeltaFunction'): Name of the component. + unique_name (str | None, default=None): Unique name of the component. If None, a unique_name is automatically generated. - - Raises: - TypeError: If center is not a number or None. - TypeError: If area is not a number. - TypeError: If unit is not a string or sc.Unit. """ # Validate inputs and create Parameters if not given super().__init__( @@ -123,7 +100,7 @@ def center(self, value: Numeric | None) -> None: Args: value (Numeric | None): The new value for the center - parameter. If None, defaults to 0 and is fixed. + parameter. If None, defaults to 0 and is fixed. Raises: TypeError: If the value is not a number or None. diff --git a/src/easydynamics/sample_model/components/exponential.py b/src/easydynamics/sample_model/components/exponential.py index 8ad9fe09f..56a72912c 100644 --- a/src/easydynamics/sample_model/components/exponential.py +++ b/src/easydynamics/sample_model/components/exponential.py @@ -18,33 +18,10 @@ class Exponential(CreateParametersMixin, ModelComponent): The intensity is given by - $$ - I(x) = A e^{B (x-x_0)}, - $$ + $$ I(x) = A e^{B (x-x_0)}, $$ where $A$ is the amplitude, $x_0$ is the center, and $B$ describes the rate of decay or growth. - - - Args: - amplitude (Int | float | Parameter | None): Amplitude of the - exponential. - center (Int | float | Parameter | None): Center of the - exponential. - rate (Int | float | Parameter | None): Decay or growth constant - unit (str | sc.Unit): Unit of the parameters. Defaults to - "meV". - display_name (str | None): Name of the component. - unique_name (str | None): Unique name of the component. if - None, a unique_name is automatically generated. - - Attributes: - amplitude (Parameter): Amplitude of the exponential. - center (Parameter): Center of the exponential. - rate (Parameter): Decay or growth constant of the exponential. - unit (str | sc.Unit): Unit of the parameters. - display_name (str | None): Name of the component. - unique_name (str | None): Unique name of the component. """ def __init__( @@ -55,25 +32,25 @@ def __init__( unit: str | sc.Unit = 'meV', display_name: str | None = 'Exponential', unique_name: str | None = None, - ): + ) -> None: """Initialize the Exponential component. Args: - amplitude (Int | float | Parameter | None): Amplitude of the + amplitude (Numeric | Parameter, default=1.0): Amplitude of the Exponential. - center (Int | float | Parameter): Center of the Exponential. - rate (Int | float | Parameter | None): Decay or growth + center (Numeric | Parameter | None, default=None): Center of the Exponential. + If None, the center is fixed at 0. + rate (Numeric | Parameter, default=1.0): Decay or growth constant of the Exponential. - unit (str | sc.Unit): Unit of the parameters. Defaults to - "meV". - display_name (str | None): Name of the component. - unique_name (str | None): Unique name of the component. if + unit (str | sc.Unit, default='meV'): Unit of the parameters. + display_name (str | None, default='Exponential'): Name of the component. + unique_name (str | None, default=None): Unique name of the component. if None, a unique_name is automatically generated. Raises: TypeError: If amplitude, center, or rate are not numbers or Parameters. - TypeError: If unit is not a string or sc.Unit. + ValueError: If amplitude, center or rate are not finite numbers """ # Validate inputs and create Parameters if not given super().__init__( @@ -94,7 +71,7 @@ def __init__( ) center = self._create_center_parameter( - center=center, name=display_name, fix_if_none=False, unit=self._unit + center=center, name=display_name, fix_if_none=True, unit=self._unit ) if not isinstance(rate, (Parameter, Numeric)): @@ -146,17 +123,21 @@ def center(self) -> Parameter: return self._center @center.setter - def center(self, value: Numeric) -> None: + def center(self, value: Numeric | None) -> None: """Set the center parameter value. Args: - value (Numeric): The new value for the center - parameter. + value (Numeric | None): The new value for the center + parameter. Raises: TypeError: If the value is not a number. """ + if value is None: + value = 0.0 + self._center.fixed = True + if not isinstance(value, Numeric): raise TypeError('center must be a number') self._center.value = value @@ -175,11 +156,11 @@ def rate(self, value: Numeric) -> None: """Set the rate parameter value. Args: - value (Numeric | None): The new value for the rate - parameter. + value (Numeric): The new value for the rate + parameter. Raises: - TypeError: If the value is not a number or None. + TypeError: If the value is not a number. """ if not isinstance(value, Numeric): raise TypeError('rate must be a number') @@ -203,7 +184,7 @@ def evaluate( rate. Args: - x (Numeric or list or np.ndarray or sc.Variable or sc.DataArray): + x (Numeric | list | np.ndarray | sc.Variable | sc.DataArray): The x values at which to evaluate the Exponential. Returns: @@ -216,13 +197,20 @@ def evaluate( return self.amplitude.value * np.exp(exponent) - def convert_unit(self, unit: str | sc.Unit): + def convert_unit(self, unit: str | sc.Unit) -> None: """Convert the unit of the Parameters in the component. Args: - unit (str or sc.Unit): The new unit to convert to. + unit (str | sc.Unit): The new unit to convert to. + + Raises: + TypeError: If unit is not a string or sc.Unit. + Exception: If conversion fails for any parameter. """ + if not isinstance(unit, (str, sc.Unit)): + raise TypeError('unit must be a string or sc.Unit') + old_unit = self._unit pars = [self.amplitude, self.center] try: diff --git a/src/easydynamics/sample_model/components/gaussian.py b/src/easydynamics/sample_model/components/gaussian.py index 7e89cf18a..892577e17 100644 --- a/src/easydynamics/sample_model/components/gaussian.py +++ b/src/easydynamics/sample_model/components/gaussian.py @@ -31,25 +31,6 @@ class Gaussian(CreateParametersMixin, ModelComponent): If the center is not provided, it will be centered at 0 and fixed, which is typically what you want in QENS. - - Args: - area (Int | float | Parameter | None): Area of the Gaussian. - center (Int | float | Parameter | None): Center of the - Gaussian. If None, defaults to 0 and is fixed. - width (Int | float | Parameter | None): Standard deviation. - unit (str | sc.Unit): Unit of the parameters. Defaults to - "meV". - display_name (str | None): Name of the component. - unique_name (str | None): Unique name of the component. if - None, a unique_name is automatically generated. - - Attributes: - area (Parameter): Area of the Gaussian. - center (Parameter): Center of the Gaussian. - width (Parameter): Standard deviation of the Gaussian. - unit (str | sc.Unit): Unit of the parameters. - display_name (str | None): Name of the component. - unique_name (str | None): Unique name of the component. """ def __init__( @@ -60,25 +41,18 @@ def __init__( unit: str | sc.Unit = 'meV', display_name: str | None = 'Gaussian', unique_name: str | None = None, - ): + ) -> None: """Initialize the Gaussian component. Args: - area (Int | float | Parameter | None): Area of the Gaussian. - center (Int | float | Parameter | None): Center of the + area (Numeric | Parameter, default=1.0): Area of the Gaussian. + center (Numeric | Parameter | None, default=None): Center of the Gaussian. If None, defaults to 0 and is fixed. - width (Int | float | Parameter | None): Standard deviation. - unit (str | sc.Unit): Unit of the parameters. Defaults to - "meV". - display_name (str | None): Name of the component. - unique_name (str | None): Unique name of the component. if - None, a unique_name is automatically generated. - - Raises: - TypeError: If area, center, or width are not numbers or - Parameters. - ValueError: If width is not positive. - TypeError: If unit is not a string or sc.Unit. + width (Numeric | Parameter, default=1.0): Standard deviation. + unit (str | sc.Unit, default='meV'): Unit of the parameters. + display_name (str | None, default='Gaussian'): Name of the component. + unique_name (str | None, default=None): Unique name of the component. + if None, a unique_name is automatically generated. """ # Validate inputs and create Parameters if not given super().__init__( @@ -134,12 +108,12 @@ def center(self) -> Parameter: return self._center @center.setter - def center(self, value: Numeric) -> None: + def center(self, value: Numeric | None) -> None: """Set the center parameter value. Args: value (Numeric | None): The new value for the center - parameter. If None, defaults to 0 and is fixed. + parameter. If None, defaults to 0 and is fixed. Raises: TypeError: If the value is not a number or None. @@ -166,8 +140,8 @@ def width(self, value: Numeric) -> None: """Set the width parameter value. Args: - value (Numeric | None): The new value for the width - parameter. + value (Numeric): The new value for the width + parameter. Raises: TypeError: If the value is not a number or None. @@ -203,7 +177,7 @@ def evaluate( Args: - x (Numeric or list or np.ndarray or sc.Variable or sc.DataArray): + x (Numeric | list | np.ndarray | sc.Variable | sc.DataArray): The x values at which to evaluate the Gaussian. Returns: diff --git a/src/easydynamics/sample_model/components/lorentzian.py b/src/easydynamics/sample_model/components/lorentzian.py index b61b7896b..8f5697bd5 100644 --- a/src/easydynamics/sample_model/components/lorentzian.py +++ b/src/easydynamics/sample_model/components/lorentzian.py @@ -25,26 +25,6 @@ class Lorentzian(CreateParametersMixin, ModelComponent): If the center is not provided, it will be centered at 0 and fixed, which is typically what you want in QENS. - - Args: - area (Int | float | Parameter): Area of the Lorentzian. - center (Int | float | None | Parameter): Center of the - Lorentzian. If None, defaults to 0 and is fixed - width (Int | float | Parameter): Half width at half maximum - (HWHM). - unit (str | sc.Unit): Unit of the parameters. Defaults to "meV". - display_name (str | None): Name of the component. - unique_name (str | None): Unique name of the component. If None, - a unique_name is automatically generated. - - Attributes: - area (Parameter): Area of the Lorentzian. - center (Parameter): Center of the Lorentzian. - width (Parameter): Half width at half maximum (HWHM) of the - Lorentzian. - unit (str | sc.Unit): Unit of the parameters. - display_name (str | None): Name of the component. - unique_name (str | None): Unique name of the component. """ def __init__( @@ -55,24 +35,19 @@ def __init__( unit: str | sc.Unit = 'meV', display_name: str | None = 'Lorentzian', unique_name: str | None = None, - ): + ) -> None: """Initialize the Lorentzian component. Args: - area (Int | float | Parameter): Area of the Lorentzian. - center (Int | float | None | Parameter): Center of the - Lorentzian. If None, defaults to 0 and is fixed - width (Int | float | Parameter): Half width at half maximum + area (Numeric | Parameter, default=1.0): Area of the Lorentzian. + center (Numeric | Parameter | None, default=None): Center of the + Lorentzian. If None, defaults to 0 and is fixed. + width (Numeric | Parameter, default=1.0): Half width at half maximum (HWHM). - unit (str | sc.Unit): Unit of the parameters. Defaults to - "meV". - display_name (str | None): Name of the component. - unique_name (str | None): Unique name of the component. If + unit (str | sc.Unit, default='meV'): Unit of the parameters. + display_name (str | None, default='Lorentzian'): Name of the component. + unique_name (str | None, default=None): Unique name of the component. If None, a unique_name is automatically generated. - - Raises: - TypeError: If any of the parameters are of the wrong type. - ValueError: If width is not positive. """ super().__init__( @@ -157,11 +132,11 @@ def width(self, value: Numeric) -> None: """Set the width parameter value (HWHM). Args: - value (Numeric | None): The new value for the width - parameter. + value (Numeric): The new value for the width + parameter. Raises: - TypeError: If the value is not a number or None. + TypeError: If the value is not a number. ValueError: If the value is not positive. """ if not isinstance(value, Numeric): @@ -186,7 +161,7 @@ def evaluate(self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray) the half width at half maximum (HWHM). Args: - x (Numeric or list or np.ndarray or sc.Variable or sc.DataArray): + x (Numeric | list | np.ndarray | sc.Variable | sc.DataArray): The x values at which to evaluate the Lorentzian. Returns: diff --git a/src/easydynamics/sample_model/components/mixins.py b/src/easydynamics/sample_model/components/mixins.py index c0b14d585..97c324224 100644 --- a/src/easydynamics/sample_model/components/mixins.py +++ b/src/easydynamics/sample_model/components/mixins.py @@ -40,15 +40,16 @@ def _create_area_parameter( Args: area (Numeric | Parameter): The area value or Parameter. name (str): The name of the model component. - unit (str | sc.Unit): The unit of the area Parameter. - minimum_area (float): The minimum allowed area. + unit (str | sc.Unit, default='meV'): The unit of the area Parameter. + minimum_area (float, default=MINIMUM_AREA): The minimum allowed area. Returns: Parameter: The validated area Parameter. Raises: TypeError: If area is not a number or a Parameter. - Warning: If area is negative. + ValueError: If area is not a finite number or if the area + Parameter has a non-finite value. """ if not isinstance(area, (Parameter, Numeric)): raise TypeError('area must be a number or a Parameter.') @@ -85,13 +86,19 @@ def _create_center_parameter( name (str): The name of the model component. fix_if_none (bool): Whether to fix the center Parameter if center is None. - unit (str | sc.Unit): The unit of the center Parameter. + unit (str | sc.Unit, default='meV'): The unit of the center + Parameter. + enforce_minimum_center (bool, default=False): Whether to + enforce a minimum center value to avoid zero center in + DHO. Returns: Parameter: The validated center Parameter. Raises: TypeError: If center is not None, a number, or a Parameter. + ValueError: If center is a number but not finite, or if + center is a Parameter but has a non-finite value. """ if center is not None and not isinstance(center, (Numeric, Parameter)): raise TypeError('center must be None, a number, or a Parameter.') @@ -124,11 +131,12 @@ def _create_width_parameter( width of a function. Args: - width (Numeric or Parameter): The width value or Parameter. + width (Numeric | Parameter): The width value or Parameter. name (str): The name of the model component. - param_name (str): The name of the width parameter. - unit (str or sc.Unit): The unit of the width Parameter. - minimum_width (float): The minimum allowed width. + param_name (str, default='width'): The name of the width parameter. + unit (str | sc.Unit, default='meV'): The unit of the width Parameter. + minimum_width (float, default=MINIMUM_WIDTH): The minimum + allowed width. Returns: Parameter: The validated width Parameter. diff --git a/src/easydynamics/sample_model/components/model_component.py b/src/easydynamics/sample_model/components/model_component.py index d4d5bc494..176d32e48 100644 --- a/src/easydynamics/sample_model/components/model_component.py +++ b/src/easydynamics/sample_model/components/model_component.py @@ -5,7 +5,6 @@ import warnings from abc import abstractmethod -from typing import List import numpy as np import scipp as sc @@ -16,42 +15,22 @@ class ModelComponent(ModelBase): - """Abstract base class for all model components. - - Args: - unit (str | sc.Unit): The unit of the model component. - Default is 'meV'. - display_name (str | None): A human-readable name for the - component. Default is None. - unique_name (str | None): A unique identifier for the - component. Default is None. - - Attributes: - unit (str): The unit of the model component. - display_name (str | None): A human-readable name for the - component. - unique_name (str | None): A unique identifier for the - component. - """ + """Abstract base class for all model components.""" def __init__( self, unit: str | sc.Unit = 'meV', display_name: str | None = None, unique_name: str | None = None, - ): + ) -> None: """Initialize the ModelComponent. Args: - unit (str | sc.Unit): The unit of the model component. - Default is 'meV'. - display_name (str | None): A human-readable name for the - component. Default is None. - unique_name (str | None): A unique identifier for the - component. Default is None. - - Raises: - TypeError: If unit is not a string or scipp Unit. + unit (str | sc.Unit, default="meV"): The unit of the model component. + display_name (str | None, default=None): A human-readable name for the + component. + unique_name (str | None, default=None): A unique identifier for the + component. """ self.validate_unit(unit) super().__init__(display_name=display_name, unique_name=unique_name) @@ -85,27 +64,27 @@ def unit(self, unit_str: str) -> None: ) ) # noqa: E501 - def fix_all_parameters(self): + def fix_all_parameters(self) -> None: """Fix all parameters in the model component.""" pars = self.get_fittable_parameters() for p in pars: p.fixed = True - def free_all_parameters(self): + def free_all_parameters(self) -> None: """Free all parameters in the model component.""" for p in self.get_fittable_parameters(): p.fixed = False def _prepare_x_for_evaluate( - self, x: Numeric | List[Numeric] | np.ndarray | sc.Variable | sc.DataArray + self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray ) -> np.ndarray: """Prepare the input x for evaluation by handling units and converting to a numpy array. Args: - x (Numeric | List[Numeric] | np.ndarray | sc.Variable | - sc.DataArray): The input data to prepare. + x (Numeric | list | np.ndarray | sc.Variable | sc.DataArray): + The input data to prepare. Returns: np.ndarray: The prepared input data as a numpy array. @@ -171,9 +150,12 @@ def _prepare_x_for_evaluate( return np.sort(x_in) @staticmethod - def validate_unit(unit) -> None: + def validate_unit(unit: str | sc.Unit | None) -> None: """Validate that the unit is either a string or a scipp Unit. + Args: + unit (str | sc.Unit | None): The unit to validate. + Raises: TypeError: If unit is not a string or scipp Unit. """ @@ -182,12 +164,19 @@ def validate_unit(unit) -> None: f'unit must be None, a string, or a scipp Unit, got {type(unit).__name__}' ) - def convert_unit(self, unit: str | sc.Unit): + def convert_unit(self, unit: str | sc.Unit) -> None: """Convert the unit of the Parameters in the component. Args: - unit (str or sc.Unit): The new unit to convert to. + unit (str | sc.Unit): The new unit to convert to. + + Raises: + TypeError: If the provided unit is not a str or sc.Unit + Exception: If the provided unit is invalid or incompatible + with the component's parameters. """ + if not isinstance(unit, (str, sc.Unit)): + raise TypeError(f'Unit must be a string or sc.Unit, got {type(unit).__name__}') old_unit = self._unit pars = self.get_all_parameters() @@ -206,15 +195,13 @@ def convert_unit(self, unit: str | sc.Unit): raise e @abstractmethod - def evaluate( - self, x: Numeric | List[Numeric] | np.ndarray | sc.Variable | sc.DataArray - ) -> np.ndarray: + def evaluate(self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray) -> np.ndarray: """Abstract method to evaluate the model component at input x. Must be implemented by subclasses. Args: - x (Numeric | list[Numeric] | np.ndarray | sc.Variable | sc.DataArray): - Input values. + x (Numeric | list | np.ndarray | sc.Variable | sc.DataArray): + The x values at which to evaluate the component. Returns: np.ndarray: Evaluated function values. diff --git a/src/easydynamics/sample_model/components/polynomial.py b/src/easydynamics/sample_model/components/polynomial.py index 4cd60902c..554fb2545 100644 --- a/src/easydynamics/sample_model/components/polynomial.py +++ b/src/easydynamics/sample_model/components/polynomial.py @@ -25,21 +25,6 @@ class Polynomial(ModelComponent): I(x) = c_0 + c_1 x + c_2 x^2 + ... + c_N x^N, $$ where $C_i$ are the coefficients. - - Args: - coefficients (list or tuple): Coefficients c0, c1, ..., cN - unit (str or sc.Unit): Unit of the Polynomial component. - display_name (str): Display name of the Polynomial component. - unique_name (str or None): Unique name of the component. - If None, a unique_name is automatically generated. - - Attributes: - coefficients (list of Parameter): Coefficients of the polynomial - as Parameters. - unit (str): Unit of the Polynomial component. - display_name (str): Display name of the Polynomial component. - unique_name (str or None): Unique name of the component. - If None, a unique_name is automatically generated. """ def __init__( @@ -48,24 +33,25 @@ def __init__( unit: str | sc.Unit = 'meV', display_name: str | None = 'Polynomial', unique_name: str | None = None, - ): + ) -> None: """Initialize the Polynomial component. Args: - coefficients (list or tuple): Coefficients c0, c1, ..., cN - unit (str or sc.Unit): Unit of the Polynomial component. - display_name (str): Display name of the Polynomial + coefficients (Sequence[Numeric | Parameter], default=(0.0,)): + Coefficients c0, c1, ..., cN + unit (str | sc.Unit, default='meV'): Unit of the Polynomial component. - unique_name (str or None): Unique name of the component. - If None, a unique_name is automatically generated. + display_name (str | None, default='Polynomial'): Display + name of the Polynomial component. + unique_name (str | None, default=None): Unique name of the + component. If None, a unique_name is automatically + generated. Raises: TypeError: If coefficients is not a sequence of numbers or - Parameters. + Parameters or if any item in coefficients is not a + number or Parameter. ValueError: If coefficients is an empty sequence. - TypeError: If any item in coefficients is not a number or - Parameter. - UnitError: If unit is not a string or sc.Unit. """ super().__init__(display_name=display_name, unit=unit, unique_name=unique_name) @@ -118,11 +104,9 @@ def coefficients(self, coeffs: Sequence[Numeric | Parameter]) -> None: Raises: TypeError: If coeffs is not a sequence of numbers or - Parameters. + Parameters or if any item in coeffs is not a number or Parameter. ValueError: If the length of coeffs does not match the existing number of coefficients. - TypeError: If any item in coeffs is not a number or - Parameter. """ if not isinstance(coeffs, (list, tuple, np.ndarray)): raise TypeError( @@ -209,18 +193,18 @@ def degree(self, value: int) -> None: ) def get_all_variables(self) -> list[DescriptorBase]: - """Get all parameters from the model component. + """Get all variables from the model component. Returns: - List[Parameter]: List of parameters in the component. + list[DescriptorBase]: List of variables in the component. """ return list(self._coefficients) - def convert_unit(self, unit: str | sc.Unit): + def convert_unit(self, unit: str | sc.Unit) -> None: """Convert the unit of the polynomial. Args: - unit (str or sc.Unit): The target unit to convert to. + unit (str | sc.Unit): The target unit to convert to. Raises: UnitError: If the provided unit is not a string or sc.Unit. diff --git a/src/easydynamics/sample_model/components/voigt.py b/src/easydynamics/sample_model/components/voigt.py index 16d5d1e08..d57c91bcc 100644 --- a/src/easydynamics/sample_model/components/voigt.py +++ b/src/easydynamics/sample_model/components/voigt.py @@ -20,29 +20,6 @@ class Voigt(CreateParametersMixin, ModelComponent): typically what you want in QENS. Use scipy.special.voigt_profile to evaluate the Voigt profile. - - Args: - area (Int | float): Total area under the curve. - center (Int | float | None): Center of the Voigt profile. - gaussian_width (Int | float): Standard deviation of the - Gaussian part. - lorentzian_width (Int | float): Half width at half max (HWHM) - of the Lorentzian part. - unit (str | sc.Unit): Unit of the parameters. Defaults to "meV" - display_name (str | None): Display name of the component. - unique_name (str | None): Unique name of the component. - If None, a unique_name is automatically generated. - - Attributes: - area (Parameter): Total area under the curve. - center (Parameter): Center of the Voigt profile. - gaussian_width (Parameter): Standard deviation of the Gaussian - part. - lorentzian_width (Parameter): Half width at half max (HWHM) of - the Lorentzian part. - unit (str | sc.Unit): Unit of the parameters. - display_name (str | None): Display name of the component. - unique_name (str | None): Unique name of the component. """ def __init__( @@ -54,27 +31,20 @@ def __init__( unit: str | sc.Unit = 'meV', display_name: str | None = 'Voigt', unique_name: str | None = None, - ): + ) -> None: """Initialize a Voigt component. Args: - area (Int | float): Total area under the curve. - center (Int | float | None): Center of the Voigt profile. - gaussian_width (Int | float): Standard deviation of the + area (Numeric | Parameter, default=1.0): Total area under the curve. + center (Numeric | Parameter | None, default=None): Center of the Voigt profile. + gaussian_width (Numeric | Parameter, default=1.0): Standard deviation of the Gaussian part. - lorentzian_width (Int | float): Half width at half max + lorentzian_width (Numeric | Parameter, default=1.0): Half width at half max (HWHM) of the Lorentzian part. - unit (str | sc.Unit): Unit of the parameters. Defaults to - "meV" - display_name (str | None): Display name of the component. - unique_name (str | None): Unique name of the component. + unit (str | sc.Unit, default='meV'): Unit of the parameters. + display_name (str | None, default='Voigt'): Display name of the component. + unique_name (str | None, default=None): Unique name of the component. If None, a unique_name is automatically generated. - - Raises: - TypeError: If any of the parameters are not of the correct - type. - ValueError: If any of the parameters are not valid (e.g. - negative widths). """ super().__init__( @@ -144,7 +114,7 @@ def center(self, value: Numeric | None) -> None: Args: value (Numeric | None): The new value for the center - parameter. If None, defaults to 0 and is fixed. + parameter. If None, defaults to 0 and is fixed. Raises: TypeError: If the value is not a number. @@ -170,11 +140,11 @@ def gaussian_width(self, value: Numeric) -> None: """Set the width parameter value. Args: - value (Numeric | None): The new value for the width - parameter. + value (Numeric): The new value for the width + parameter. Raises: - TypeError: If the value is not a number or None. + TypeError: If the value is not a number. ValueError: If the value is not positive. """ if not isinstance(value, Numeric): @@ -221,7 +191,7 @@ def evaluate(self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray) Args: - x (Numeric | list[Numeric] | np.ndarray | sc.Variable | sc.DataArray): + x (Numeric | list | np.ndarray | sc.Variable | sc.DataArray): The x values at which to evaluate the Voigt. Returns: diff --git a/src/easydynamics/sample_model/diffusion_model/brownian_translational_diffusion.py b/src/easydynamics/sample_model/diffusion_model/brownian_translational_diffusion.py index 87f37f598..881a158a1 100644 --- a/src/easydynamics/sample_model/diffusion_model/brownian_translational_diffusion.py +++ b/src/easydynamics/sample_model/diffusion_model/brownian_translational_diffusion.py @@ -28,24 +28,7 @@ class BrownianTranslationalDiffusion(DiffusionModelBase): 1/angstrom. Creates ComponentCollections with Lorentzian components for given Q-values. - Args: - display_name (str): Display name of the diffusion model. - unique_name (str | None): Unique name of the diffusion model. If - None, a unique name will be generated. - unit (str | sc.Unit): Unit of the diffusion model. Must be - convertible to meV. Defaults to "meV". - scale (Numeric): Scale factor for the diffusion model. Must be - a non-negative number. Defaults to 1.0. - diffusion_coefficient (Numeric): Diffusion coefficient D in - m^2/s. Defaults to 1.0. - - Attributes: - unit (str | sc.Unit): Unit of the diffusion model. - scale (Parameter): Scale parameter of the diffusion model. - diffusion_coefficient (Parameter): Diffusion coefficient D in - m^2/s. - - Example usage: + Example: >>>Q=np.linspace(0.5,2,7) >>>energy=np.linspace(-2, 2, 501) >>>scale=1.0 @@ -53,7 +36,7 @@ class BrownianTranslationalDiffusion(DiffusionModelBase): >>>diffusion_model=BrownianTranslationalDiffusion(display_name="DiffusionModel", >>>scale=scale, diffusion_coefficient= diffusion_coefficient) >>>component_collections=diffusion_model.create_component_collections(Q) - See also the examples. + See also the tutorials. """ def __init__( @@ -63,25 +46,24 @@ def __init__( unit: str | sc.Unit = 'meV', scale: Numeric = 1.0, diffusion_coefficient: Numeric = 1.0, - ): + ) -> None: """Initialize a new BrownianTranslationalDiffusion model. Args: - display_name (str): Display name of the diffusion model. - unique_name (str | None): Unique name of the diffusion + display_name (str | None, default='BrownianTranslationalDiffusion'): + Display name of the diffusion model. + unique_name (str | None, default=None): Unique name of the diffusion model. If None, a unique name will be generated. - unit (str | sc.Unit): Unit of the diffusion model. Must be - convertible to meV. Defaults to "meV". - scale (Numeric): Scale factor for the diffusion model. Must - be a non-negative number. Defaults to 1.0. - diffusion_coefficient (Numeric): Diffusion coefficient D in - m^2/s. Defaults to 1.0. + unit (str | sc.Unit, default='meV'): Unit of the diffusion model. Must be + convertible to meV. + scale (Numeric, default=1.0): Scale factor for the diffusion model. Must + be a non-negative number. + diffusion_coefficient (Numeric, default=1.0): Diffusion coefficient D in + m^2/s. Raises: TypeError: If scale or diffusion_coefficient is not a number. - ValueError: If scale is negative. - UnitError: If unit is not a string or scipp Unit. """ if not isinstance(scale, Numeric): raise TypeError('scale must be a number.') @@ -147,8 +129,7 @@ def calculate_width(self, Q: Q_type) -> np.ndarray: diffusion model. Args: - Q (np.ndarray | Numeric | list | ArrayLike): Scattering - vector in 1/angstrom + Q (Q_type): Scattering vector in 1/angstrom Returns: np.ndarray: HWHM values in the unit of the model @@ -168,7 +149,7 @@ def calculate_EISF(self, Q: Q_type) -> np.ndarray: the Brownian translational diffusion model. Args: - Q (np.ndarray | Numeric | list | ArrayLike): Scattering + Q (Q_type): Scattering vector in 1/angstrom Returns: @@ -183,8 +164,7 @@ def calculate_QISF(self, Q: Q_type) -> np.ndarray: (QISF). Args: - Q (np.ndarray | Numeric | list | ArrayLike): Scattering - vector in 1/angstrom + Q (Q_type): Scattering vector in 1/angstrom Returns: np.ndarray: QISF values (dimensionless). @@ -197,15 +177,15 @@ def calculate_QISF(self, Q: Q_type) -> np.ndarray: def create_component_collections( self, Q: Q_type, - component_display_name: str = 'Brownian translational diffusion', + component_display_name: str = 'Brownian diffusion', ) -> List[ComponentCollection]: r"""Create ComponentCollection components for the Brownian translational diffusion model at given Q values. Args: - Q (Number, list, or np.ndarray): Scattering vector values. - component_display_name (str): Name of the Lorentzian - component. + Q (Q_type): Scattering vector values. + component_display_name (str, default="Brownian diffusion"): + Name of the Lorentzian component. Returns: List[ComponentCollection]: List of ComponentCollections with @@ -270,13 +250,13 @@ def _write_width_dependency_expression(self, Q: float) -> str: of Q to make dependent Parameters. Args: - Q (float): Scattering vector in 1/angstrom + Q (float): Scattering vector in 1/angstrom. - Returns: - str: Dependency expression for the width. + Returns: + str: Dependency expression for the width. - Raises: - TypeError: If Q is not a float. + Raises: + TypeError: If Q is not a float. """ if not isinstance(Q, (float)): raise TypeError('Q must be a float.') diff --git a/src/easydynamics/sample_model/diffusion_model/diffusion_model_base.py b/src/easydynamics/sample_model/diffusion_model/diffusion_model_base.py index 837f5271e..2f23779bf 100644 --- a/src/easydynamics/sample_model/diffusion_model/diffusion_model_base.py +++ b/src/easydynamics/sample_model/diffusion_model/diffusion_model_base.py @@ -11,39 +11,26 @@ class DiffusionModelBase(ModelBase): - """Base class for constructing diffusion models. - - Args: - display_name (str): Display name of the diffusion model. - unique_name (str | None): Unique name of the diffusion model. - If None, a unique name will be generated. - scale (Numeric): Scale factor for the diffusion model. Must be a - non-negative number. Defaults to 1.0. - unit (str | sc.Unit): Unit of the diffusion model. Must be - convertible to meV. Defaults to "meV". - - Attributes: - unit (str | sc.Unit): Unit of the diffusion model. - scale (Parameter): Scale parameter of the diffusion model. - """ + """Base class for constructing diffusion models.""" def __init__( self, - display_name='MyDiffusionModel', + display_name: str | None = 'MyDiffusionModel', unique_name: str | None = None, scale: Numeric = 1.0, unit: str | sc.Unit = 'meV', - ): + ) -> None: """Initialize a new DiffusionModel. Args: - display_name (str): Display name of the diffusion model. - unique_name (str | None): Unique name of the diffusion + display_name (str | None, default='MyDiffusionModel'): + Display name of the diffusion model. + unique_name (str | None, default=None): Unique name of the diffusion model. If None, a unique name will be generated. - scale (Numeric): Scale factor for the diffusion model. Must - be a non-negative number. Defaults to 1.0. - unit (str | sc.Unit): Unit of the diffusion model. Must be - convertible to meV. Defaults to "meV". + scale (Numeric, default=1.0): Scale factor for the diffusion model. Must + be a non-negative number. + unit (str | sc.Unit, default='meV'): Unit of the diffusion model. Must be + convertible to meV. Raises: TypeError: If scale is not a number. @@ -72,11 +59,11 @@ def __init__( # ------------------------------------------------------------------ @property - def unit(self) -> str: + def unit(self) -> str | sc.Unit | None: """Get the unit of the energy axis of the DiffusionModel. Returns: - (str | sc.Unit | None): Unit of the DiffusionModel. + str | sc.Unit | None: Unit of the DiffusionModel. """ return str(self._unit) diff --git a/src/easydynamics/sample_model/diffusion_model/jump_translational_diffusion.py b/src/easydynamics/sample_model/diffusion_model/jump_translational_diffusion.py index 3ee24a998..bc27f3d86 100644 --- a/src/easydynamics/sample_model/diffusion_model/jump_translational_diffusion.py +++ b/src/easydynamics/sample_model/diffusion_model/jump_translational_diffusion.py @@ -30,27 +30,7 @@ class JumpTranslationalDiffusion(DiffusionModelBase): time. Q is assumed to have units of 1/angstrom. Creates ComponentCollections with Lorentzian components for given Q-values. - Args: - display_name (str): Display name of the diffusion model. - unique_name (str | None): Unique name of the diffusion model. If - None, a unique name will be generated. - unit (str | sc.Unit): Unit of the diffusion model. Must be - convertible to meV. Defaults to "meV". - scale (Numeric): Scale factor for the diffusion model. Must be - a non-negative number. Defaults to 1.0. - diffusion_coefficient (Numeric): Diffusion coefficient D in - m^2/s. Defaults to 1.0. - relaxation_time (Numeric): Relaxation time t in ps. Defaults to - 1.0. - - Attributes: - unit (str | sc.Unit): Unit of the diffusion model. - scale (Parameter): Scale parameter of the diffusion model. - diffusion_coefficient (Parameter): Diffusion coefficient D in - m^2/s. - relaxation_time (Parameter): Relaxation time t in ps. - - Example usage: + Example: >>> Q = np.linspace(0.5, 2, 7) >>> energy = np.linspace(-2, 2, 501) >>> scale = 1.0 @@ -61,7 +41,7 @@ class JumpTranslationalDiffusion(DiffusionModelBase): >>> relaxation_time=relaxation_time) >>> component_collections= >>> diffusion_model.create_component_collections(Q) - See also the examples. + See also the tutorials.. """ def __init__( @@ -72,27 +52,25 @@ def __init__( scale: Numeric = 1.0, diffusion_coefficient: Numeric = 1.0, relaxation_time: Numeric = 1.0, - ): + ) -> None: """Initialize a new JumpTranslationalDiffusion model. Args: - display_name (str): Display name of the diffusion model. - unique_name (str | None): Unique name of the diffusion + display_name (str | None, default="JumpTranslationalDiffusion"): + Display name of the diffusion model. + unique_name (str | None, default=None): Unique name of the diffusion model. If None, a unique name will be generated. - unit (str | sc.Unit): Unit of the diffusion model. Must be - convertible to meV. Defaults to "meV". - scale (Numeric): Scale factor for the diffusion model. Must - be a non-negative number. Defaults to 1.0. - diffusion_coefficient (Numeric): Diffusion coefficient D in - m^2/s. Defaults to 1.0. - relaxation_time (Numeric): Relaxation time t in ps. Defaults - to 1.0. + unit (str | sc.Unit, default="meV"): Unit of the diffusion model. Must be + convertible to meV. + scale (Numeric, default=1.0): Scale factor for the diffusion model. Must + be a non-negative number. + diffusion_coefficient (Numeric, default=1.0): Diffusion coefficient D in + m^2/s. + relaxation_time (Numeric, default=1.0): Relaxation time t in ps. Raises: TypeError: If scale, diffusion_coefficient, or relaxation_time are not numbers. - ValueError: If scale is negative. - UnitError: If unit is not a valid unit string or scipp Unit. """ super().__init__( display_name=display_name, @@ -260,8 +238,8 @@ def create_component_collections( Args: Q (Q_type): Scattering vector in 1/angstrom. Can be a single value or an array of values. - component_display_name (str): Name of the Jump Diffusion - Lorentzian component. + component_display_name (str, default="Jump translational diffusion"): + Name of the Jump Diffusion Lorentzian component. Returns: List[ComponentCollection]: List of ComponentCollections with @@ -327,6 +305,9 @@ def _write_width_dependency_expression(self, Q: float) -> str: Returns: str: Dependency expression for the width. + + Raises: + TypeError: If Q is not a float. """ if not isinstance(Q, (float)): raise TypeError('Q must be a float.') @@ -357,6 +338,9 @@ def _write_area_dependency_expression(self, QISF: float) -> str: Returns: str: Dependency expression for the area. + + Raises: + TypeError: If QISF is not a float. """ if not isinstance(QISF, (float)): diff --git a/src/easydynamics/sample_model/instrument_model.py b/src/easydynamics/sample_model/instrument_model.py index 89ae38884..2768cb621 100644 --- a/src/easydynamics/sample_model/instrument_model.py +++ b/src/easydynamics/sample_model/instrument_model.py @@ -18,40 +18,10 @@ class InstrumentModel(NewBase): """InstrumentModel represents a model of the instrument in an - experiment at various Q. It can contain a model of the resolution - function for convolutions, of the background and an offset in the - energy axis. - - Args: - display_name (str | None): The display name of the - InstrumentModel. Default is "MyInstrumentModel". - unique_name (str | None): The unique name of the - InstrumentModel. Default is None. - Q (np.ndarray | list | sc.Variable | None): The Q values where - the instrument is modelled. - resolution_model (ResolutionModel | None): The resolution model - of the instrument. If None, an empty resolution model is - created and no resolution convolution is carried out. - Default is None. - background_model (BackgroundModel | None): The background model - of the instrument. If None, an empty background model is - created, and the background evaluates to 0. Default is None. - energy_offset (float | int | None): Template energy offset of - the instrument. Will be copied to each Q value. If None, the - energy offset will be 0. Default is None. - unit (str | sc.Unit): The unit of the energy axis. Default is - 'meV'. - - Attributes: - resolution_model (ResolutionModel): The resolution model of the - instrument. - background_model (BackgroundModel): The background model of the - instrument. - Q (np.ndarray | None): The Q values where the instrument is - modelled. - energy_offset (Parameter): The template energy offset Parameter - of the instrument. Will be copied to each Q value. - unit (str | sc.Unit): The unit of the energy axis. + experiment at various Q. + + It can contain a model of the resolution function for convolutions, + of the background and an offset in the energy axis. """ def __init__( @@ -63,37 +33,31 @@ def __init__( background_model: BackgroundModel | None = None, energy_offset: Numeric | None = None, unit: str | sc.Unit = 'meV', - ): + ) -> None: """Initialize an InstrumentModel. Args: - display_name (str | None): The display name of the + display_name (str, default="MyInstrumentModel"): The display name of the InstrumentModel. Default is "MyInstrumentModel". - unique_name (str | None): The unique name of the - InstrumentModel. Default is None. - Q (np.ndarray | list | sc.Variable | None): The Q values - where the instrument is modelled. - resolution_model (ResolutionModel | None): The resolution + unique_name (str | None, default=None): The unique name of the + InstrumentModel. + Q (Q_type | None, default=None): The Q values where the instrument is modelled. + resolution_model (ResolutionModel | None, default=None): The resolution model of the instrument. If None, an empty resolution model is created and no resolution convolution is - carried out. Default is None. - background_model (BackgroundModel | None): The background + carried out. + background_model (BackgroundModel | None, default=None): The background model of the instrument. If None, an empty background model is created, and the background evaluates to 0. - Default is None. - energy_offset (float | int | None): Template energy offset + energy_offset (Numeric | None, default=None): Template energy offset of the instrument. Will be copied to each Q value. If - None, the energy offset will be 0. Default is None. - unit (str | sc.Unit): The unit of the energy axis. Default - is 'meV'. + None, the energy offset will be 0. + unit (str | sc.Unit, default="meV"): The unit of the energy axis. Raises: TypeError: If resolution_model is not a ResolutionModel or - None - TypeError: If background_model is not a BackgroundModel or - None - TypeError: If energy_offset is not a number or None - UnitError: If unit is not a valid unit string or scipp Unit. + None, or if background_model is not a BackgroundModel or None, or + if energy_offset is not a number or None. """ super().__init__( display_name=display_name, @@ -151,7 +115,7 @@ def resolution_model(self) -> ResolutionModel: return self._resolution_model @resolution_model.setter - def resolution_model(self, value: ResolutionModel): + def resolution_model(self, value: ResolutionModel) -> None: """Set the resolution model of the instrument. Args: @@ -179,7 +143,7 @@ def background_model(self) -> BackgroundModel: return self._background_model @background_model.setter - def background_model(self, value: BackgroundModel): + def background_model(self, value: BackgroundModel) -> None: """Set the background model of the instrument. Args: @@ -202,7 +166,7 @@ def Q(self) -> np.ndarray | None: """Get the Q values of the InstrumentModel. Returns: - np.ndarray or None: The Q values of the InstrumentModel, or + np.ndarray | None: The Q values of the InstrumentModel, or None if not set """ return self._Q @@ -213,20 +177,17 @@ def Q(self, value: Q_type | None) -> None: Args: value (Q_type | None): The new Q values for the - InstrumentModel. - - Raises: - TypeError: If value is not a valid Q_type or None. + InstrumentModel. """ self._Q = _validate_and_convert_Q(value) self._on_Q_change() @property - def unit(self) -> sc.Unit: + def unit(self) -> str | sc.Unit: """Get the unit of the InstrumentModel. Returns: - (str | sc.Unit): The unit of the InstrumentModel. + str | sc.Unit: The unit of the InstrumentModel. """ return self._unit @@ -263,11 +224,11 @@ def energy_offset(self) -> Parameter: return self._energy_offset @energy_offset.setter - def energy_offset(self, value: Numeric): + def energy_offset(self, value: Numeric) -> None: """Set the offset parameter of the instrument model. Args: - value (float | int): The new value for the energy offset + value (Numeric): The new value for the energy offset parameter. Will be copied to all Q values. Raises: @@ -290,7 +251,6 @@ def convert_unit(self, unit_str: str | sc.Unit) -> None: unit_str (str | sc.Unit): The unit to convert to. Raises: - TypeError: If unit_str is not a string or scipp Unit. ValueError: If unit_str is not a valid unit string or scipp Unit. """ @@ -310,7 +270,7 @@ def get_all_variables(self, Q_index: int | None = None) -> list[Parameter]: """Get all variables in the InstrumentModel. Args: - Q_index (int | None): The index of the Q value to get + Q_index (int | None, default=None): The index of the Q value to get variables for. If None, get variables for all Q values. Returns: diff --git a/src/easydynamics/sample_model/model_base.py b/src/easydynamics/sample_model/model_base.py index c78cef357..88f95c766 100644 --- a/src/easydynamics/sample_model/model_base.py +++ b/src/easydynamics/sample_model/model_base.py @@ -19,27 +19,8 @@ class ModelBase(EasyScienceModelBase): """Base class for Sample Models. - Contains common functionality for models with components and - Q dependence. - - Args: - display_name (str): Display name of the model. - unique_name (str | None): Unique name of the model. If None, a - unique name will be generated. - unit (str | sc.Unit | None): Unit of the model. Defaults to - "meV". - components (ModelComponent | ComponentCollection | None): - Template components of the model. If None, no components - are added. These components are copied into - ComponentCollections for each Q value. - Q (ArrayLike | sc.Variable | None): Q values for the model. If - None, Q is not set. - - Attributes: - unit (str | sc.Unit): Unit of the model. - components (list[ModelComponent]): List of ModelComponents in - the model. - Q (ArrayLike | sc.Variable | None): Q values of the model. + Contains common functionality for models with components and Q + dependence. """ def __init__( @@ -49,21 +30,23 @@ def __init__( unit: str | sc.Unit | None = 'meV', components: ModelComponent | ComponentCollection | None = None, Q: Q_type | None = None, - ): + ) -> None: """Initialize the ModelBase. Args: - display_name (str): Display name of the model. - unique_name (str | None): Unique name of the model. If None, + display_name (str, default="MyModelBase"): Display name of the model. + unique_name (str | None, default=None): Unique name of the model. If None, a unique name will be generated. - unit (str | sc.Unit | None): Unit of the model. Defaults to - "meV". - components (ModelComponent | ComponentCollection | None): + unit (str | sc.Unit | None, default="meV"): Unit of the model. + components (ModelComponent | ComponentCollection | None, default=None): Template components of the model. If None, no components are added. These components are copied into ComponentCollections for each Q value. - Q (ArrayLike | sc.Variable | None): Q values for the model. + Q (Q_type | None, default=None): Q values for the model. If None, Q is not set. + + Raises: + TypeError: If components is not a ModelComponent or ComponentCollection. """ super().__init__( display_name=display_name, @@ -92,11 +75,11 @@ def evaluate( """Evaluate the sample model at all Q for the given x values. Args: - x (Numeric | list | np.ndarray | sc.Variable | sc.DataArray): - Energy axis values to evaluate the model at. If a scipp - Variable or DataArray is provided, the unit of the model - will be converted to match the unit of x for evaluation, and - the result will be returned in the same unit as x. + x (Numeric | list | np.ndarray | sc.Variable | sc.DataArray): + Energy axis values to evaluate the model at. If a scipp + Variable or DataArray is provided, the unit of the model + will be converted to match the unit of x for evaluation, and + the result will be returned in the same unit as x. Returns: list[np.ndarray]: A list of numpy arrays containing the @@ -152,11 +135,11 @@ def clear_components(self) -> None: # ------------------------------------------------------------------ @property - def unit(self) -> str | sc.Unit: + def unit(self) -> str | sc.Unit | None: """Get the unit of the ComponentCollection. Returns: - str | sc.Unit |None: The unit of the ComponentCollection. + str | sc.Unit | None: The unit of the ComponentCollection. """ return self._unit @@ -188,12 +171,14 @@ def convert_unit(self, unit: str | sc.Unit) -> None: Raises: TypeError: If the provided unit is not a string or sc.Unit. - UnitError: If the provided unit is not compatible with the + Exception: If the provided unit is not compatible with the current unit. """ old_unit = self._unit + if not isinstance(unit, (str, sc.Unit)): + raise TypeError(f'Unit must be a string or sc.Unit, got {type(unit).__name__}') try: for component in self.components: component.convert_unit(unit) @@ -223,7 +208,7 @@ def components(self, value: ModelComponent | ComponentCollection | None) -> None Args: value (ModelComponent | ComponentCollection | None): The new - components to set. If None, all components will be cleared. + components to set. If None, all components will be cleared. Raises: TypeError: If value is not a ModelComponent, @@ -252,7 +237,7 @@ def Q(self, value: Q_type | None) -> None: Args: value (Q_type | None): The new Q values to set. If None, Q - will be unset. + will be unset. """ old_Q = self._Q new_Q = _validate_and_convert_Q(value) @@ -287,13 +272,18 @@ def get_all_variables(self, Q_index: int | None = None) -> list[Parameter]: templates. Args: - Q_index (int | None): If None, get variables for all + Q_index (int | None, default=None): If None, get variables for all ComponentCollections. If int, get variables for the ComponentCollection at this index. Defaults to None. Returns: list[Parameter]: A list of all Parameters and Descriptors from the ComponentCollections in the ModelBase. + + Raises: + TypeError: If Q_index is not an int or None. + IndexError: If Q_index is out of bounds for the number of + ComponentCollections. """ if Q_index is None: diff --git a/src/easydynamics/sample_model/resolution_model.py b/src/easydynamics/sample_model/resolution_model.py index c1913a3b3..639bf8f81 100644 --- a/src/easydynamics/sample_model/resolution_model.py +++ b/src/easydynamics/sample_model/resolution_model.py @@ -15,26 +15,6 @@ class ResolutionModel(ModelBase): """ResolutionModel represents a model of the instrment resolution in an experiment at various Q. - - Args: - display_name (str): Display name of the model. - unique_name (str | None): Unique name of the model. If None, a - unique name will be generated. - unit (str | sc.Unit | None): Unit of the model. Defaults to - "meV". - components (ModelComponent | ComponentCollection | None): - Template components of the model. If None, no components - are added. These components are copied into - ComponentCollections for each Q value. - Q (Q_type | None): Q values for the model. If None, Q is not - set. - - Attributes: - unit (str | sc.Unit): Unit of the model. - components (list[ModelComponent]): List of ModelComponents in - the model. - Q (np.ndarray | Numeric | list | ArrayLike | sc.Variable | None): - Q values of the model. """ def __init__( @@ -42,28 +22,22 @@ def __init__( display_name: str = 'MyResolutionModel', unique_name: str | None = None, unit: str | sc.Unit = 'meV', - components: ComponentCollection | ModelComponent | None = None, + components: ModelComponent | ComponentCollection | None = None, Q: Q_type | None = None, - ): + ) -> None: """Initialize a ResolutionModel. Args: - display_name (str): Display name of the model. - unique_name (str | None): Unique name of the model. If None, + display_name (str, default="MyResolutionModel"): Display name of the model. + unique_name (str | None, default=None): Unique name of the model. If None, a unique name will be generated. - unit (str | sc.Unit | None): Unit of the model. Defaults to - "meV". - components (ModelComponent | ComponentCollection | None): + unit (str | sc.Unit, default="meV"): Unit of the model. + components (ModelComponent | ComponentCollection | None, default=None): Template components of the model. If None, no components are added. These components are copied into ComponentCollections for each Q value. - Q (Q_type | None): Q values for the model. If None, Q is not + Q (Q_type | None, default=None): Q values for the model. If None, Q is not set. - - Raises: - TypeError: If components is not a ModelComponent or - ComponentCollection. - ValueError: If Q is not a valid Q_type. """ super().__init__( diff --git a/src/easydynamics/sample_model/sample_model.py b/src/easydynamics/sample_model/sample_model.py index a624ac586..5cf9bf77c 100644 --- a/src/easydynamics/sample_model/sample_model.py +++ b/src/easydynamics/sample_model/sample_model.py @@ -22,41 +22,6 @@ class SampleModel(ModelBase): components from the base model and diffusion models. Applies detailed balancing based on temperature if provided. - - Args: - display_name (str): Display name of the model. - unique_name (str | None): Unique name of the model. If None, a - unique name will be generated. - unit (str | sc.Unit | None): Unit of the model. If None, - defaults to "meV". - components (ModelComponent | ComponentCollection | None): - Template components of the model. If None, no components are - added. These components are copied into ComponentCollections - for each Q value. - Q (Number, list, np.ndarray, sc.array | None): - Q values for the model. If None, Q is not set. - diffusion_models (DiffusionModelBase | list[DiffusionModelBase] | None): - Diffusion models to include in the SampleModel. - If None, no diffusion models are added. - temperature (float | None): Temperature for detailed balancing. - If None, no detailed balancing is applied. - temperature_unit (str | sc.Unit): Unit of the temperature. - Defaults to "K". - divide_by_temperature (bool): Whether to divide the detailed - balance factor by temperature. Defaults to True. - - Attributes: - unit (str | sc.Unit): Unit of the model. - components (list[ModelComponent]): List of ModelComponents in - the model. - Q (np.ndarray | Numeric | list | ArrayLike | sc.Variable | None): - Q values of the model. - diffusion_models (list[DiffusionModelBase]): List of diffusion - models in the SampleModel. - temperature (Parameter | None): Temperature Parameter for - detailed balancing, or None if not set. - divide_by_temperature (bool): Whether to divide the detailed - balance factor by temperature. """ def __init__( @@ -64,43 +29,43 @@ def __init__( display_name: str = 'MySampleModel', unique_name: str | None = None, unit: str | sc.Unit = 'meV', - components: ComponentCollection | ModelComponent | None = None, + components: ModelComponent | ComponentCollection | None = None, Q: Q_type | None = None, diffusion_models: DiffusionModelBase | list[DiffusionModelBase] | None = None, temperature: float | None = None, temperature_unit: str | sc.Unit = 'K', divide_by_temperature: bool = True, - ): + ) -> None: """Initialize the SampleModel. Args: - display_name (str): Display name of the model. - unique_name (str | None): Unique name of the model. If None, + display_name (str, default="MySampleModel"): Display name of the model. + unique_name (str | None, default=None): Unique name of the model. If None, a unique name will be generated. - unit (str | sc.Unit | None): Unit of the model. If None, + unit (str | sc.Unit, default="meV"): Unit of the model. If None, defaults to "meV". - components (ModelComponent | ComponentCollection | None): + components (ModelComponent | ComponentCollection | None, default=None): Template components of the model. If None, no components are added. These components are copied into ComponentCollections for each Q value. - Q (Number, list, np.ndarray, sc.array | None): + Q (Q_type | None, default=None): Q values for the model. If None, Q is not set. - diffusion_models (DiffusionModelBase | list[DiffusionModelBase] | None): + diffusion_models (DiffusionModelBase | list[DiffusionModelBase] | None, default=None): Diffusion models to include in the SampleModel. If None, no diffusion models are added. - temperature (float | None): Temperature for detailed + temperature (float | None, default=None): Temperature for detailed balancing. If None, no detailed balancing is applied. - temperature_unit (str | sc.Unit): Unit of the temperature. + temperature_unit (str | sc.Unit, default="K"): Unit of the temperature. Defaults to "K". - divide_by_temperature (bool): Whether to divide the detailed + divide_by_temperature (bool, default=True): Whether to divide the detailed balance factor by temperature. Defaults to True. Raises: TypeError: If diffusion_models is not a DiffusionModelBase, - a list of DiffusionModelBase, or None. - TypeError: If temperature is not a number or None. + a list of DiffusionModelBase, or None, or if temperature + is not a number or None, or if divide_by_temperature is + not a bool. ValueError: If temperature is negative. - TypeError: If divide_by_temperature is not a bool. """ if diffusion_models is None: self._diffusion_models = [] @@ -215,11 +180,14 @@ def diffusion_models( """Set the diffusion models of the SampleModel. Args: - value (DiffusionModelBase | list[DiffusionModelBase] | - None): + value (DiffusionModelBase | list[DiffusionModelBase] | None): The diffusion model(s) to set. Can be a single DiffusionModelBase, a list of DiffusionModelBase, or None to clear all diffusion models. + + Raises: + TypeError: If value is not a DiffusionModelBase, a list of + DiffusionModelBase, or None. """ if value is None: @@ -255,6 +223,10 @@ def temperature(self, value: Numeric | None) -> None: Args: value (Numeric | None): The temperature value to set. Can be a number or None to unset the temperature. + + Raises: + TypeError: If value is not a number or None. + ValueError: If value is negative. """ if value is None: self._temperature = None @@ -312,6 +284,7 @@ def convert_temperature_unit(self, unit: str | sc.Unit) -> None: Raises: ValueError: If temperature is not set or conversion fails. + Exception: If the provided unit is invalid or cannot be converted. """ if self._temperature is None: @@ -349,6 +322,9 @@ def divide_by_temperature(self, value: bool) -> None: Args: value (bool): True to divide the detailed balance factor by temperature, False otherwise. + + Raises: + TypeError: If value is not a bool. """ if not isinstance(value, bool): raise TypeError('divide_by_temperature must be True or False') @@ -394,9 +370,14 @@ def get_all_variables(self, Q_index: int | None = None) -> list[Parameter]: self._components as these are just templates. Args: - Q_index (int | None): If specified, only get variables from + Q_index (int | None, default=None): If specified, only get variables from the ComponentCollection at the given Q index. If None, get variables from all ComponentCollections. + + Returns: + list[Parameter]: List of all Parameters and Descriptors, + including temperature if set and all variables from + diffusion models. """ all_vars = super().get_all_variables(Q_index=Q_index) diff --git a/src/easydynamics/utils/detailed_balance.py b/src/easydynamics/utils/detailed_balance.py index ab323530d..086917fa3 100644 --- a/src/easydynamics/utils/detailed_balance.py +++ b/src/easydynamics/utils/detailed_balance.py @@ -41,26 +41,40 @@ def _detailed_balance_factor( $k_B*T$ to have value 1 at $E=0$. Args: - energy (number | list | np.ndarray | scipp.Variable): The energy + energy (int | float | list | np.ndarray | sc.Variable): The energy transfer. If number, assumed to be in meV unless energy_unit is set. - temperature (number | scipp.Variable | Parameter): The + temperature (int | float | sc.Variable | Parameter): The temperature. If number, assumed to be in K unless temperature_unit is set. - energy_unit (str | sc.Unit |None): Unit for energy if energy is + energy_unit (str | sc.Unit, default='meV'): Unit for energy if energy is given as a number or list. Default is 'meV' - temperature_unit (str | sc.Unit |None): Unit for temperature if + temperature_unit (str | sc.Unit, default='K'): Unit for temperature if temperature is given as a number. Default is 'K' - divide_by_temperature (bool | None): If True, divide the result + divide_by_temperature (bool, default=True): If True, divide the result by $k_B*T$ to make it dimensionless and have value 1 at E=0. Default is True. Returns: - DBF (np.ndarray): Detailed balance factor evaluated at the + np.ndarray: Detailed balance factor evaluated at the given energy and temperature. - Examples - -------- + Raises: + TypeError: If energy or temperature is not a number, list, + numpy array, or scipp Variable, or if energy_unit or + temperature_unit is not a string or scipp Unit, + or if divide_by_temperature is not a boolean. + ValueError: If temperature is negative, or if energy is a numpy + array with more than 1 dimension, or if temperature is a + scipp Variable that does not have a single dimension named + 'temperature', or if energy is a scipp Variable that does + not have a single dimension named 'energy'. + UnitError: If the provided energy_unit or temperature_unit is + invalid, or if the units of energy or temperature cannot be + converted to the expected units. + ZeroDivisionError: If divide_by_temperature is True and temperature is zero. + + Examples: >>> detailed_balance_factor(1.0, 300) # 1 meV at 300 K >>> detailed_balance_factor( ... energy=[1.0, 2.0], @@ -182,12 +196,12 @@ def _convert_to_scipp_variable( units. Args: - value (int | float | list | np.ndarray | Parameter | - sc.Variable): The value to convert. Can be a number, list, - numpy array, Parameter, or scipp Variable. If a number or - list, the unit must be specified in the unit argument. + value (int | float | list | np.ndarray | Parameter | sc.Variable): + The value to convert. Can be a number, list, numpy array, + Parameter, or scipp Variable. If a number or list, the unit + must be specified in the unit argument. name (str): The name of the variable, used for error messages. - unit (str | None): The unit to use if value is a number or list. + unit (str | None, default=None): The unit to use if value is a number or list. Must be specified if value is a number or list. Ignored if value is a Parameter or sc.Variable, which have their own units. @@ -195,8 +209,6 @@ def _convert_to_scipp_variable( Raises: TypeError: If value is not one of the accepted types, or if unit is not a string when needed. - ValueError: If value is a number or list and unit is not - provided. UnitError: If the provided unit is invalid. Returns: diff --git a/src/easydynamics/utils/utils.py b/src/easydynamics/utils/utils.py index 03965c6e9..ec5cb0563 100644 --- a/src/easydynamics/utils/utils.py +++ b/src/easydynamics/utils/utils.py @@ -11,20 +11,28 @@ energy_type = np.ndarray | Numeric | list | ArrayLike | sc.Variable -def _validate_and_convert_Q(Q: Q_type | None) -> np.ndarray | None: +def _validate_and_convert_Q( + Q: np.ndarray | Numeric | list | ArrayLike | sc.Variable | None, +) -> np.ndarray | None: """Validate and convert Q to a numpy array. Args: - Q (Number, list, np.ndarray or sc.Variable): Scattering vector - values in 1/angstrom. + Q (np.ndarray | Numeric | list | ArrayLike | sc.Variable | None): + Scattering vector values in 1/angstrom. Returns: - np.ndarray: Q as a np.ndarray. + np.ndarray | None: Q as a np.ndarray or None if Q is None. + + Raises: + TypeError: If Q is not a number, list, numpy array, or scipp + Variable. + ValueError: If Q is a numpy array with more than 1 dimension, or + if Q is a scipp Variable that does not have a single dimension named 'Q'. """ if Q is None: return None - if not isinstance(Q, (Numeric, list, np.ndarray, sc.Variable)): - raise TypeError('Q must be a number, list, numpy array, or scipp array.') + if not isinstance(Q, (np.ndarray, Numeric, list, sc.Variable)): + raise TypeError('Q must be a number, list, numpy array, or scipp Variable.') if isinstance(Q, Numeric): Q = np.array([Q]) diff --git a/tests/unit/easydynamics/sample_model/components/test_exponential.py b/tests/unit/easydynamics/sample_model/components/test_exponential.py index 7e9499c36..d61be76e5 100644 --- a/tests/unit/easydynamics/sample_model/components/test_exponential.py +++ b/tests/unit/easydynamics/sample_model/components/test_exponential.py @@ -125,6 +125,17 @@ def test_property_setters( with pytest.raises(TypeError, match=invalid_message): setattr(exponential, prop, invalid_value) + def test_center_is_fixed_if_set_to_None(self, exponential: Exponential): + # WHEN + assert exponential.center.fixed is False + + # THEN + exponential.center = None + + # EXPECT + assert exponential.center.value == 0.0 + assert exponential.center.fixed is True + def test_evaluate(self, exponential: Exponential): # WHEN x = np.array([0.0, 0.5, 1.0]) @@ -168,6 +179,11 @@ def test_convert_unit(self, exponential: Exponential): assert exponential.rate.value == 1.2 / 1e3 assert str(exponential.rate.unit) == '1/ueV' + def test_convert_unit_incorrect_unit_raises(self, exponential: Exponential): + # WHEN THEN EXPECT + with pytest.raises(TypeError, match='unit must be a string or sc.Unit'): + exponential.convert_unit(123) + def test_convert_unit_rollback(self, exponential: Exponential): # WHEN with pytest.raises( diff --git a/tests/unit/easydynamics/sample_model/components/test_model_component.py b/tests/unit/easydynamics/sample_model/components/test_model_component.py index 3bd917422..93e42bc09 100644 --- a/tests/unit/easydynamics/sample_model/components/test_model_component.py +++ b/tests/unit/easydynamics/sample_model/components/test_model_component.py @@ -46,6 +46,11 @@ def test_convert_unit(self, dummy: DummyComponent): assert dummy.center.value == 2 * 1e3 assert dummy.width.value == 3 * 1e3 + def test_convert_unit_incorrect_unit_raises(self, dummy: DummyComponent): + # WHEN THEN EXPECT + with pytest.raises(TypeError, match='Unit must be a string or sc.Unit'): + dummy.convert_unit(123) + def test_free_and_fix_all_parameters(self, dummy): # WHEN THEN EXPECT dummy.free_all_parameters() diff --git a/tests/unit/easydynamics/sample_model/test_component_collection.py b/tests/unit/easydynamics/sample_model/test_component_collection.py index 595cdb8ed..fcc65b6f1 100644 --- a/tests/unit/easydynamics/sample_model/test_component_collection.py +++ b/tests/unit/easydynamics/sample_model/test_component_collection.py @@ -177,6 +177,10 @@ def test_is_empty_setter(self, component_collection): with pytest.raises(AttributeError, match='is_empty is a read-only property.'): component_collection.is_empty = True + def test_component_setter_empty_list(self, component_collection): + component_collection.components = [] + assert component_collection.components == [] + def test_list_component_names(self, component_collection): # WHEN THEN components = component_collection.list_component_names() @@ -198,6 +202,11 @@ def test_convert_unit(self, component_collection): for component in component_collection.components: assert component.unit == 'eV' + def test_convert_unit_incorrect_unit_raises(self, component_collection): + # WHEN THEN EXPECT + with pytest.raises(TypeError, match='Unit must be a string or sc.Unit'): + component_collection.convert_unit(123) + def test_convert_unit_failure_rolls_back(self, component_collection): # WHEN THEN # Introduce a faulty component that will fail conversion diff --git a/tests/unit/easydynamics/sample_model/test_model_base.py b/tests/unit/easydynamics/sample_model/test_model_base.py index 8b39c6f38..4368823aa 100644 --- a/tests/unit/easydynamics/sample_model/test_model_base.py +++ b/tests/unit/easydynamics/sample_model/test_model_base.py @@ -266,6 +266,11 @@ def test_convert_unit_invalid_raises(self, model_base): with pytest.raises(Exception): model_base.convert_unit('invalid_unit') + def test_convert_unit_incorrect_unit_raises(self, model_base): + # WHEN THEN EXPECT + with pytest.raises(TypeError, match='Unit must be a string or sc.Unit'): + model_base.convert_unit(123) + def test_components_setter(self, model_base): # WHEN new_component = Lorentzian(unique_name='NewLorentzian') diff --git a/tests/unit/easydynamics/sample_model/test_sample_model.py b/tests/unit/easydynamics/sample_model/test_sample_model.py index 22a5f2fb5..e1f7223e9 100644 --- a/tests/unit/easydynamics/sample_model/test_sample_model.py +++ b/tests/unit/easydynamics/sample_model/test_sample_model.py @@ -352,7 +352,7 @@ def test_generate_component_collections(self, sample_model): assert collection.components[0].area.value == 1.0 assert collection.components[1].display_name == 'TestLorentzian1' assert collection.components[1].area.value == 2.0 - assert collection.components[2].display_name == 'Brownian translational diffusion' + assert collection.components[2].display_name == 'Brownian diffusion' assert isinstance(collection.components[2], Lorentzian) def test_get_all_variables(self, sample_model): diff --git a/tools/add_spdx.py b/tools/add_license_headers.py similarity index 100% rename from tools/add_spdx.py rename to tools/add_license_headers.py diff --git a/tools/check_spdx.py b/tools/check_license_headers.py similarity index 100% rename from tools/check_spdx.py rename to tools/check_license_headers.py diff --git a/tools/remove_spdx.py b/tools/remove_license_headers.py similarity index 100% rename from tools/remove_spdx.py rename to tools/remove_license_headers.py