diff --git a/.badgery.yaml b/.badgery.yaml index f22752fa..99bcdc1c 100644 --- a/.badgery.yaml +++ b/.badgery.yaml @@ -5,19 +5,18 @@ cards: - group: Tests type: gh_action title: Code/package tests (GitHub) - file: test.yaml + file: test.yml enabled: true - - group: Tests type: gh_action title: Tutorial tests (GitHub) - file: tutorial-tests.yaml + file: tutorial-tests.yml enabled: true - group: Tests type: gh_action title: Package tests (PyPI) - file: pypi-test.yaml + file: pypi-test.yml enabled: true - group: Code Quality @@ -60,15 +59,14 @@ cards: title: Docstring coverage (interrogate) report: reports/{branch}/coverage-docstring.txt enabled: true - - group: Build & Release type: gh_action title: Publishing (PyPI) - workflow: pypi-publish.yaml + workflow: pypi-publish.yml enabled: true - group: Build & Release type: gh_action title: Docs build/deployment - workflow: docs.yaml + workflow: docs.yml enabled: true diff --git a/.copier-answers.yml b/.copier-answers.yml new file mode 100644 index 00000000..778744fa --- /dev/null +++ b/.copier-answers.yml @@ -0,0 +1,27 @@ +# WARNING: Do not edit this file manually. +# Any changes will be overwritten by Copier. +_commit: v0.10.1-25-ga5301e9 +_src_path: gh:easyscience/templates +app_docs_url: https://easyscience.github.io/diffraction-app +app_doi: 10.5281/zenodo.18163581 +app_package_name: easydiffraction_app +app_python: '3.13' +app_repo_name: diffraction-app +home_page_url: https://easyscience.github.io/diffraction +home_repo_name: diffraction +lib_docs_url: https://easyscience.github.io/diffraction-lib +lib_doi: 10.5281/zenodo.18163581 +lib_package_name: easydiffraction +lib_python_max: '3.13' +lib_python_min: '3.11' +lib_repo_name: diffraction-lib +project_contact_email: support@easydiffraction.org +project_copyright_years: 2021-2026 +project_extended_description: A software for calculating neutron powder diffraction + patterns based on a structural model and refining its parameters against experimental + data +project_name: EasyDiffraction +project_short_description: Diffraction data analysis +project_shortcut: ED +project_type: both +template_type: lib diff --git a/.github/actions/download-artifact/action.yml b/.github/actions/download-artifact/action.yml new file mode 100644 index 00000000..e4fd62f5 --- /dev/null +++ b/.github/actions/download-artifact/action.yml @@ -0,0 +1,50 @@ +name: 'Download artifact' +description: 'Generic wrapper for actions/download-artifact' +inputs: + name: + description: 'Name of the artifact to download' + required: true + + path: + description: 'Destination path' + required: false + default: '.' + + pattern: + description: 'Glob pattern to match artifact names (optional)' + required: false + default: '' + + merge-multiple: + description: 'Merge multiple artifacts into the same directory' + required: false + default: 'false' + + github-token: + description: 'GitHub token for cross-repo download (optional)' + required: false + default: '' + + repository: + description: 'owner/repo for cross-repo download (optional)' + required: false + default: '' + + run-id: + description: 'Workflow run ID for cross-run download (optional)' + required: false + default: '' + +runs: + using: 'composite' + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.name }} + path: ${{ inputs.path }} + pattern: ${{ inputs.pattern }} + merge-multiple: ${{ inputs.merge-multiple }} + github-token: ${{ inputs.github-token }} + repository: ${{ inputs.repository }} + run-id: ${{ inputs.run-id }} diff --git a/.github/actions/github-script/action.yml b/.github/actions/github-script/action.yml new file mode 100644 index 00000000..ab32da56 --- /dev/null +++ b/.github/actions/github-script/action.yml @@ -0,0 +1,19 @@ +name: 'GitHub Script' +description: 'Wrapper for actions/github-script' +inputs: + script: + description: 'JavaScript to run' + required: true + + github-token: + description: 'GitHub token (defaults to github.token)' + required: false + default: ${{ github.token }} + +runs: + using: 'composite' + steps: + - uses: actions/github-script@v8 + with: + script: ${{ inputs.script }} + github-token: ${{ inputs.github-token }} diff --git a/.github/actions/setup-easyscience-bot/action.yml b/.github/actions/setup-easyscience-bot/action.yml new file mode 100644 index 00000000..4b28eaf8 --- /dev/null +++ b/.github/actions/setup-easyscience-bot/action.yml @@ -0,0 +1,40 @@ +name: 'Setup EasyScience bot for pushing' +description: 'Create GitHub App token and configure git identity + origin remote' +inputs: + app-id: + description: 'GitHub App ID' + required: true + private-key: + description: 'GitHub App private key (PEM)' + required: true + repositories: + description: 'Additional repositories to grant access to (newline-separated)' + required: false + default: '' + +outputs: + token: + description: 'Installation access token' + value: ${{ steps.app-token.outputs.token }} + +runs: + using: 'composite' + steps: + - name: Create GitHub App installation token + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ inputs.app-id }} + private-key: ${{ inputs.private-key }} + repositories: ${{ inputs.repositories }} + + - name: Configure git for pushing + shell: bash + run: | + git config user.name "easyscience[bot]" + git config user.email "${{ inputs.app-id }}+easyscience[bot]@users.noreply.github.com" + + - name: Configure origin remote to use the bot token + shell: bash + run: | + git remote set-url origin https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository }}.git diff --git a/.github/actions/setup-pixi/action.yml b/.github/actions/setup-pixi/action.yml new file mode 100644 index 00000000..167ee623 --- /dev/null +++ b/.github/actions/setup-pixi/action.yml @@ -0,0 +1,44 @@ +name: 'Setup Pixi Environment' +description: 'Sets up pixi with common configuration' +inputs: + environments: + description: 'Pixi environments to setup' + required: false + default: 'default' + activate-environment: + description: 'Environment to activate' + required: false + default: 'default' + run-install: + description: 'Whether to run pixi install' + required: false + default: 'true' + locked: + description: 'Whether to run pixi install --locked' + required: false + default: 'false' + frozen: + description: 'Whether to run pixi install --frozen' + required: false + default: 'true' + cache: + description: 'Whether to use cache' + required: false + default: 'false' + post-cleanup: + description: 'Whether to run post cleanup' + required: false + default: 'false' + +runs: + using: 'composite' + steps: + - uses: prefix-dev/setup-pixi@v0.9.4 + with: + environments: ${{ inputs.environments }} + activate-environment: ${{ inputs.activate-environment }} + run-install: ${{ inputs.run-install }} + locked: ${{ inputs.locked }} + frozen: ${{ inputs.frozen }} + cache: ${{ inputs.cache }} + post-cleanup: ${{ inputs.post-cleanup }} diff --git a/.github/actions/upload-artifact/action.yml b/.github/actions/upload-artifact/action.yml new file mode 100644 index 00000000..825ac396 --- /dev/null +++ b/.github/actions/upload-artifact/action.yml @@ -0,0 +1,49 @@ +name: 'Upload artifact' +description: 'Generic wrapper for actions/upload-artifact' +inputs: + name: + description: 'Artifact name' + required: true + + path: + description: 'File(s)/dir(s)/glob(s) to upload (newline-separated)' + required: true + + include-hidden-files: + description: 'Include hidden files' + required: false + default: 'true' + + if-no-files-found: + description: 'warn | error | ignore' + required: false + default: 'error' + + compression-level: + description: '0-9 (0 = no compression)' + required: false + default: '0' + + retention-days: + description: 'Retention in days (optional)' + required: false + default: '' + + overwrite: + description: 'Overwrite an existing artifact with the same name' + required: false + default: 'false' + +runs: + using: 'composite' + steps: + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.name }} + path: ${{ inputs.path }} + include-hidden-files: ${{ inputs.include-hidden-files }} + if-no-files-found: ${{ inputs.if-no-files-found }} + compression-level: ${{ inputs.compression-level }} + retention-days: ${{ inputs.retention-days }} + overwrite: ${{ inputs.overwrite }} diff --git a/.github/actions/upload-codecov/action.yml b/.github/actions/upload-codecov/action.yml new file mode 100644 index 00000000..37d6298a --- /dev/null +++ b/.github/actions/upload-codecov/action.yml @@ -0,0 +1,42 @@ +name: 'Upload coverage to Codecov' +description: 'Generic wrapper for codecov/codecov-action@v5' + +inputs: + name: + description: 'Codecov upload name' + required: true + + flags: + description: 'Codecov flags' + required: false + default: '' + + files: + description: 'Coverage report files' + required: true + + fail_ci_if_error: + description: 'Fail CI if upload fails' + required: false + default: 'true' + + verbose: + description: 'Enable verbose output' + required: false + default: 'true' + + token: + description: 'Codecov token' + required: true + +runs: + using: composite + steps: + - uses: codecov/codecov-action@v5 + with: + name: ${{ inputs.name }} + flags: ${{ inputs.flags }} + files: ${{ inputs.files }} + fail_ci_if_error: ${{ inputs.fail_ci_if_error }} + verbose: ${{ inputs.verbose }} + token: ${{ inputs.token }} diff --git a/.github/configs/pages-deployment.json b/.github/configs/pages-deployment.json new file mode 100644 index 00000000..c0d3fbee --- /dev/null +++ b/.github/configs/pages-deployment.json @@ -0,0 +1,6 @@ +{ + "source": { + "branch": "gh-pages", + "path": "/" + } +} diff --git a/.github/configs/rulesets-develop.json b/.github/configs/rulesets-develop.json new file mode 100644 index 00000000..04489e52 --- /dev/null +++ b/.github/configs/rulesets-develop.json @@ -0,0 +1,37 @@ +{ + "name": "develop branch", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["refs/heads/develop"], + "exclude": [] + } + }, + "bypass_actors": [ + { + "actor_id": 2476259, + "actor_type": "Integration", + "bypass_mode": "always" + } + ], + "rules": [ + { + "type": "non_fast_forward" + }, + { + "type": "deletion" + }, + { + "type": "pull_request", + "parameters": { + "allowed_merge_methods": ["squash"], + "dismiss_stale_reviews_on_push": false, + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_approving_review_count": 0, + "required_review_thread_resolution": false + } + } + ] +} diff --git a/.github/configs/rulesets-gh-pages.json b/.github/configs/rulesets-gh-pages.json new file mode 100644 index 00000000..ebf38928 --- /dev/null +++ b/.github/configs/rulesets-gh-pages.json @@ -0,0 +1,19 @@ +{ + "name": "gh-pages branch", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["refs/heads/gh-pages"], + "exclude": [] + } + }, + "rules": [ + { + "type": "non_fast_forward" + }, + { + "type": "deletion" + } + ] +} diff --git a/.github/configs/rulesets-master.json b/.github/configs/rulesets-master.json new file mode 100644 index 00000000..f658a5c6 --- /dev/null +++ b/.github/configs/rulesets-master.json @@ -0,0 +1,30 @@ +{ + "name": "master branch", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["~DEFAULT_BRANCH"], + "exclude": [] + } + }, + "rules": [ + { + "type": "non_fast_forward" + }, + { + "type": "deletion" + }, + { + "type": "pull_request", + "parameters": { + "allowed_merge_methods": ["merge"], + "dismiss_stale_reviews_on_push": false, + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_approving_review_count": 0, + "required_review_thread_resolution": false + } + } + ] +} diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 58e7d29d..d29a15d1 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -2,8 +2,8 @@ ## Project Context -- Python library for crystallographic diffraction analysis, such as refinement - of the structural model against experimental data. +- Python library for crystallographic diffraction analysis, such as + refinement of the structural model against experimental data. - Support for - sample_form: powder and single crystal - beam_mode: time-of-flight and constant wavelength @@ -13,124 +13,140 @@ - `cryspy` for Bragg diffraction - `crysfml` for Bragg diffraction - `pdffit2` for Total scattering -- Follow CIF naming conventions where possible. In some places, we deviate for - better API design, but we try to keep the spirit of the CIF names. +- Follow CIF naming conventions where possible. In some places, we + deviate for better API design, but we try to keep the spirit of the + CIF names. - Reusing the concept of datablocks and categories from CIF. We have `DatablockItem` (structure or experiment) and `DatablockCollection` - (collection of structures or experiments), as well as `CategoryItem` (single - categories in CIF) and `CategoryCollection` (loop categories in CIF). + (collection of structures or experiments), as well as `CategoryItem` + (single categories in CIF) and `CategoryCollection` (loop categories + in CIF). - Metadata via frozen dataclasses: `TypeInfo`, `Compatibility`, `CalculatorSupport`. -- The API is designed for scientists who use EasyDiffraction as a final product - in a user-friendly, intuitive way. The target users are not software - developers and may have little or no Python experience. The design is not - oriented toward developers building their own tooling on top of the library, - although experienced developers will find their own way. Prioritize - discoverability, clear error messages, and safe defaults so that - non-programmers are not stuck by standard API conventions. -- This project must be developed to be as error-free as possible, with the same - rigour applied to critical software (e.g. nuclear-plant control systems). - Every code path must be tested, edge cases must be handled explicitly, and - silent failures are not acceptable. +- The API is designed for scientists who use EasyDiffraction as a final + product in a user-friendly, intuitive way. The target users are not + software developers and may have little or no Python experience. The + design is not oriented toward developers building their own tooling on + top of the library, although experienced developers will find their + own way. Prioritize discoverability, clear error messages, and safe + defaults so that non-programmers are not stuck by standard API + conventions. +- This project must be developed to be as error-free as possible, with + the same rigour applied to critical software (e.g. nuclear-plant + control systems). Every code path must be tested, edge cases must be + handled explicitly, and silent failures are not acceptable. ## Code Style -- Use snake_case for functions and variables, PascalCase for classes, and - UPPER_SNAKE_CASE for constants. +- Use snake_case for functions and variables, PascalCase for classes, + and UPPER_SNAKE_CASE for constants. - Use `from __future__ import annotations` in every module. - Type-annotate all public function signatures. -- Docstrings on all public classes and methods (Google style). +- Docstrings on all public classes and methods (numpy style). - Prefer flat over nested, explicit over clever. -- Write straightforward code; do not add defensive checks for unlikely edge - cases. +- Write straightforward code; do not add defensive checks for unlikely + edge cases. - Prefer composition over deep inheritance. -- One class per file when the class is substantial; group small related classes. -- Avoid `**kwargs`; use explicit keyword arguments for clarity, autocomplete, - and typo detection. -- Do not use string-based dispatch (e.g. `getattr(self, f'_{name}')`) to route - to attributes or methods. Instead, write explicit named methods (e.g. - `_set_sample_form`, `_set_beam_mode`). This keeps the code greppable, - autocomplete-friendly, and type-safe. -- Public parameters and descriptors are either **editable** (property with both - getter and setter) or **read-only** (property with getter only). If internal - code needs to mutate a read-only property, add a private `_set_` method - instead of exposing a public setter. +- One class per file when the class is substantial; group small related + classes. +- Avoid `**kwargs`; use explicit keyword arguments for clarity, + autocomplete, and typo detection. +- Do not use string-based dispatch (e.g. `getattr(self, f'_{name}')`) to + route to attributes or methods. Instead, write explicit named methods + (e.g. `_set_sample_form`, `_set_beam_mode`). This keeps the code + greppable, autocomplete-friendly, and type-safe. +- Public parameters and descriptors are either **editable** (property + with both getter and setter) or **read-only** (property with getter + only). If internal code needs to mutate a read-only property, add a + private `_set_` method instead of exposing a public setter. ## Architecture -- Eager imports at the top of the module by default. Use lazy imports (inside a - method body) only when necessary to break circular dependencies or to keep - `core/` free of heavy utility imports on rarely-called paths (e.g. `help()`). +- Eager imports at the top of the module by default. Use lazy imports + (inside a method body) only when necessary to break circular + dependencies or to keep `core/` free of heavy utility imports on + rarely-called paths (e.g. `help()`). - No `pkgutil` / `importlib` auto-discovery patterns. - No background/daemon threads. - No monkey-patching or runtime class mutation. - Do not use `__all__` in modules; instead, rely on explicit imports in `__init__.py` to control the public API. -- Do not use redundant `import X as X` aliases in `__init__.py`. Use plain - `from module import X`. -- Concrete classes use `@Factory.register` decorators. To trigger registration, - each package's `__init__.py` must explicitly import every concrete class (e.g. - `from .chebyshev import ChebyshevPolynomialBackground`). When adding a new - concrete class, always add its import to the corresponding `__init__.py`. -- Switchable categories (those whose implementation can be swapped at runtime - via a factory) follow a fixed naming convention on the owner (experiment, - structure, or analysis): `` (read-only property), `_type` - (getter + setter), `show_supported__types()`, - `show_current__type()`. The owner class owns the type setter and the - show methods; the show methods delegate to `Factory.show_supported(...)` - passing context. Every factory-created category must have this full API, even - if only one implementation exists today. -- Categories are flat siblings within their owner (datablock or analysis). A - category must never be a child of another category of a different type. - Categories can reference each other via IDs, but not via parent-child nesting. -- Every finite, closed set of values (factory tags, experiment axes, category - descriptors with enumerated choices) must use a `(str, Enum)` class. Internal - code compares against enum members, never raw strings. +- Do not use redundant `import X as X` aliases in `__init__.py`. Use + plain `from module import X`. +- Concrete classes use `@Factory.register` decorators. To trigger + registration, each package's `__init__.py` must explicitly import + every concrete class (e.g. + `from .chebyshev import ChebyshevPolynomialBackground`). When adding a + new concrete class, always add its import to the corresponding + `__init__.py`. +- Switchable categories (those whose implementation can be swapped at + runtime via a factory) follow a fixed naming convention on the owner + (experiment, structure, or analysis): `` (read-only + property), `_type` (getter + setter), + `show_supported__types()`, `show_current__type()`. + The owner class owns the type setter and the show methods; the show + methods delegate to `Factory.show_supported(...)` passing context. + Every factory-created category must have this full API, even if only + one implementation exists today. +- Categories are flat siblings within their owner (datablock or + analysis). A category must never be a child of another category of a + different type. Categories can reference each other via IDs, but not + via parent-child nesting. +- Every finite, closed set of values (factory tags, experiment axes, + category descriptors with enumerated choices) must use a `(str, Enum)` + class. Internal code compares against enum members, never raw strings. - Keep `core/` free of domain logic — only base classes and utilities. -- Don't introduce a new abstraction until there is a concrete second use case. +- Don't introduce a new abstraction until there is a concrete second use + case. - Don't add dependencies without asking. ## Changes -- Before implementing any structural or design change (new categories, new - factories, switchable-category wiring, new datablocks, CIF serialisation - changes), read `docs/architecture/architecture.md` to understand the current - design choices and conventions. Follow the documented patterns (factory - registration, switchable-category naming, metadata classification, etc.) to - stay consistent with the rest of the codebase. For localised bug fixes or test - updates, the rules in this file are sufficient. -- The project is in beta; do not keep legacy code or add deprecation warnings. - Instead, update tests and tutorials to follow the current API. +- Before implementing any structural or design change (new categories, + new factories, switchable-category wiring, new datablocks, CIF + serialisation changes), read `docs/architecture/architecture.md` to + understand the current design choices and conventions. Follow the + documented patterns (factory registration, switchable-category naming, + metadata classification, etc.) to stay consistent with the rest of the + codebase. For localised bug fixes or test updates, the rules in this + file are sufficient. +- The project is in beta; do not keep legacy code or add deprecation + warnings. Instead, update tests and tutorials to follow the current + API. - Minimal diffs: don't rewrite working code just to reformat it. -- Never remove or replace existing functionality as part of a new change without - explicit confirmation. If a refactor would drop features, options, or - configurations, highlight every removal and wait for approval. -- Fix only what's asked; flag adjacent issues as comments, don't fix them - silently. -- Don't add new features or refactor existing code unless explicitly asked. +- Never remove or replace existing functionality as part of a new change + without explicit confirmation. If a refactor would drop features, + options, or configurations, highlight every removal and wait for + approval. +- Fix only what's asked; flag adjacent issues as comments, don't fix + them silently. +- Don't add new features or refactor existing code unless explicitly + asked. - Do not remove TODOs or comments unless the change fully resolves them. - When renaming, grep the entire project (code, tests, tutorials, docs). -- Every change should be atomic and self-contained, small enough to be described - by a single commit message. Make one change, suggest the commit message, then - stop and wait for confirmation before starting the next change. +- Every change should be atomic and self-contained, small enough to be + described by a single commit message. Make one change, suggest the + commit message, then stop and wait for confirmation before starting + the next change. - When in doubt, ask for clarification before making changes. ## Workflow -- All open issues, design questions, and planned improvements are tracked in - `docs/architecture/issues_open.md`, ordered by priority. When an issue is - fully implemented, move it from that file to +- All open issues, design questions, and planned improvements are + tracked in `docs/architecture/issues_open.md`, ordered by priority. + When an issue is fully implemented, move it from that file to `docs/architecture/issues_closed.md`. When the resolution affects the architecture, update the relevant sections of `docs/architecture/architecture.md`. -- After changes, run linting and formatting fixes with `pixi run fix`. Do not - check what was auto-fixed, just accept the fixes and move on. +- After changes, run linting and formatting fixes with `pixi run fix`. + Do not check what was auto-fixed, just accept the fixes and move on. - After changes, run unit tests with `pixi run unit-tests`. -- After changes, run integration tests with `pixi run integration-tests`. +- After changes, run integration tests with + `pixi run integration-tests`. - After changes, run tutorial tests with `pixi run script-tests`. -- Suggest a concise commit message (as a code block) after each change (less - than 72 characters, imperative mood, without prefixing with the type of - change). E.g.: +- Suggest a concise commit message (as a code block) after each change + (less than 72 characters, imperative mood, without prefixing with the + type of change). E.g.: - Add ChebyshevPolynomialBackground class - Implement background_type setter on Experiment - Standardize switchable-category naming convention diff --git a/.github/scripts/backmerge-conflict-issue.js b/.github/scripts/backmerge-conflict-issue.js new file mode 100644 index 00000000..f6bd98b5 --- /dev/null +++ b/.github/scripts/backmerge-conflict-issue.js @@ -0,0 +1,69 @@ +module.exports = async ({ github, context, core }) => { + // Repo context + const owner = context.repo.owner + const repo = context.repo.repo + + // Link to the exact workflow run that detected the conflict + const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}` + + // We use a *stable title* so we can find/reuse the same "conflict tracker" issue + // instead of creating a new issue on every failed run. + const title = 'Backmerge conflict: master → develop' + + // Comment/issue body includes the run URL so maintainers can jump straight to logs. + const body = [ + 'Automatic backmerge failed due to merge conflicts.', + '', + `Workflow run: ${runUrl}`, + '', + 'Manual resolution required.', + ].join('\n') + + // Label applied to the tracker issue (assumed to already exist in the repo). + const label = '[bot] backmerge' + + // Search issues by title across *open and closed* issues. + // Why: if the conflict was resolved previously and the issue was closed, + // we prefer to reopen it and append a new comment instead of creating duplicates. + const q = `repo:${owner}/${repo} is:issue in:title "${title}"` + const search = await github.rest.search.issuesAndPullRequests({ + q, + per_page: 10, + }) + + // Pick the first exact-title match (search can return partial matches). + const existing = search.data.items.find((i) => i.title === title) + + if (existing) { + // If a tracker issue exists, reuse it: + // - reopen it if needed + // - add a comment with the new run URL + if (existing.state === 'closed') { + await github.rest.issues.update({ + owner, + repo, + issue_number: existing.number, + state: 'open', + }) + } + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: existing.number, + body, + }) + + core.notice(`Conflict issue updated: #${existing.number}`) + return + } + + // No tracker issue exists yet -> create the first one. + await github.rest.issues.create({ + owner, + repo, + title, + body, + labels: [label], + }) +} diff --git a/.github/workflows/backmerge.yaml b/.github/workflows/backmerge.yaml deleted file mode 100644 index f69b5379..00000000 --- a/.github/workflows/backmerge.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# This workflow automatically merges `master` into `develop` whenever a new version tag is pushed (v*). -# -# Key points: -# - Directly merges master into develop without creating a PR. -# - Skips CI on the merge commit using [skip ci] in the commit message. -# - The code being merged has already been tested as part of the release process. -# - This ensures develop stays up-to-date with release changes (version bumps, etc.). -# -# Required repo config: -# https://github.com/organizations/easyscience/settings/secrets/actions -# https://github.com/organizations/easyscience/settings/variables/actions -# - Actions secret: EASYSCIENCE_APP_KEY (GitHub App private key PEM) -# - Actions variable: EASYSCIENCE_APP_ID (GitHub App ID) -# The GitHub App must be added to the develop branch ruleset bypass list. - -name: Backmerge (master -> develop) - -on: - push: - tags: ['v*'] - -permissions: - contents: write - -jobs: - backmerge: - runs-on: ubuntu-latest - - steps: - - name: Create GitHub App installation token - id: app-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ vars.EASYSCIENCE_APP_ID }} - private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} - - - name: Checkout repository - uses: actions/checkout@v5 - with: - fetch-depth: 0 - token: ${{ steps.app-token.outputs.token }} - - - name: Configure git for pushing - run: | - git config user.name "easyscience[bot]" - git config user.email "${{ vars.EASYSCIENCE_APP_ID }}+easyscience[bot]@users.noreply.github.com" - - - name: Merge master into develop - run: | - set -euo pipefail - - TAG='${{ github.ref_name }}' - - # Ensure local develop branch exists and is up-to-date with origin - git fetch origin develop:develop - # Switch to develop branch - git checkout develop - - # Merge master into develop (no fast-forward to preserve history) - # Use [skip ci] to avoid triggering CI - the code was already tested on master - git merge origin/master --no-ff -m "Backmerge: ${TAG} from master into develop [skip ci]" - - # Push the merge commit to develop - git push origin develop - - echo "✅ Successfully merged master (${TAG}) into develop" diff --git a/.github/workflows/backmerge.yml b/.github/workflows/backmerge.yml new file mode 100644 index 00000000..47b3384a --- /dev/null +++ b/.github/workflows/backmerge.yml @@ -0,0 +1,109 @@ +# This workflow automatically merges `master` into `develop` whenever a +# new version release with a tag is published. It can also be triggered +# manually via workflow_dispatch for cases where an automatic backmerge +# is needed outside of the standard release process. +# If a merge conflict occurs, the workflow creates an issue to notify +# maintainers for manual resolution. + +name: Backmerge (master → develop) + +on: + release: + types: [published, prereleased] + workflow_dispatch: + +permissions: + contents: write + issues: write + +concurrency: + group: backmerge-master-into-develop + cancel-in-progress: false + +jobs: + backmerge: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout repository (for local actions) + uses: actions/checkout@v5 + + - name: Setup easyscience[bot] + id: bot + uses: ./.github/actions/setup-easyscience-bot + with: + app-id: ${{ vars.EASYSCIENCE_APP_ID }} + private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} + repositories: ${{ github.event.repository.name }} + + - name: Checkout repository (with bot token) + uses: actions/checkout@v5 + with: + fetch-depth: 0 + token: ${{ steps.bot.outputs.token }} + + - name: Configure git identity + run: | + git config user.name "easyscience[bot]" + git config user.email "${{ vars.EASYSCIENCE_APP_ID }}+easyscience[bot]@users.noreply.github.com" + + - name: Set merge message + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + MESSAGE="Backmerge: master into develop (manual) [skip ci]" + else + TAG="${{ github.event.release.tag_name }}" + MESSAGE="Backmerge: master (${TAG}) into develop [skip ci]" + fi + + echo "MESSAGE=$MESSAGE" >> "$GITHUB_ENV" + echo "message=$MESSAGE" >> "$GITHUB_OUTPUT" + echo "📝 Merge message: $MESSAGE" | tee -a "$GITHUB_STEP_SUMMARY" + + - name: Prepare branches + run: | + git fetch origin master develop + git checkout -B develop origin/develop + + - name: Check if develop is already up-to-date + id: up_to_date + run: | + if git merge-base --is-ancestor origin/master develop; then + echo "value=true" >> "$GITHUB_OUTPUT" + echo "ℹ️ Develop is already up-to-date with master" | tee -a "$GITHUB_STEP_SUMMARY" + else + echo "value=false" >> "$GITHUB_OUTPUT" + fi + + - name: Try merge master into develop + id: merge + if: steps.up_to_date.outputs.value == 'false' + continue-on-error: true + run: | + if ! git merge origin/master --no-ff -m "${MESSAGE}"; then + echo "conflict=true" >> "$GITHUB_OUTPUT" + echo "❌ Backmerge conflict detected." | tee -a "$GITHUB_STEP_SUMMARY" + git status --porcelain || true + exit 0 + fi + + echo "conflict=false" >> "$GITHUB_OUTPUT" + echo "✅ Merge commit created." | tee -a "$GITHUB_STEP_SUMMARY" + + - name: Push to develop (if merge succeeded) + if: + steps.up_to_date.outputs.value == 'false' && steps.merge.outputs.conflict == + 'false' + run: | + git push origin develop + echo "🚀 Backmerge successful: master → develop" | tee -a "$GITHUB_STEP_SUMMARY" + + - name: Create issue (if merge failed with conflicts) + if: steps.merge.outputs.conflict == 'true' + uses: ./.github/actions/github-script + with: + github-token: ${{ steps.bot.outputs.token }} + script: | + const run = require('./.github/scripts/backmerge-conflict-issue.js') + await run({ github, context, core }) diff --git a/.github/workflows/cleanup.yaml b/.github/workflows/cleanup.yml similarity index 88% rename from .github/workflows/cleanup.yaml rename to .github/workflows/cleanup.yml index eef184db..7305679b 100644 --- a/.github/workflows/cleanup.yaml +++ b/.github/workflows/cleanup.yml @@ -22,8 +22,8 @@ on: default: 6 delete_workflow_pattern: description: - 'The name or filename of the workflow. if not set then it will target - all workflows.' + 'The name or filename of the workflow. if not set then it will target all + workflows.' required: false delete_workflow_by_state_pattern: description: @@ -40,8 +40,8 @@ on: - disabled_manually delete_run_by_conclusion_pattern: description: - 'Remove workflow by conclusion: action_required, cancelled, failure, - skipped, success' + 'Remove workflow by conclusion: action_required, cancelled, failure, skipped, + success' required: true default: 'All' type: choice @@ -53,8 +53,13 @@ on: - skipped - success dry_run: - description: 'Only log actions, do not perform any delete operations.' + description: 'Only log actions, do not perform any delete operations (dry run).' required: false + default: 'false' + type: choice + options: + - 'false' + - 'true' jobs: del-runs: @@ -71,8 +76,7 @@ jobs: repository: ${{ github.repository }} retain_days: ${{ github.event.inputs.days }} keep_minimum_runs: ${{ github.event.inputs.minimum_runs }} - delete_workflow_pattern: - ${{ github.event.inputs.delete_workflow_pattern }} + delete_workflow_pattern: ${{ github.event.inputs.delete_workflow_pattern }} delete_workflow_by_state_pattern: ${{ github.event.inputs.delete_workflow_by_state_pattern }} delete_run_by_conclusion_pattern: diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yml similarity index 50% rename from .github/workflows/coverage.yaml rename to .github/workflows/coverage.yml index 96ae3386..cd9ff1e0 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yml @@ -3,6 +3,8 @@ name: Coverage checks on: # Trigger the workflow on push push: + # Do not run on version tags (those are handled by other workflows) + tags-ignore: ['v*'] # Trigger the workflow on pull request pull_request: # Allows you to run this workflow manually from the Actions tab @@ -16,8 +18,7 @@ permissions: # Allow only one concurrent workflow, skipping runs queued between the run # in-progress and latest queued. And cancel in-progress runs. concurrency: - group: - ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true # Set the environment variables to be used in all jobs defined in this workflow @@ -34,18 +35,7 @@ jobs: uses: actions/checkout@v5 - name: Set up pixi - uses: prefix-dev/setup-pixi@v0.9.3 - with: - environments: default - activate-environment: default - run-install: true - frozen: true - cache: false - post-cleanup: false - - - name: Install and setup development dependencies - shell: bash - run: pixi run dev + uses: ./.github/actions/setup-pixi - name: Run docstring coverage run: pixi run docstring-coverage @@ -59,34 +49,21 @@ jobs: uses: actions/checkout@v5 - name: Set up pixi - uses: prefix-dev/setup-pixi@v0.9.3 - with: - environments: default - activate-environment: default - run-install: true - frozen: true - cache: false - post-cleanup: false - - - name: Install and setup development dependencies - shell: bash - run: pixi run dev + uses: ./.github/actions/setup-pixi - name: Run unit tests with coverage run: pixi run unit-tests-coverage --cov-report=xml:coverage-unit.xml - name: Upload unit tests coverage to Codecov if: ${{ !cancelled() }} - uses: codecov/codecov-action@v5 + uses: ./.github/actions/upload-codecov with: name: unit-tests-job flags: unittests files: ./coverage-unit.xml - fail_ci_if_error: true - verbose: true token: ${{ secrets.CODECOV_TOKEN }} - # Job 3: Run integration tests with coverage and upload to Codecov + # Job 2: Run integration tests with coverage and upload to Codecov integration-tests-coverage: runs-on: ubuntu-latest @@ -95,53 +72,23 @@ jobs: uses: actions/checkout@v5 - name: Set up pixi - uses: prefix-dev/setup-pixi@v0.9.3 - with: - environments: default - activate-environment: default - run-install: true - frozen: true - cache: false - post-cleanup: false - - - name: Install and setup development dependencies - shell: bash - run: pixi run dev + uses: ./.github/actions/setup-pixi - name: Run integration tests with coverage run: - pixi run integration-tests-coverage - --cov-report=xml:coverage-integration.xml + pixi run integration-tests-coverage --cov-report=xml:coverage-integration.xml - name: Upload integration tests coverage to Codecov if: ${{ !cancelled() }} - uses: codecov/codecov-action@v5 + uses: ./.github/actions/upload-codecov with: name: integration-tests-job flags: integration files: ./coverage-integration.xml - fail_ci_if_error: true - verbose: true token: ${{ secrets.CODECOV_TOKEN }} - # Job 4: Trigger dashboard build - dashboard-build-trigger: + # Job 4: Build and publish dashboard (reusable workflow) + run-reusable-workflows: needs: [docstring-coverage, unit-tests-coverage, integration-tests-coverage] # depend on the previous jobs - - runs-on: ubuntu-latest - - steps: - - name: Check-out repository - uses: actions/checkout@v5 - - - name: Trigger dashboard build - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - await github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: "dashboard.yaml", - ref: "${{ env.CI_BRANCH }}" - }); + uses: ./.github/workflows/dashboard.yml + secrets: inherit diff --git a/.github/workflows/dashboard.yaml b/.github/workflows/dashboard.yml similarity index 66% rename from .github/workflows/dashboard.yaml rename to .github/workflows/dashboard.yml index 71d64401..5159f71b 100644 --- a/.github/workflows/dashboard.yaml +++ b/.github/workflows/dashboard.yml @@ -4,12 +4,8 @@ on: workflow_dispatch: workflow_call: -# Allow only one concurrent workflow, skipping runs queued between the run -# in-progress and latest queued. And cancel in-progress runs. -concurrency: - group: - ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true +permissions: + contents: read # Set the environment variables to be used in all jobs defined in this workflow env: @@ -24,78 +20,83 @@ jobs: runs-on: ubuntu-latest steps: - # Create GitHub App token for pushing to external dashboard repo. - # The 'repositories' parameter is required to grant access to repos - # other than the one where the workflow is running. - - name: Create GitHub App installation token - id: app-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ vars.EASYSCIENCE_APP_ID }} - private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} - repositories: | - ${{ github.event.repository.name }} - dashboard - - name: Checkout repository uses: actions/checkout@v5 with: fetch-depth: 0 - name: Set up pixi - uses: prefix-dev/setup-pixi@v0.9.3 - with: - environments: default - activate-environment: default - run-install: true - frozen: true - cache: false - post-cleanup: false - - - name: Install and setup development dependencies + uses: ./.github/actions/setup-pixi + + - name: Install badgery shell: bash - run: | - pixi run dev - pixi add --pypi --git https://github.com/enhantica/badgery badgery + run: pixi add --pypi --git https://github.com/enhantica/badgery badgery - name: Run docstring coverage and code complexity/maintainability checks run: | - for BRANCH in ${{ env.DEFAULT_BRANCH }} ${{ env.DEVELOP_BRANCH }} ${{ env.CI_BRANCH }}; do - echo "=== Processing branch $BRANCH ===" + for BRANCH in $DEFAULT_BRANCH $DEVELOP_BRANCH $CI_BRANCH; do + echo + echo "🔹🔸🔹🔸🔹 Processing branch $BRANCH 🔹🔸🔹🔸🔹" if [ -d "../$BRANCH" ]; then echo "Branch $BRANCH already processed, skipping" continue fi + git worktree add ../$BRANCH origin/$BRANCH mkdir -p reports/$BRANCH + echo "Docstring coverage for branch $BRANCH" pixi run interrogate -c pyproject.toml --fail-under=0 ../$BRANCH/src > reports/$BRANCH/coverage-docstring.txt + echo "Cyclomatic complexity for branch $BRANCH" pixi run radon cc -s -j ../$BRANCH/src > reports/$BRANCH/cyclomatic-complexity.json + echo "Maintainability index for branch $BRANCH" pixi run radon mi -j ../$BRANCH/src > reports/$BRANCH/maintainability-index.json + echo "Raw metrics for branch $BRANCH" pixi run radon raw -s -j ../$BRANCH/src > reports/$BRANCH/raw-metrics.json done - name: Generate dashboard HTML run: > - pixi run python -m badgery --config .badgery.yaml --repo ${{ - github.repository }} --branch ${{ env.CI_BRANCH }} --output index.html + pixi run python -m badgery --config .badgery.yaml --repo ${{ github.repository + }} --branch ${{ env.CI_BRANCH }} --output index.html - name: Prepare publish directory run: | mkdir -p _dashboard_publish/${{ env.REPO_NAME }}/${{ env.CI_BRANCH }} cp index.html _dashboard_publish/${{ env.REPO_NAME }}/${{ env.CI_BRANCH }} + # Create GitHub App token for pushing to external dashboard repo. + # The 'repositories' parameter is required to grant access to repos + # other than the one where the workflow is running. + - name: Setup easyscience[bot] + id: bot + uses: ./.github/actions/setup-easyscience-bot + with: + app-id: ${{ vars.EASYSCIENCE_APP_ID }} + private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} + repositories: | + ${{ github.event.repository.name }} + dashboard + + # Publish to external dashboard repository with retry logic. + # Retry is needed to handle transient GitHub API/authentication issues + # that occasionally cause 403 errors when multiple workflows push concurrently. + # Uses personal_token (not github_token) as GITHUB_TOKEN cannot access external repos. - name: Publish to main branch of ${{ github.repository }} - uses: peaceiris/actions-gh-pages@v3 + uses: Wandalen/wretry.action@v3.8.0 with: - external_repository: ${{ env.REPO_OWNER }}/dashboard - publish_branch: ${{ env.DEFAULT_BRANCH }} - personal_token: ${{ steps.app-token.outputs.token }} - publish_dir: ./_dashboard_publish - keep_files: true + attempt_limit: 3 + attempt_delay: 15000 # 15 seconds between retries + action: peaceiris/actions-gh-pages@v4 + with: | + publish_dir: ./_dashboard_publish + keep_files: true + external_repository: ${{ env.REPO_OWNER }}/dashboard + publish_branch: master + personal_token: ${{ steps.bot.outputs.token }} - name: Add dashboard link to summary run: | diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yml similarity index 66% rename from .github/workflows/docs.yaml rename to .github/workflows/docs.yml index f9bddf67..66b06e1d 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yml @@ -14,16 +14,16 @@ name: Docs build and deployment on: - # Trigger the workflow on pull request - pull_request: - # Selected branches - branches: [master, main, develop] # Trigger the workflow on push push: # Selected branches - branches: [master, main, develop] + branches: [develop] # master and main are already verified in PR # Runs on creating a new tag starting with 'v', e.g. 'v1.0.3' tags: ['v*'] + # Trigger the workflow on pull request + pull_request: + # Selected branches + branches: [master, main, develop] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -53,11 +53,7 @@ jobs: # Single job that builds and deploys documentation. # Uses macOS runner for consistent Plotly chart rendering. build-deploy-docs: - strategy: - matrix: - os: [macos-14] - - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest # macos-latest permissions: contents: write # Required for pushing to the gh-pages branch @@ -83,22 +79,11 @@ jobs: fi echo "RELEASE_VERSION=${RELEASE_VERSION}" >> "$GITHUB_ENV" echo "DOCS_VERSION=${DOCS_VERSION}" >> "$GITHUB_ENV" - echo "DEPLOYMENT_URL=https://easyscience.github.io/${{ github.event.repository.name }}/${DOCS_VERSION}" >> "$GITHUB_ENV" - - # Create GitHub App token for pushing to gh-pages as easyscience[bot]. - - name: Create GitHub App installation token - id: app-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ vars.EASYSCIENCE_APP_ID }} - private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} # Check out the repository source code. # Note: The gh-pages branch is fetched separately later for mike deployment. - - name: Check-out repository + - name: Checkout repository uses: actions/checkout@v5 - with: - token: ${{ steps.app-token.outputs.token }} # Activate dark mode to create documentation with Plotly charts in dark mode # Need a better solution to automatically switch the chart colour theme based on the mkdocs material switcher @@ -114,83 +99,53 @@ jobs: # Set up the pixi package manager and install dependencies from pixi.toml. # Uses frozen lockfile to ensure reproducible builds. - name: Set up pixi - uses: prefix-dev/setup-pixi@v0.9.3 - with: - environments: default - activate-environment: default - run-install: true - frozen: true - cache: false - post-cleanup: false - - # Install additional development dependencies (e.g., pre-commit hooks, dev tools). - - name: Install and setup development dependencies - shell: bash - run: pixi run dev - - # Clone shared documentation assets and branding resources from external repositories. - # These contain common MkDocs configuration, templates, stylesheets, and images. - - name: Clone easyscience/assets-docs and easyscience/assets-branding - run: | - cd .. - git clone https://github.com/easyscience/assets-docs.git - git clone https://github.com/easyscience/assets-branding.git - - # Copy assets from the cloned repositories into the docs/ directory. - # This includes stylesheets, images, templates, and other shared resources. - - name: Add files from cloned repositories - run: pixi run docs-assets - - # Convert Python scripts in the tutorials/ directory to Jupyter notebooks. - # This step also strips any existing output from the notebooks and prepares - # them for documentation. - - name: Convert tutorial scripts to notebooks - run: pixi run notebook-prepare - - # Pre-import the main package to trigger Matplotlib font cache building. - # This avoids "Matplotlib is building the font cache" messages during notebook execution. + uses: ./.github/actions/setup-pixi + # Pre-import the main package to exclude info messages from the docs + # E.g., Matplotlib may print messages to stdout/stderr when first + # imported. This step allows to avoid "Matplotlib is building the font + # cache" messages during notebook execution. - name: Pre-build site step run: pixi run python -c "import easydiffraction" + # Prepare the Jupyter notebooks for documentation (strip output, etc.). + - name: Prepare notebooks + run: pixi run notebook-prepare + # Execute all Jupyter notebooks to generate output cells (plots, tables, etc.). # Uses multiple cores for parallel execution to speed up the process. - name: Run notebooks - # if: false # Temporarily disabled to speed up the docs build + # if: false # Temporarily disabled to speed up the docs build run: pixi run notebook-exec - # Move the executed notebooks to docs/tutorials/ directory - # so they can be included in the documentation site. - - name: Move notebooks to docs/tutorials - run: pixi run docs-notebooks - - # Create the mkdocs.yml configuration file - # The file is created by merging two files: - # - assets-docs/mkdocs.yml - the common configuration (theme, plugins, etc.) - # - docs/mkdocs.yml - the project-specific configuration (project name, TOC, etc.) - - name: Create mkdocs.yml file - run: pixi run docs-config - # Build the static files for the documentation site for local inspection # Input: docs/ directory containing the Markdown files # Output: site/ directory containing the generated HTML files - name: Build site for local check - run: pixi run docs-local + run: pixi run docs-build-local # Upload the static files from the site/ directory to be used for # local check - name: Upload built site as artifact - uses: actions/upload-artifact@v4 + uses: ./.github/actions/upload-artifact + with: + name: site-local_easydiffraction-lib-${{ env.RELEASE_VERSION }} + path: docs/site/ + + # Create GitHub App token for pushing to gh-pages as easyscience[bot]. + - name: Setup easyscience[bot] + id: bot + uses: ./.github/actions/setup-easyscience-bot with: - name: site-local_edl-${{ env.RELEASE_VERSION }} - path: site/ - if-no-files-found: 'error' - compression-level: 0 + app-id: ${{ vars.EASYSCIENCE_APP_ID }} + private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} - # Configure git user for mike to commit and push to gh-pages branch. + # Configure git identity and remote URL so mike pushes as easyscience[bot]. - name: Configure git for pushing run: | + set -euo pipefail git config user.name "easyscience[bot]" git config user.email "${{ vars.EASYSCIENCE_APP_ID }}+easyscience[bot]@users.noreply.github.com" + git remote set-url origin "https://x-access-token:${{ steps.bot.outputs.token }}@github.com/${{ github.repository }}.git" # Fetch the gh-pages branch to ensure mike has the latest remote state. # This is required because the checkout step only fetches the source branch, @@ -206,10 +161,24 @@ jobs: # Also sets 'latest' as the default version for the version selector. - name: Rebuild and deploy docs with mike run: | + # Exit on error (-e), undefined vars (-u), and pipeline failures (pipefail) + set -euo pipefail + + REPO_NAME="${{ github.event.repository.name }}" + BASE_URL="https://easyscience.github.io/${REPO_NAME}" + + # Deploy the release version and update the "latest" alias if [[ "${IS_RELEASE_TAG}" == "true" ]]; then - pixi run docs-deploy "${RELEASE_VERSION#v}" latest + pixi run docs-deploy-pre "${RELEASE_VERSION#v}" latest + pixi run docs-set-default-pre latest + DEPLOYMENT_URL="${BASE_URL}/latest" + + # Deploy/update the "dev" alias (or whatever your convention is) else - pixi run docs-deploy dev + pixi run docs-deploy-pre dev + DEPLOYMENT_URL="${BASE_URL}/dev" + fi - pixi run docs-set-default latest - echo "🔗 deployment url [${{ env.DEPLOYMENT_URL }}](${{ env.DEPLOYMENT_URL }})" >> $GITHUB_STEP_SUMMARY + + # Add links to the action summary page for easy access + echo "🔗 deployment url [${DEPLOYMENT_URL}](${DEPLOYMENT_URL})" >> "${GITHUB_STEP_SUMMARY}" diff --git a/.github/workflows/issues-labels.yml b/.github/workflows/issues-labels.yml new file mode 100644 index 00000000..3a60cdd7 --- /dev/null +++ b/.github/workflows/issues-labels.yml @@ -0,0 +1,42 @@ +# Verifies if an issue has at least one of the `[scope]` and one of the +# `[priority]` labels. If not, the bot adds labels with a warning emoji +# to indicate that those labels need to be added. + +name: Issue labels check + +on: + issues: + types: [opened, labeled, unlabeled] + +permissions: + issues: write + +jobs: + check-labels: + runs-on: ubuntu-latest + + steps: + - name: Setup easyscience[bot] + id: bot + uses: ./.github/actions/setup-easyscience-bot + with: + app-id: ${{ vars.EASYSCIENCE_APP_ID }} + private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} + + - name: Check for required [scope] label + uses: trstringer/require-label-prefix@v1 + with: + secret: ${{ steps.bot.outputs.token }} + prefix: '[scope]' + labelSeparator: ' ' + addLabel: true + defaultLabel: '[scope] ⚠️ label needed' + + - name: Check for required [priority] label + uses: trstringer/require-label-prefix@v1 + with: + secret: ${{ steps.bot.outputs.token }} + prefix: '[priority]' + labelSeparator: ' ' + addLabel: true + defaultLabel: '[priority] ⚠️ label needed' diff --git a/.github/workflows/lint-format.yml b/.github/workflows/lint-format.yml new file mode 100644 index 00000000..f1135fa5 --- /dev/null +++ b/.github/workflows/lint-format.yml @@ -0,0 +1,124 @@ +# 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 + +on: + # Trigger the workflow on push + push: + branches-ignore: [master, main] # Already verified in PR + # Do not run this workflow on creating a new tag starting with + # 'v', e.g. 'v1.0.3' (see publish-pypi.yml) + tags-ignore: ['v*'] + # Trigger the workflow on pull request + pull_request: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Allow only one concurrent workflow, skipping runs queued between the run +# in-progress and latest queued. And cancel in-progress runs. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +# Set the environment variables to be used in all jobs defined in this workflow +env: + CI_BRANCH: ${{ github.head_ref || github.ref_name }} + +jobs: + lint-format: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + + - name: Run post-install developer steps + run: pixi run post-install + + - name: Check validity of pyproject.toml + id: pyproject + continue-on-error: true + shell: bash + run: pixi run pyproject-check + + - name: Check SPDX license headers + id: license_headers + continue-on-error: true + shell: bash + run: pixi run license-check + + - name: Check linting of Python code + id: py_lint + continue-on-error: true + shell: bash + run: pixi run py-lint-check + + - name: Check formatting of Python code + id: py_format + continue-on-error: true + shell: bash + run: pixi run py-format-check + + - name: Check linting of docstrings in Python code + id: docstring_lint + continue-on-error: true + shell: bash + run: pixi run docstring-lint-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 + + - name: Check linting of Python code in Jupyter notebooks (ipynb) + id: notebook_lint + continue-on-error: true + shell: bash + run: pixi run notebook-lint-check + + # Add summary + - name: Add quality checks summary + if: always() + shell: bash + run: | + { + echo "## 🧪 Checks Summary" + echo "" + echo "| Check | Status |" + echo "|-------|--------|" + 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 "| 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.pyproject.outcome == 'failure' + || steps.license_headers.outcome == 'failure' + || steps.py_lint.outcome == 'failure' + || steps.py_format.outcome == 'failure' + || steps.docstring_lint.outcome == 'failure' + || steps.nonpy_format.outcome == 'failure' + || steps.notebook_lint.outcome == 'failure' + shell: bash + run: exit 1 diff --git a/.github/workflows/labels.yaml b/.github/workflows/pr-labels.yml similarity index 70% rename from .github/workflows/labels.yaml rename to .github/workflows/pr-labels.yml index 1e8bd846..642cd318 100644 --- a/.github/workflows/labels.yaml +++ b/.github/workflows/pr-labels.yml @@ -1,7 +1,16 @@ # Verifies if a pull request has at least one label from a set of valid # labels before it can be merged. +# +# NOTE: +# This workflow may be triggered twice in quick succession when a PR is +# created: +# 1) `opened` — when the pull request is initially created +# 2) `labeled` — if labels are added immediately after creation +# (e.g. by manual labeling, another workflow, or GitHub App). +# +# These are separate GitHub events, so two workflow runs can be started. -name: PR label checks +name: PR labels check on: pull_request_target: @@ -11,15 +20,17 @@ permissions: pull-requests: read jobs: - require-label: + check-labels: runs-on: ubuntu-latest + steps: - - name: Validate required labels + - name: Check for valid labels run: | PR_LABELS=$(echo '${{ toJson(github.event.pull_request.labels.*.name) }}' | jq -r '.[]') + echo "Current PR labels: $PR_LABELS" VALID_LABELS=( - "[maintainer] auto-pull-request" + "[bot] release" "[scope] bug" "[scope] documentation" "[scope] enhancement" diff --git a/.github/workflows/pypi-publish.yaml b/.github/workflows/pypi-publish.yaml deleted file mode 100644 index 7767f8d2..00000000 --- a/.github/workflows/pypi-publish.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# Builds a Python package and publish it to PyPI when a new tag is -# created. - -name: PyPI publishing - -on: - # Runs on creating a new tag starting with 'v', e.g. 'v1.0.3' - push: - tags: ['v*'] - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -jobs: - pypi-publish: - runs-on: ubuntu-latest - - steps: - - name: Check-out repository - uses: actions/checkout@v5 - with: - fetch-depth: '0' # full history with tags to get the version number by versioningit - - - name: Set up pixi - uses: prefix-dev/setup-pixi@v0.9.3 - with: - environments: default - activate-environment: default - run-install: true - frozen: true - cache: false - post-cleanup: false - - - name: Install and setup development dependencies - shell: bash - run: pixi run dev - - - name: Create Python package - run: pixi run python -m build - - - name: Publish distribution 📦 to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.PYPI_PASSWORD }} diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml new file mode 100644 index 00000000..e9986cac --- /dev/null +++ b/.github/workflows/pypi-publish.yml @@ -0,0 +1,46 @@ +# Builds a Python package and publish it to PyPI when a new tag is +# created. + +name: PyPI publishing + +on: + # Runs on creating a new tag starting with 'v', e.g. 'v1.0.3' + push: + tags: ['v*'] + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + pypi-publish: + runs-on: ubuntu-latest + + permissions: + contents: read + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + + steps: + - name: Check-out repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 # full history with tags to get the version number by versioningit + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + + # Build the Python package (to dist/ folder) + - name: Create Python package + run: pixi run default-build + + # Publish the package to PyPI (from dist/ folder) + # Instead of publishing with personal access token, we use + # GitHub Actions OIDC to get a short-lived token from PyPI. + # New publisher must be previously configured in PyPI at + # https://pypi.org/manage/project/easydiffraction/settings/publishing/ + # Use the following data: + # Owner: easyscience + # Repository name: diffraction-lib + # Workflow name: pypi-publish.yml + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: 'dist' diff --git a/.github/workflows/pypi-test.yaml b/.github/workflows/pypi-test.yaml deleted file mode 100644 index e083846e..00000000 --- a/.github/workflows/pypi-test.yaml +++ /dev/null @@ -1,97 +0,0 @@ -name: PyPI package tests - -on: - # Run daily, at 00:00. - schedule: - - cron: '0 0 * * *' - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Allow only one concurrent workflow, skipping runs queued between the run -# in-progress and latest queued. And cancel in-progress runs. -concurrency: - group: - ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -# Set the environment variables to be used in all jobs defined in this workflow -env: - CI_BRANCH: ${{ github.head_ref || github.ref_name }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - -jobs: - # Job 1: Test installation from PyPI on multiple OS - pypi-package-tests: - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - - runs-on: ${{ matrix.os }} - - steps: - - name: Set up pixi - uses: prefix-dev/setup-pixi@v0.9.3 - with: - run-install: false - cache: false - post-cleanup: false - - - name: - Download the pixi configuration file from the ${{ env.CI_BRANCH}} - branch - shell: bash - run: | - curl -LO https://raw.githubusercontent.com/easyscience/diffraction-lib/${CI_BRANCH}/pixi.toml - - - name: Download the tests from the ${{ env.DEFAULT_BRANCH }} branch - shell: bash - run: | - curl -LO https://github.com/easyscience/diffraction-lib/archive/refs/heads/${DEFAULT_BRANCH}.zip - unzip ${DEFAULT_BRANCH}.zip -d . - mkdir -p tests - cp -r diffraction-lib-${DEFAULT_BRANCH}/tests/* tests/ - cp diffraction-lib-${DEFAULT_BRANCH}/pytest.ini . - rm -rf ${DEFAULT_BRANCH}.zip diffraction-lib-${DEFAULT_BRANCH} - - - name: Create the environment and install dependencies - run: pixi install - - - name: Run unit tests to verify the installation - run: pixi run unit-tests - - - name: Run integration tests to verify the installation - run: pixi run integration-tests - - # Github token to avoid hitting the unauthenticated API rate limit - - name: List and fetch the EasyDiffraction tutorials - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - pixi run easydiffraction --version - pixi run easydiffraction list-tutorials - pixi run easydiffraction download-all-tutorials - - - name: Test tutorials as notebooks - run: pixi run notebook-tests - - # Job 2: Trigger dashboard build - dashboard-build-trigger: - needs: pypi-package-tests - - runs-on: ubuntu-latest - - steps: - - name: Check-out repository - uses: actions/checkout@v5 - - - name: Trigger dashboard build - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - await github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: "dashboard.yaml", - ref: "${{ env.CI_BRANCH }}" - }); diff --git a/.github/workflows/pypi-test.yml b/.github/workflows/pypi-test.yml new file mode 100644 index 00000000..6493e64f --- /dev/null +++ b/.github/workflows/pypi-test.yml @@ -0,0 +1,80 @@ +name: PyPI package tests + +on: + # Run daily, at 00:00. + schedule: + - cron: '0 0 * * *' + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Allow only one concurrent workflow, skipping runs queued between the run +# in-progress and latest queued. And cancel in-progress runs. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +# Set the environment variables to be used in all jobs defined in this workflow +env: + CI_BRANCH: ${{ github.head_ref || github.ref_name }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + +jobs: + # Job 1: Test installation from PyPI on multiple OS + pypi-package-tests: + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + with: + environments: '' + activate-environment: '' + run-install: false + frozen: false + + - name: Init pixi project + run: pixi init easydiffraction + + - name: Add Python 3.13 from Conda + working-directory: easydiffraction + run: pixi add "python=3.13" + + - name: Add other Conda dependencies + working-directory: easydiffraction + run: pixi add gsl + + - name: Add easydiffraction from PyPI + working-directory: easydiffraction + run: pixi add --pypi "easydiffraction" + + - name: Add dev dependencies from PyPI + working-directory: easydiffraction + run: pixi add --pypi pytest pytest-xdist + + - name: Add Pixi task as a shortcut + working-directory: easydiffraction + run: pixi task add easydiffraction "python -m easydiffraction" + + - name: Run unit tests to verify the installation + working-directory: easydiffraction + run: pixi run python -m pytest ../tests/unit/ --color=yes -v + + - name: Run integration tests to verify the installation + working-directory: easydiffraction + run: pixi run python -m pytest ../tests/integration/ --color=yes -n auto + + # Job 2: Build and publish dashboard (reusable workflow) + run-reusable-workflows: + needs: pypi-package-tests # depend on previous job + uses: ./.github/workflows/dashboard.yml + secrets: inherit diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml deleted file mode 100644 index a3c1b72a..00000000 --- a/.github/workflows/quality.yaml +++ /dev/null @@ -1,123 +0,0 @@ -# The workflow is divided into several steps to ensure code quality: -# - 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 - -name: Code quality checks - -on: - # Trigger the workflow on push - push: - # Every branch - branches: ['**'] - # Do not run this workflow on creating a new tag starting with - # 'v', e.g. 'v1.0.3' (see publish-pypi.yml) - tags-ignore: ['v*'] - # Trigger the workflow on pull request - pull_request: - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Allow only one concurrent workflow, skipping runs queued between the run -# in-progress and latest queued. And cancel in-progress runs. -concurrency: - group: - ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -# Set the environment variables to be used in all jobs defined in this workflow -env: - CI_BRANCH: ${{ github.head_ref || github.ref_name }} - -jobs: - code-quality: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v5 - - - name: Set up pixi - uses: prefix-dev/setup-pixi@v0.9.3 - with: - environments: default - activate-environment: default - run-install: true - frozen: true - cache: false - post-cleanup: false - - - name: Install and setup development dependencies - shell: bash - run: pixi run dev - - # Check the validity of pyproject.toml - - name: Check validity of pyproject.toml - id: check_pyproject - continue-on-error: true - shell: bash - run: pixi run pyproject-check - - # Check code linting with Ruff in the project root - - name: Check code linting - id: check_code_linting - 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 - 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 - continue-on-error: true - shell: bash - run: pixi run docs-format-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 - continue-on-error: true - shell: bash - run: pixi run nonpy-format-check - - # Check formatting of Jupyter Notebooks in the tutorials folder - - name: Convert tutorial scripts to notebooks and check formatting - id: check_notebooks_formatting - continue-on-error: true - shell: bash - run: | - pixi run notebook-prepare - pixi run notebook-format-check - - # Add summary - - name: Add quality checks summary - if: always() - shell: bash - run: | - { - echo "## 🧪 Code Quality Checks Summary" - echo "" - echo "| Check | Status |" - echo "|-------|--------|" - echo "| pyproject.toml | ${{ steps.check_pyproject.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' && '✅' || '❌' }} |" - } >> "$GITHUB_STEP_SUMMARY" - - # Fail job requirement - - name: Fail job if any check failed - if: failure() - shell: bash - run: exit 1 diff --git a/.github/workflows/release-notes.yaml b/.github/workflows/release-notes.yml similarity index 81% rename from .github/workflows/release-notes.yaml rename to .github/workflows/release-notes.yml index 73006ff2..cb3e28d6 100644 --- a/.github/workflows/release-notes.yaml +++ b/.github/workflows/release-notes.yml @@ -25,18 +25,14 @@ jobs: fetch-depth: 0 # full history with tags to get the version number - name: Set up pixi - uses: prefix-dev/setup-pixi@v0.9.3 - with: - environments: default - activate-environment: default - run-install: true - frozen: true - cache: false - post-cleanup: false + uses: ./.github/actions/setup-pixi - - name: Install and setup development dependencies - shell: bash - run: pixi run dev + - name: Setup easyscience[bot] + id: bot + uses: ./.github/actions/setup-easyscience-bot + with: + app-id: ${{ vars.EASYSCIENCE_APP_ID }} + private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} - name: Drafts the next release notes id: draft @@ -61,9 +57,8 @@ jobs: labels: ['[scope] bug'] - title: 'Changed' labels: ['[scope] maintenance', '[scope] documentation'] - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ steps.bot.outputs.token }} - name: Create GitHub draft release uses: softprops/action-gh-release@v2 @@ -73,4 +68,4 @@ jobs: name: ${{ steps.draft.outputs.release_name }} body: ${{ steps.draft.outputs.release_notes }} env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ steps.bot.outputs.token }} diff --git a/.github/workflows/release-pr.yaml b/.github/workflows/release-pr.yaml deleted file mode 100644 index eda67884..00000000 --- a/.github/workflows/release-pr.yaml +++ /dev/null @@ -1,57 +0,0 @@ -# This workflow creates an automated release PR from `develop` into `master`. -# -# Usage: -# - Triggered manually via workflow_dispatch. -# - Creates a PR titled "Release: merge develop into master". -# - Adds the label "[maintainer] auto-pull-request" so it is excluded from changelogs. -# - The PR body makes clear that this is automation only (no review needed). -# -# Required repo config: -# https://github.com/organizations/easyscience/settings/secrets/actions -# https://github.com/organizations/easyscience/settings/variables/actions -# - Actions secret: EASYSCIENCE_APP_KEY (GitHub App private key PEM) -# - Actions variable: EASYSCIENCE_APP_ID (GitHub App ID) - -name: Release PR (develop -> master) - -on: - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -permissions: - contents: read - pull-requests: write - -# Set the environment variables to be used in all jobs defined in this workflow -env: - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - -jobs: - create-pull-request: - runs-on: ubuntu-latest - steps: - - name: Create GitHub App installation token - id: app-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ vars.EASYSCIENCE_APP_ID }} - private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} - - - name: Checkout develop branch - uses: actions/checkout@v5 - with: - ref: develop - token: ${{ steps.app-token.outputs.token }} - - - name: Create PR from develop to ${{ env.DEFAULT_BRANCH }} - run: | - gh pr create \ - --base ${{ env.DEFAULT_BRANCH }} \ - --head develop \ - --title "Release: merge develop into ${{ env.DEFAULT_BRANCH }}" \ - --label "[maintainer] auto-pull-request" \ - --body "This PR is created automatically to trigger the release pipeline. It merges the accumulated changes from \`develop\` into \`${{ env.DEFAULT_BRANCH }}\`. - - It is labeled \`[maintainer] auto-pull-request\` and is excluded from release notes and version bump logic." - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml new file mode 100644 index 00000000..7e6fda49 --- /dev/null +++ b/.github/workflows/release-pr.yml @@ -0,0 +1,55 @@ +# This workflow creates an automated release PR from a source branch into the default branch. +# +# Usage: +# - Triggered manually via workflow_dispatch. +# - Creates a PR titled "Release: merge into ". +# - Adds the label "[bot] release" so it is excluded from changelogs. +# - The PR body makes clear that this is automation only (no review needed). + +name: 'Release PR (develop → master)' + +on: + workflow_dispatch: + inputs: + source_branch: + description: 'Source branch to create PR from' + required: false + default: 'develop' + type: string + +permissions: + contents: read + pull-requests: write + +env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + SOURCE_BRANCH: ${{ inputs.source_branch || 'develop' }} + +jobs: + create-pull-request: + runs-on: ubuntu-latest + steps: + - name: Checkout ${{ env.SOURCE_BRANCH }} branch + uses: actions/checkout@v5 + with: + ref: ${{ env.SOURCE_BRANCH }} + + - name: Setup easyscience[bot] + id: bot + uses: ./.github/actions/setup-easyscience-bot + with: + app-id: ${{ vars.EASYSCIENCE_APP_ID }} + private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} + + - name: Create PR from ${{ env.SOURCE_BRANCH }} to ${{ env.DEFAULT_BRANCH }} + env: + GH_TOKEN: ${{ steps.bot.outputs.token }} + run: | + gh pr create \ + --base ${{ env.DEFAULT_BRANCH }} \ + --head ${{ env.SOURCE_BRANCH }} \ + --title "Release: merge ${{ env.SOURCE_BRANCH }} into ${{ env.DEFAULT_BRANCH }}" \ + --label "[bot] release" \ + --body "This PR is created automatically to trigger the release pipeline. It merges the accumulated changes from \`${{ env.SOURCE_BRANCH }}\` into \`${{ env.DEFAULT_BRANCH }}\`. + + ⚠️ It is labeled \`[bot] release\` and is excluded from release notes and version bump logic." diff --git a/.github/workflows/security.yaml b/.github/workflows/security.yaml deleted file mode 100644 index b837b879..00000000 --- a/.github/workflows/security.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# Integrates a collection of open source static analysis tools with -# GitHub code scanning. -# https://github.com/github/ossar-action - -name: Security scans - -on: - # Trigger the workflow on pull request - pull_request: - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -jobs: - scan-security-ossar: - # OSSAR runs on windows-latest. - # ubuntu-latest and macos-latest support coming soon - runs-on: windows-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v5 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 - - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} - - - name: Run open source static analysis tools - uses: github/ossar-action@main - id: ossar - - - name: Upload results to Security tab - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: ${{ steps.ossar.outputs.sarifFile }} diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 00000000..9b34cccf --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,93 @@ +# Code scanning (CodeQL) for vulnerabilities and insecure coding patterns. +# +# What this workflow does +# - Runs GitHub CodeQL analysis and uploads results to your repository's Security tab. +# - Triggers on PRs (so findings appear as PR checks) and on pushes to `develop`. +# - Runs on a weekly schedule. +# +# Where to find results on GitHub +# - Repository → Security → Code scanning alerts +# (You can filter by tool = CodeQL and by branch.) +# +# Where to configure on GitHub +# - Repository → Settings → Advanced Security +# Enable "GitHub Advanced Security" (if available) and configure CodeQL there. +# - Repository → Security → Code scanning alerts +# This page shows findings produced by this workflow. +# +# Notes about the scheduled run +# - Scheduled workflows are triggered from the repository's *default branch*. +# If your default branch is `master` but you want the scheduled scan to analyze +# `develop`, this workflow checks out `develop` explicitly for scheduled runs. +# +# References +# - CodeQL Action: https://github.com/github/codeql-action +# - Advanced setup docs: https://docs.github.com/en/code-security/code-scanning + +name: Security scans with CodeQL + +on: + # Run on pull requests so results show up as PR checks and code + # scanning alerts. + pull_request: + branches: [master, main, develop] + + # Run on pushes (e.g., after merging PRs). + push: + branches: [master, main, develop] + + # Run weekly. (Cron is in UTC.) + schedule: + - cron: '0 3 * * 1' + +permissions: + contents: read + security-events: write + +jobs: + codeql: + name: Code scanning + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + # Keep this list tight to avoid noise and speed up runs. + language: [python, actions] + + steps: + # Scheduled workflows run from the default branch. + # We explicitly analyze `develop` on the schedule to keep the scan + # focused on the active dev branch. + - name: Checkout repository (scheduled → develop) + if: ${{ github.event_name == 'schedule' }} + uses: actions/checkout@v5 + with: + ref: develop + + - name: Checkout repository + if: ${{ github.event_name != 'schedule' }} + uses: actions/checkout@v5 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + + print-link: + name: Print results link + runs-on: ubuntu-latest + + needs: codeql + permissions: {} # no special perms needed just to print links + + steps: + - name: Add Code Scanning link to job summary + run: | + echo "## 🔎 CodeQL Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "View Code Scanning alerts here:" >> $GITHUB_STEP_SUMMARY + echo "${{ github.server_url }}/${{ github.repository }}/security/code-scanning" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/test-trigger.yaml b/.github/workflows/test-trigger.yml similarity index 61% rename from .github/workflows/test-trigger.yaml rename to .github/workflows/test-trigger.yml index dedfbd91..ecf6b40c 100644 --- a/.github/workflows/test-trigger.yaml +++ b/.github/workflows/test-trigger.yml @@ -7,6 +7,9 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: +permissions: + contents: read + jobs: code-tests-trigger: runs-on: ubuntu-latest @@ -17,14 +20,21 @@ jobs: with: ref: develop + - name: Setup easyscience[bot] + id: bot + uses: ./.github/actions/setup-easyscience-bot + with: + app-id: ${{ vars.EASYSCIENCE_APP_ID }} + private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} + - name: Dispatch code tests workflow - uses: actions/github-script@v7 + uses: ./.github/actions/github-script with: - github-token: ${{ secrets.GITHUB_TOKEN }} + github-token: ${{ steps.bot.outputs.token }} script: | await github.rest.actions.createWorkflowDispatch({ owner: context.repo.owner, repo: context.repo.repo, - workflow_id: "test.yaml", + workflow_id: "test.yml", ref: "develop" }); diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml deleted file mode 100644 index 11b06f14..00000000 --- a/.github/workflows/test.yaml +++ /dev/null @@ -1,259 +0,0 @@ -# This is the main workflow for testing the code before and after -# packaging it. -# The workflow is divided into three jobs: -# 1. env-prepare: -# - Prepare the environment for testing -# 2. source-test: -# - Test the code base against the latest code in the repository -# - Create the Python package -# - Upload the Python package for the next job -# 3. package-test: -# - Download the Python package (including extra files) from the previous job -# - Install the downloaded Python package -# - Test the code base against the installed package -# 4. dashboard-build-trigger: -# - Trigger the dashboard build workflow to update the code quality -# metrics on the dashboard - -name: Code and package tests - -on: - # Trigger the workflow on push - push: - # Every branch - branches: ['**'] - # But do not run this workflow on creating a new tag starting with - # 'v', e.g. 'v1.0.3' (see publish-pypi.yml) - tags-ignore: ['v*'] - # Trigger the workflow on pull request - pull_request: - branches: ['**'] - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Need permissions to trigger the dashboard build workflow -permissions: - actions: write - contents: read - -# Allow only one concurrent workflow, skipping runs queued between the run -# in-progress and latest queued. And cancel in-progress runs. -concurrency: - group: - ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -# Set the environment variables to be used in all jobs defined in this workflow -env: - CI_BRANCH: ${{ github.head_ref || github.ref_name }} - -jobs: - # Job 1: Prepare environment - env-prepare: - runs-on: [ubuntu-latest] - - outputs: - pytest-marks: ${{ steps.set-mark.outputs.pytest_marks }} - - steps: - # Determine if integration tests should be run fully or only the fast ones - # (to save time on branches other than master and develop) - - name: Set mark for integration tests - id: set-mark - run: | - if [[ "${{ env.CI_BRANCH }}" == "master" || "${{ env.CI_BRANCH }}" == "develop" ]]; then - echo "pytest_marks=" >> $GITHUB_OUTPUT - else - echo "pytest_marks=-m fast" >> $GITHUB_OUTPUT - fi - - # Job 2: Test code - source-test: - needs: env-prepare # depend on previous job - - strategy: - fail-fast: false - matrix: - os: [ubuntu-24.04, macos-14, windows-2022] - - runs-on: ${{ matrix.os }} - - env: - PIXI_ENVS: 'py311-dev py313-dev' - - steps: - - name: Checkout repository - uses: actions/checkout@v5 - with: - fetch-depth: '0' # full history with tags to get the version number by versioningit - - - name: Set up pixi - uses: prefix-dev/setup-pixi@v0.9.3 - with: - environments: ${{ env.PIXI_ENVS }} - run-install: true - frozen: true - cache: false - post-cleanup: false - - - name: Install and setup development dependencies - shell: bash - run: | - for env in ${{ env.PIXI_ENVS }}; do - echo "🔹🔸🔹🔸🔹 Current env: $env 🔹🔸🔹🔸🔹" - pixi run --environment $env dev - echo "PYTHONPATH:" - pixi run printenv PYTHONPATH || true - pixi run --environment $env easydiffraction --version - done - - - name: Run unit tests - shell: bash - run: | - for env in ${{ env.PIXI_ENVS }}; do - echo "🔹🔸🔹🔸🔹 Current env: $env 🔹🔸🔹🔸🔹" - pixi run --environment $env unit-tests - done - - - name: - Run integration tests ${{ needs.env-prepare.outputs.pytest-marks }} - shell: bash - run: | - for env in ${{ env.PIXI_ENVS }}; do - echo "🔹🔸🔹🔸🔹 Current env: $env 🔹🔸🔹🔸🔹" - pixi run --environment $env integration-tests ${{ needs.env-prepare.outputs.pytest-marks }} - done - - # Delete all local tags when not on a tagged commit to force versioningit - # to fall back to the configured default-tag, which is '999.0.0' in our case. - # This is needed for testing the package in the next job, as its version - # must be higher than the PyPI version for pip to prefer the local version. - - name: Force using versioningit default tag (non tagged release) - if: startsWith(github.ref , 'refs/tags/v') != true - run: git tag --delete $(git tag) - - - name: Create Python package - shell: bash - run: | - for env in ${{ env.PIXI_ENVS }}; do - echo "🔹🔸🔹🔸🔹 Current env: $env 🔹🔸🔹🔸🔹" - pixi run -e $env dist-build - env_prefix="${env%%-*}" - echo "📦 Moving built wheel to dist/$env_prefix/" - pixi run mkdir -p dist/$env_prefix - pixi run mv dist/*.whl dist/$env_prefix/ - done - - - name: Remove local easydiffraction from pixi.toml - shell: bash - run: pixi remove --pypi easydiffraction - - - name: Remove Python cache files before uploading - shell: bash - run: pixi run clean-pycache - - # More than one file/dir need to be specified in 'path', to preserve the - # structure of the dist/ directory, not only its contents. - - name: Upload Python package for the next job - uses: actions/upload-artifact@v4 - with: - name: edl_${{ matrix.os }}_${{ runner.arch }} - path: | - dist/ - tests/ - pytest.ini - pixi.toml - pixi.lock - if-no-files-found: 'error' - compression-level: 0 - - # Job 3: Test the package - package-test: - needs: [env-prepare, source-test] # depend on previous jobs - - strategy: - fail-fast: false - matrix: - os: [ubuntu-24.04, macos-14, windows-2022] - - runs-on: ${{ matrix.os }} - - env: - PIXI_ENVS: 'py311-dev py313-dev' - - steps: - - name: - Download zipped Python package (incl. extra files) from previous job - uses: actions/download-artifact@v4 - with: # name or path are taken from the upload step of the previous job - name: edl_${{ matrix.os }}_${{ runner.arch }} - path: . # directory to extract downloaded zipped artifacts - - - name: Set up pixi - uses: prefix-dev/setup-pixi@v0.9.3 - with: - environments: ${{ env.PIXI_ENVS }} - run-install: true - frozen: true - cache: false - post-cleanup: false - - - name: Install and setup development dependencies - shell: bash - run: | - for env in ${{ env.PIXI_ENVS }}; do - echo "🔹🔸🔹🔸🔹 Current env: $env 🔹🔸🔹🔸🔹" - pixi run --environment $env wheel - done - - - name: Install easydiffraction package from the built wheel - shell: bash - run: | - for env in ${{ env.PIXI_ENVS }}; do - echo "🔹🔸🔹🔸🔹 Current env: $env 🔹🔸🔹🔸🔹" - env_prefix="${env%%-*}" - echo "📦 Looking for wheel in dist/$env_prefix/" - whl_path="$(find dist/${env_prefix} -name '*.whl' | head -1)" - echo "📦 Installing easydiffraction from: $whl_path" - pixi run --environment $env python -m uv pip install "${whl_path}[all]" --reinstall-package easydiffraction - pixi run --environment $env easydiffraction --version - done - - - name: Run unit tests - shell: bash - run: | - for env in ${{ env.PIXI_ENVS }}; do - echo "🔹🔸🔹🔸🔹 Current env: $env 🔹🔸🔹🔸🔹" - pixi run --environment $env unit-tests - done - - - name: - Run integration tests ${{ needs.env-prepare.outputs.pytest-marks }} - shell: bash - run: | - for env in ${{ env.PIXI_ENVS }}; do - echo "🔹🔸🔹🔸🔹 Current env: $env 🔹🔸🔹🔸🔹" - pixi run --environment $env integration-tests ${{ needs.env-prepare.outputs.pytest-marks }} - done - - # Job 4: Trigger dashboard build - dashboard-build-trigger: - needs: [source-test, package-test] # depend on previous jobs - - runs-on: ubuntu-latest - - steps: - - name: Check-out repository - uses: actions/checkout@v5 - - - name: Trigger dashboard build - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - await github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: "dashboard.yaml", - ref: "${{ env.CI_BRANCH }}" - }); diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..745c0b03 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,279 @@ +# This is the main workflow for testing the code before and after +# packaging it. +# The workflow is divided into three jobs: +# 1. env-prepare: +# - Prepare the environment for testing +# 2. source-test: +# - Test the code base against the latest code in the repository +# - Create the Python package +# - Upload the Python package for the next job +# 3. package-test: +# - Download the Python package (including extra files) from the previous job +# - Install the downloaded Python package +# - Test the code base against the installed package +# 4. dashboard-build-trigger: +# - Trigger the dashboard build workflow to update the code quality +# metrics on the dashboard + +name: Code and package tests + +on: + # Trigger the workflow on push + push: + branches-ignore: [master, main] # Already verified in PR + # But do not run this workflow on creating a new tag starting with + # 'v', e.g. 'v1.0.3' (see publish-pypi.yml) + tags-ignore: ['v*'] + # Trigger the workflow on pull request + pull_request: + branches: ['**'] + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Need permissions to trigger the dashboard build workflow +permissions: + actions: write + contents: read + +# Allow only one concurrent workflow, skipping runs queued between the run +# in-progress and latest queued. And cancel in-progress runs. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +# Set the environment variables to be used in all jobs defined in this workflow +env: + CI_BRANCH: ${{ github.head_ref || github.ref_name }} + PY_VERSIONS: '3.11 3.13' + PIXI_ENVS: 'py-311-env py-313-env' + +jobs: + # Job 1: Set up environment variables + env-prepare: + runs-on: [ubuntu-latest] + + outputs: + pytest-marks: ${{ steps.set-mark.outputs.pytest_marks }} + + steps: + # Determine if integration tests should be run fully or only the fast ones + # (to save time on branches other than master and develop) + - name: Set mark for integration tests + id: set-mark + run: | + if [[ "${{ env.CI_BRANCH }}" == "master" || "${{ env.CI_BRANCH }}" == "develop" ]]; then + echo "pytest_marks=" >> $GITHUB_OUTPUT + else + echo "pytest_marks=-m fast" >> $GITHUB_OUTPUT + fi + + # Job 2: Test code + source-test: + needs: env-prepare # depend on previous job + + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, macos-15, windows-2022] + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + with: + environments: ${{ env.PIXI_ENVS }} + + - name: Run unit tests + shell: bash + run: | + set -euo pipefail + + for py_ver in $PY_VERSIONS; do + echo + echo "🔹🔸🔹🔸🔹 Python: $py_ver 🔹🔸🔹🔸🔹" + + env="py-$(echo $py_ver | tr -d .)-env" # Converts 3.11 -> py-311-env + + echo "Running tests in environment: $env" + pixi run --environment $env unit-tests + done + + - name: Run integration tests ${{ needs.env-prepare.outputs.pytest-marks }} + shell: bash + run: | + set -euo pipefail + + for py_ver in $PY_VERSIONS; do + echo + echo "🔹🔸🔹🔸🔹 Python: $py_ver 🔹🔸🔹🔸🔹" + + env="py-$(echo $py_ver | tr -d .)-env" # Converts 3.11 -> py-311-env + + echo "Running tests in environment: $env" + pixi run --environment $env integration-tests ${{ needs.env-prepare.outputs.pytest-marks }} + done + + # Delete all local tags when not on a tagged commit to force versioningit + # to fall back to the configured default-tag, which is '999.0.0' in our case. + # This is needed for testing the package in the next job, as its version + # must be higher than the PyPI version for pip to prefer the local version. + - name: Force using versioningit default tag (non tagged release) + if: startsWith(github.ref , 'refs/tags/v') != true + run: git tag --delete $(git tag) + + - name: Build package wheels for all Python versions + shell: bash + run: | + set -euo pipefail + + for py_ver in $PY_VERSIONS; do + echo + echo "🔹🔸🔹🔸🔹 Python: $py_ver 🔹🔸🔹🔸🔹" + + env="py-$(echo $py_ver | tr -d .)-env" # Converts 3.11 -> py-311-env + + echo "Building wheel in environment: $env" + pixi run --environment $env dist-build + + echo "Moving built wheel to dist/py$py_ver/" + pixi run mkdir -p dist/py$py_ver + pixi run mv dist/*.whl dist/py$py_ver/ + done + + - name: Remove Python cache files before uploading + shell: bash + run: pixi run clean-pycache + + # More than one file/dir need to be specified in 'path', to preserve the + # structure of the dist/ directory, not only its contents. + - name: Upload package (incl. extras) for next job + uses: ./.github/actions/upload-artifact + with: + name: easydiffraction_${{ matrix.os }}_${{ runner.arch }} + path: dist/ + + # Job 3: Test the package + package-test: + needs: source-test # depend on previous job + + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, macos-15, windows-2022] + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Download package (incl. extras) from previous job + uses: ./.github/actions/download-artifact + with: + # name and path should be taken from the upload step of the previous job + name: easydiffraction_${{ matrix.os }}_${{ runner.arch }} + path: dist/ + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + with: + environments: '' + activate-environment: '' + run-install: false + frozen: false + + - name: Install easydiffraction from the built wheel + shell: bash + run: | + set -euo pipefail + + for py_ver in $PY_VERSIONS; do + echo + echo "🔹🔸🔹🔸🔹 Python: $py_ver 🔹🔸🔹🔸🔹" + + echo "Initializing pixi project" + pixi init easydiffraction_py$py_ver + cd easydiffraction_py$py_ver + + echo "Setting macOS 14.0 as minimum required" + pixi project system-requirements add macos 14.0 + + echo "Adding Python $py_ver" + pixi add "python=$py_ver" + + echo "Adding GNU Scientific Library (required by diffpy.pdffit2)" + pixi add gsl + + # diffpy.pdffit2 wheel links @rpath/libc++.1.dylib, which must be + # present in the conda env lib/ dir on macOS (Python propagates its + # own @loader_path/../lib/ rpath to loaded extensions). Added as + # platform-specific deps so this is a no-op on Linux/Windows. + echo "Adding libc++ for macOS (required by diffpy.pdffit2)" + pixi add --platform osx-arm64 libcxx + pixi add --platform osx-64 libcxx + + echo "Looking for wheel in ../dist/py$py_ver/" + ls -l "../dist/py$py_ver/" + + whl_path=(../dist/"py$py_ver"/*.whl) + if [[ ! -f "${whl_path[0]}" ]]; then + echo "❌ No wheel found in ../dist/py$py_ver/" + exit 1 + fi + + whl_url="file://$(python -c 'import os,sys; print(os.path.abspath(sys.argv[1]))' "${whl_path[0]}")" + + echo "Adding easydiffraction from: $whl_url" + pixi add --pypi "easydiffraction[dev] @ ${whl_url}" + + echo "Exiting pixi project directory" + cd .. + done + + - name: Run unit tests + shell: bash + run: | + set -euo pipefail + + for py_ver in $PY_VERSIONS; do + echo + echo "🔹🔸🔹🔸🔹 Python: $py_ver 🔹🔸🔹🔸🔹" + + echo "Entering pixi project directory easydiffraction_py$py_ver" + cd easydiffraction_py$py_ver + + echo "Running tests" + pixi run python -m pytest ../tests/unit/ --color=yes -v + + echo "Exiting pixi project directory" + cd .. + done + + - name: Run integration tests ${{ needs.env-prepare.outputs.pytest-marks }} + shell: bash + run: | + set -euo pipefail + + for py_ver in $PY_VERSIONS; do + echo + echo "🔹🔸🔹🔸🔹 Python: $py_ver 🔹🔸🔹🔸🔹" + + echo "Entering pixi project directory easydiffraction_py$py_ver" + cd easydiffraction_py$py_ver + + echo "Running tests" + pixi run python -m pytest ../tests/integration/ --color=yes -n auto -v ${{ needs.env-prepare.outputs.pytest-marks }} + + echo "Exiting pixi project directory" + cd .. + done + + # Job 4: Build and publish dashboard (reusable workflow) + run-reusable-workflows: + needs: package-test # depend on previous job + uses: ./.github/workflows/dashboard.yml + secrets: inherit diff --git a/.github/workflows/tutorial-tests-colab.yaml b/.github/workflows/tutorial-tests-colab.yaml index 08e2e2b6..35c4a753 100644 --- a/.github/workflows/tutorial-tests-colab.yaml +++ b/.github/workflows/tutorial-tests-colab.yaml @@ -7,8 +7,7 @@ on: # Allow only one concurrent workflow, skipping runs queued between the run in-progress and latest queued. # And cancel in-progress runs. concurrency: - group: - ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: @@ -37,10 +36,10 @@ jobs: - name: Install Python dependencies run: - python -m pip install 'easydiffraction[visualization]' nbconvert - nbmake pytest pytest-xdist + python -m pip install 'easydiffraction[visualization]' nbconvert nbmake pytest + pytest-xdist - name: Check if Jupyter Notebooks run without errors run: > - python -m pytest --nbmake docs/tutorials/ --nbmake-timeout=600 - --color=yes -n=auto + python -m pytest --nbmake docs/tutorials/ --nbmake-timeout=600 --color=yes + -n=auto diff --git a/.github/workflows/tutorial-tests-trigger.yaml b/.github/workflows/tutorial-tests-trigger.yml similarity index 61% rename from .github/workflows/tutorial-tests-trigger.yaml rename to .github/workflows/tutorial-tests-trigger.yml index ea32eef2..1bc27f4f 100644 --- a/.github/workflows/tutorial-tests-trigger.yaml +++ b/.github/workflows/tutorial-tests-trigger.yml @@ -7,6 +7,9 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: +permissions: + contents: read + jobs: tutorial-tests-trigger: runs-on: ubuntu-latest @@ -17,14 +20,21 @@ jobs: with: ref: develop + - name: Setup easyscience[bot] + id: bot + uses: ./.github/actions/setup-easyscience-bot + with: + app-id: ${{ vars.EASYSCIENCE_APP_ID }} + private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} + - name: Dispatch tutorial tests workflow - uses: actions/github-script@v7 + uses: ./.github/actions/github-script with: - github-token: ${{ secrets.GITHUB_TOKEN }} + github-token: ${{ steps.bot.outputs.token }} script: | await github.rest.actions.createWorkflowDispatch({ owner: context.repo.owner, repo: context.repo.repo, - workflow_id: "tutorial-tests.yaml", + workflow_id: "tutorial-tests.yml", ref: "develop" }); diff --git a/.github/workflows/tutorial-tests.yaml b/.github/workflows/tutorial-tests.yml similarity index 55% rename from .github/workflows/tutorial-tests.yaml rename to .github/workflows/tutorial-tests.yml index 301b43d3..4c9244d0 100644 --- a/.github/workflows/tutorial-tests.yaml +++ b/.github/workflows/tutorial-tests.yml @@ -4,7 +4,7 @@ on: # Trigger the workflow on push push: # Selected branches - branches: [master, main, develop] + branches: [develop] # master and main are already verified in PR # Trigger the workflow on pull request pull_request: branches: ['**'] @@ -15,11 +15,13 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: +permissions: + contents: read + # Allow only one concurrent workflow, skipping runs queued between the run # in-progress and latest queued. And cancel in-progress runs. concurrency: - group: - ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true # Set the environment variables to be used in all jobs defined in this workflow @@ -41,24 +43,13 @@ jobs: uses: actions/checkout@v5 - name: Set up pixi - uses: prefix-dev/setup-pixi@v0.9.3 - with: - environments: default - activate-environment: default - run-install: true - frozen: true - cache: false - post-cleanup: false - - - name: Install and setup development dependencies - shell: bash - run: pixi run dev + uses: ./.github/actions/setup-pixi - name: Test tutorials as python scripts shell: bash run: pixi run script-tests - - name: Convert tutorial scripts to notebooks + - name: Prepare notebooks shell: bash run: pixi run notebook-prepare @@ -66,24 +57,8 @@ jobs: shell: bash run: pixi run notebook-tests - # Job 2: Trigger dashboard build - dashboard-build-trigger: - needs: tutorial-tests - - runs-on: ubuntu-latest - - steps: - - name: Check-out repository - uses: actions/checkout@v5 - - - name: Trigger dashboard build - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - await github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: "dashboard.yaml", - ref: "${{ env.CI_BRANCH }}" - }); + # Job 2: Build and publish dashboard (reusable workflow) + run-reusable-workflows: + needs: tutorial-tests # depend on previous job + uses: ./.github/workflows/dashboard.yml + secrets: inherit diff --git a/.gitignore b/.gitignore index 2c43d2b8..6dc595c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,19 @@ # Python -__pycache__ -.venv +__pycache__/ +.venv/ .coverage .pyc -# PyTest -.pytest_cache - -# MyPy -.mypy_cache - # Pixi -.pixi +.pixi/ -# Ruff -.ruff_cache +# PyInstaller +dist/ +build/ +*.spec + +# MkDocs +docs/site/ # Jupyter Notebooks .ipynb_checkpoints @@ -24,16 +23,10 @@ node_modules/ # QtCreator *.autosave - -# QtCreator Qml *.qmlproject.user *.qmlproject.user.* - -# QtCreator Python *.pyproject.user *.pyproject.user.* - -# QtCreator CMake CMakeLists.txt.user* # PyCharm @@ -46,3 +39,8 @@ CMakeLists.txt.user* .DS_Store *.app *.dmg + +# Misc +.cache/ +*.log +*.zip diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c3d471cd..9a3855f4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,57 +1,61 @@ repos: - repo: local hooks: - # ----------------- - # Pre-commit checks - # ----------------- + # ------------- + # Manual checks + # ------------- - id: pixi-pyproject-check name: pixi run pyproject-check entry: pixi run pyproject-check language: system pass_filenames: false - stages: [pre-commit] + stages: [manual] - - id: pixi-py-lint-check-staged - name: pixi run py-lint-check-staged - entry: pixi run py-lint-check-pre + - id: license-headers-check + name: pixi run license-check + entry: pixi run license-check language: system pass_filenames: false - stages: [pre-commit] + stages: [manual] - - id: pixi-py-format-check-staged - name: pixi run py-format-check-staged - entry: pixi run py-format-check-pre + - id: pixi-py-lint-check + name: pixi run py-lint-check + entry: pixi run py-lint-check language: system pass_filenames: false - stages: [pre-commit] + stages: [manual] - - id: pixi-nonpy-format-check-modified - name: pixi run nonpy-format-check-modified - entry: pixi run nonpy-format-check-modified + - id: pixi-py-format-check + name: pixi run py-format-check + entry: pixi run py-format-check language: system pass_filenames: false - stages: [pre-commit] + stages: [manual] - - id: pixi-docs-format-check - name: pixi run docs-format-check - entry: pixi run docs-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: [pre-commit] + stages: [manual] - # ---------------- - # Pre-push checks - # ---------------- - id: pixi-nonpy-format-check name: pixi run nonpy-format-check entry: pixi run nonpy-format-check language: system pass_filenames: false - stages: [pre-push] + stages: [manual] + + - id: pixi-notebook-lint-check + name: pixi run notebook-lint-check + entry: pixi run notebook-lint-check + language: system + pass_filenames: false + stages: [manual] - id: pixi-unit-tests name: pixi run unit-tests entry: pixi run unit-tests language: system pass_filenames: false - stages: [pre-push] + stages: [manual] diff --git a/.prettierignore b/.prettierignore index 4bd61611..a08c3c48 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,32 @@ +# Git +.git/ + +# Copier +.copier-answers*.yml + # Pixi .pixi pixi.lock +# MkDocs +docs/overrides/ +docs/site/ +docs/docs/assets/ + +# Python +.pytest_cache/ + # MyPy -.mypy_cache +.mypy_cache/ + +# Ruff +.ruff_cache/ + +# Node +node_modules + +# Misc +.benchmarks +.cache +deps/ +tmp/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f05c041f..e8dc420f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,94 +1,446 @@ -# Contributing +# Contributing to EasyDiffraction -When contributing, please first discuss the change you wish to make via issue, -email, or any other method with the owners of this repository before making a -change. +Thank you for your interest in contributing to **EasyDiffraction**! -Please note we have a code of conduct, please follow it in all your interactions -with the project. +This guide explains how you can: -## Pull Request Process +- Report issues +- Contribute code +- Improve documentation +- Suggest enhancements +- Interact with the EasyScience community -1. Ensure any install or build dependencies are removed before the end of the - layer when doing a build. -2. Update the README.md with details of changes to the interface, this includes - new environment variables, exposed ports, useful file locations and container - parameters. -3. Increase the version numbers in any example files and the README.md to the - new version that this Pull Request would represent. The versioning scheme we - use is [SemVer](http://semver.org/). -4. You may merge the Pull Request in once you have the sign-off of two other - developers, or if you do not have permission to do that, you may request the - second reviewer to merge it for you. +Whether you are an experienced developer or contributing for the first +time, this document walks you through the entire process step by step. -## Code of Conduct +Please make sure you follow the EasyScience organization-wide +[Code of Conduct](https://github.com/easyscience/.github/blob/master/CODE_OF_CONDUCT.md). -### Our Pledge +--- -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to make participation in our project and our -community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of -experience, nationality, personal appearance, race, religion, or sexual identity -and orientation. +## Table of Contents -### Our Standards +- [How to Interact With This Project](#how-to-interact-with-this-project) +- [1. Understanding the Development Model](#1-understanding-the-development-model) +- [2. Getting the Code](#2-getting-the-code) +- [3. Setting Up the Development Environment](#3-setting-up-the-development-environment) +- [4. Creating a Branch](#4-creating-a-branch) +- [5. Implementing Your Changes](#5-implementing-your-changes) +- [6. Code Quality Checks](#6-code-quality-checks) +- [7. Opening a Pull Request](#7-opening-a-pull-request) +- [8. Continuous Integration (CI)](#8-continuous-integration-ci) +- [9. Code Review](#9-code-review) +- [10. Documentation Contributions](#10-documentation-contributions) +- [11. Reporting Issues](#11-reporting-issues) +- [12. Security Issues](#12-security-issues) +- [13. Releases](#13-releases) -Examples of behavior that contributes to creating a positive environment -include: +--- -- Being respectful of differing viewpoints and experiences -- Gracefully accepting constructive criticism -- Focusing on what is best for the community +## How to Interact With This Project -Examples of unacceptable behavior by participants include: +If you are not planning to contribute code, you may want to: -- Trolling, insulting/derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or electronic - address, without explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting +- 🐞 Report a bug — see [Reporting Issues](#11-reporting-issues) +- 🛡 Report a security issue — see + [Security Issues](#12-security-issues) +- 💬 Ask a question or start a discussion at + [Project Discussions](https://github.com/easyscience/diffraction-lib/discussions) -### Our Responsibilities +If you plan to contribute code or documentation, continue below. -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. +--- -Project maintainers have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, or to ban temporarily or permanently any -contributor for other behaviors that they deem inappropriate, threatening, -offensive, or harmful. +## 1. Understanding the Development Model -### Scope +Before you start coding, it is important to understand how development +works in this project. -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. +### Branching Strategy -### Enforcement +We use the following branches: -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at suport@easydiffraction.org. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an -incident. Further details of specific enforcement policies may be posted -separately. +- `master` — stable releases only +- `develop` — active development branch +- Short-lived branches — feature or fix branches created for a single + contribution and deleted after merge -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. +> [!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 -### Attribution +See ADR easyscience/.github#12 for more details on the branching +strategy. -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 1.4, available at [http://contributor-covenant.org/version/1/4][version] +--- -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ +## 2. Getting the Code + +### 2.1. If You Are an External Contributor + +If you are not a core maintainer of this repository, follow these steps. + +1. Open the repository page: + `https://github.com/easyscience/diffraction-lib` + +2. Click the **Fork** button (top-right corner). This creates your own + copy of the repository. + +3. Clone your fork locally: + + ```bash + git clone https://github.com//diffraction-lib.git + cd diffraction-lib + ``` + +4. Add the original repository as `upstream`: + + ```bash + git remote add upstream https://github.com/easyscience/diffraction-lib.git + ``` + +5. Switch to the `develop` branch and update it: + + ```bash + git fetch upstream + git checkout develop + git pull upstream develop + ``` + +If you have contributed before, make sure your local `develop` branch is +up to date before starting new work. You can update it with: + +```bash +git fetch upstream +git pull upstream develop +``` + +This ensures you are working on the latest version of the project. + +### 2.2. If You Are a Core Team Member + +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. + +--- + +## 3. Setting Up the Development Environment + +You need: + +- Git +- Pixi + +EasyScience projects use **Pixi** to manage the development environment. + +To install Pixi, follow the official instructions: +https://pixi.prefix.dev/latest/installation/ + +You do **not** need to manually install Python. Pixi automatically: + +- Creates the correct Python environment +- Installs all required dependencies +- Installs development tools (linters, formatters, test tools) + +Set up the environment: + +```bash +pixi 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 using Pixi for +development. + +--- + +## 4. Creating a Branch + +Never work directly on `develop`. + +Create a new branch: + +```bash +git checkout -b my-change develop +``` + +> [!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. + +--- + +## 5. Implementing Your Changes + +While developing, make small, logical commits with clear messages. + +Example: + +```bash +git add . +git commit -m "Improve performance of time integrator for large systems" +``` + +--- + +## 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: + +- 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 license-check.........................Passed +pixi run py-lint-check.........................Passed +pixi run py-format-check.......................Passed +pixi run docstring-lint-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, to run only unit tests: + +```bash +pixi run unit-tests +``` + +or to run only Python linting checks: + +```bash +pixi run py-lint-check +``` + +Some formatting issues can be fixed automatically: + +```bash +pixi run fix +``` + +If everything is correctly formatted, you will see: + +```text +✅ All auto-formatting steps completed successfully! +``` + +This indicates that the auto-formatting pipeline completed successfully. +If you do not see this message and no error messages appear, try running +the command again. + +If errors are reported, resolve them and re-run: + +```bash +pixi run check +``` + +> [!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. + +--- + +## 7. Opening a Pull Request + +Push your branch: + +```bash +git push origin my-change +``` + +On GitHub: + +- Click **Compare & Pull Request** +- Ensure the base branch is `develop` +- Write a clear and concise title +- Add a description explaining what changed and why +- Add the required `[scope]` label + +### Pull Request Title + +> [!IMPORTANT] +> +> The PR title appears in release notes and changelogs. It should be +> concise and informative. + +Good examples: + +- Improve performance of time integrator for large systems +- Fix incorrect boundary condition handling in solver +- Add adaptive step-size control to ODE solver +- Add tutorial for custom model configuration +- Refactor solver API for improved readability + +### Required `[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 | +| ----------------------- | ----------------------------------------------------------------------- | +| `[scope] bug` | Bug report or fix (major.minor.**PATCH**) | +| `[scope] documentation` | Documentation-only changes (major.minor.patch.**POST**) | +| `[scope] enhancement` | Adds or improves features (major.**MINOR**.patch) | +| `[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 more details on the standardized +labeling scheme. + +--- + +## 8. Continuous Integration (CI) + +After opening a Pull Request: + +- Automated checks run automatically +- You will see green checkmarks or red crosses + +If checks fail: + +1. Open the failing check +2. Read the logs +3. Fix the issue locally +4. Run `pixi run check` +5. Push your changes + +The Pull Request updates automatically. + +--- + +## 9. Code Review + +All Pull Requests are reviewed by at least one core team member. + +Code review is collaborative and aims to improve quality. + +Do not take comments personally — they are meant to help. + +To update your PR: + +```bash +git add . +git commit -m "Address review comments" +git push +``` + +--- + +## 10. Documentation Contributions + +> [!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: + +- API documentation +- Examples +- Tutorials +- Jupyter notebooks + +Preview documentation locally: + +```bash +pixi run docs-serve +``` + +Open the URL shown in the terminal to review your changes. + +--- + +## 11. Reporting Issues + +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, screenshots, and environment details. + +Clear and detailed reports help maintainers investigate and resolve +issues more effectively. + +--- + +## 12. Security Issues + +> [!IMPORTANT] +> +> Please do **not** report security vulnerabilities publicly. + +If you discover a potential vulnerability, please contact the +maintainers privately so the issue can be investigated and addressed +responsibly. + +--- + +## 13. Releases + +Once your contribution is merged into `develop`, it will eventually 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. + +--- + +Thank you for contributing to EasyDiffraction and the EasyScience +ecosystem! diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md deleted file mode 100644 index 2acffeb3..00000000 --- a/DEVELOPMENT.md +++ /dev/null @@ -1,150 +0,0 @@ -# Development - -This is an example of a workflow that describes the development process. - -## Installation and setup with Pixi - -- Install Pixi by following the instructions on the - [official Pixi Installation Guide](https://pixi.sh/latest/installation). -- Clone repositories with assets for building documentation - ```bash - git clone https://github.com/easyscience/assets-docs.git - git clone https://github.com/easyscience/assets-branding.git - ``` -- Clone EasyDiffraction library repository - ```bash - git clone https://github.com/easyscience/diffraction-lib - ``` -- Go to the cloned directory - ```bash - cd diffraction-lib - ``` -- Create the environment defined in `pixi.toml` and install all necessary - dependencies: - ```bash - pixi install - ``` -- Install and setup development dependencies - ```bash - pixi run dev - ``` - -## Making changes - -- Checkout/switch to the `develop` branch - ```bash - git checkout develop - ``` -- Create a new branch from the `develop` one - ```bash - git checkout -b new-feature - ``` -- Make changes in the code - ```bash - ... - ``` - -## Checking code quality and testing - -### Pre-commit checks - -- Check code quality (configuration is in `pyproject.toml` and - `prettierrc.toml`) - ```bash - pixi run pre-commit-check - ``` -- Fix some code quality issues automatically - ```bash - pixi run pre-commit-fix - ``` - -### Pre-push checks - -- Run tests and checks before pushing changes - ```bash - pixi run pre-push - ``` - -### Individual tests and checks (if needed) - -- Check coverage by tests and docstrings - ```bash - pixi run cov - ``` -- Run unit tests - ```bash - pixi run unit-tests - ``` -- Run integration tests - ```bash - pixi run integration-tests - ``` -- Test tutorials as python scripts - ```bash - pixi run script-tests - ``` -- Convert tutorial scripts to notebooks - ```bash - pixi run notebook-prepare - ``` -- Test tutorials as notebooks - ```bash - pixi run notebook-tests - ``` - -## Building and checking documentation with MkDocs - -- Move notebooks to docs/tutorials - ```bash - pixi run docs-notebooks - ``` -- Add extra files to build documentation (from `../assets-docs/` and - `../assets-branding/` directories) - ```bash - pixi run docs-assets - ``` -- Create mkdocs.yml file - ```bash - pixi run docs-config - ``` -- Build documentation - ```bash - pixi run docs-build - ``` -- Test the documentation locally (built in the `site/` directory). E.g., on - macOS, open the site in the default browser via the terminal - ```bash - open http://127.0.0.1:8000 - ``` -- Clean up after checking documentation - ```bash - pixi run docs-clean - ``` - -## Committing and pushing changes - -- Commit changes - ```bash - git add . - git commit -m "Add new feature" - ``` -- Push the new branch to a remote repository - ```bash - git push -u origin new-feature - ``` -- Create a pull request on - [EasyScience GitHub repository](https://github.com/easyscience/diffraction-lib/pulls) - and request a review from team members -- Add one of the required labels: - - `[maintainer] auto-pull-request` - - `[scope] bug` - - `[scope] documentation` - - `[scope] enhancement` - - `[scope] maintenance` - - `[scope] significant` -- After approval, merge the pull request into the `develop` branch using "Squash - and merge" option -- Delete the branch remotely - ```bash - git push origin --delete new-feature - ``` diff --git a/LICENSE b/LICENSE index 4debe9f4..c4e3e48e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2021-2025 EasyDiffraction Python Library contributors +Copyright (c) 2021-2026 EasyScience contributors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README.md b/README.md index 6278d78f..a9fccc67 100644 --- a/README.md +++ b/README.md @@ -9,45 +9,45 @@

-**EasyDiffraction** is a Python package for calculating neutron powder -diffraction patterns based on a structural model and refining its parameters -against experimental data. +**EasyDiffraction** is a software for calculating neutron powder +diffraction patterns based on a structural model and refining its +parameters against experimental data. -**EasyDiffraction** is built upon the [EasyScience] framework, which provides -essential tools for developing scientific libraries and applications. + -## Useful Links - -- [Main Website] - Learn more about EasyDiffraction. -- [Documentation] - Access the full documentation. -- [Discussions] - Ask questions or share ideas. -- [Issue Tracker] - Report bugs or request new features. -- [Source Code] - Explore the source code repository. - -## Contributing +**EasyDiffraction** is developed both as a Python library and as a +cross-platform desktop application. -We welcome contributions! Our vision is for **EasyDiffraction** to be a -community-driven, open-source project supported by a diverse group of -contributors. +Here, we focus on the Python library. For the graphical user interface +(GUI), please see the corresponding +[GUI resources](https://github.com/easyscience/diffraction-app). -The project is currently maintained by the [European Spallation Source (ESS)]. +License: +[BSD 3-Clause](https://github.com/easyscience/diffraction-lib/blob/master/LICENSE) -If you'd like to contribute, please refer to our [Contributing Guidelines] for -information about our code of conduct and instructions on submitting pull -requests. - -## License - -**EasyDiffraction** is licensed under the [BSD 3-Clause License]. +## Useful Links - -[BSD 3-Clause License]: https://github.com/easyscience/diffraction-lib/blob/master/LICENSE -[Contributing Guidelines]: https://github.com/easyscience/diffraction-lib/blob/master/CONTRIBUTING.md -[EasyScience]: https://easyscience.software -[European Spallation Source (ESS)]: https://ess.eu -[Main Website]: https://easydiffraction.org -[Documentation]: https://docs.easydiffraction.org/lib -[Discussions]: https://github.com/easyscience/diffraction-lib/discussions -[Issue Tracker]: https://github.com/easyscience/diffraction-lib/issues -[Source Code]: https://github.com/easyscience/diffraction-lib - +### For Users + +- 📖 + [Documentation](https://easyscience.github.io/diffraction-lib/latest) +- 🚀 + [Getting Started](https://easyscience.github.io/diffraction-lib/latest/introduction) +- 🧪 + [Tutorials](https://easyscience.github.io/diffraction-lib/latest/tutorials) +- 💬 + [Get in Touch](https://easyscience.github.io/diffraction-lib/latest/introduction/#get-in-touch) +- 🧾 + [Citation](https://easyscience.github.io/diffraction-lib/latest/introduction/#citation) + +### For Contributors + +- 🧑‍💻 [Source Code](https://github.com/easyscience/diffraction-lib) +- 🐞 + [Issue Tracker](https://github.com/easyscience/diffraction-lib/issues) +- 💡 + [Discussions](https://github.com/easyscience/diffraction-lib/discussions) +- 🤝 + [Contributing Guide](https://github.com/easyscience/diffraction-lib/blob/master/CONTRIBUTING.md) +- 🛡 + [Code of Conduct](https://github.com/easyscience/.github/blob/master/CODE_OF_CONDUCT.md) diff --git a/codecov.yml b/codecov.yml index ffe29da9..f62b13ab 100644 --- a/codecov.yml +++ b/codecov.yml @@ -11,8 +11,3 @@ coverage: default: # Require patch coverage but with threshold threshold: 1% - -ignore: - # Vendored third-party code - not our code to test - - 'src/easydiffraction/utils/_vendored/**' - - 'src/easydiffraction/utils/_vendored/jupyter_dark_detect/**' diff --git a/docs/architecture/architecture.md b/docs/architecture/architecture.md index 3770f3ea..884ed4f1 100644 --- a/docs/architecture/architecture.md +++ b/docs/architecture/architecture.md @@ -8,11 +8,11 @@ ## 1. Overview -EasyDiffraction is a Python library for crystallographic diffraction analysis -(Rietveld refinement, pair-distribution-function fitting, etc.). It models the -domain using **CIF-inspired abstractions** — datablocks, categories, and -parameters — while providing a high-level, user-friendly API through a single -`Project` façade. +EasyDiffraction is a Python library for crystallographic diffraction +analysis (Rietveld refinement, pair-distribution-function fitting, +etc.). It models the domain using **CIF-inspired abstractions** — +datablocks, categories, and parameters — while providing a high-level, +user-friendly API through a single `Project` façade. ### 1.1 Supported Experiment Dimensions @@ -25,8 +25,8 @@ Every experiment is fully described by four orthogonal axes: | Beam mode | constant wavelength, time-of-flight | `BeamModeEnum` | | Radiation probe | neutron, X-ray | `RadiationProbeEnum` | -> **Planned extensions:** 1D / 2D data dimensionality, polarised / unpolarised -> neutron beam. +> **Planned extensions:** 1D / 2D data dimensionality, polarised / +> unpolarised neutron beam. ### 1.2 Calculation Engines @@ -56,50 +56,56 @@ GuardedBase # Controlled attribute access, parent lin └── DatablockItem # CIF data block (e.g. Structure, Experiment) ``` -`CollectionBase` provides a unified dict-like API over an ordered item list with -name-based indexing. All key operations — `__getitem__`, `__setitem__`, -`__delitem__`, `__contains__`, `remove()` — resolve keys through a single -`_key_for(item)` method that returns `category_entry_name` for category items or -`datablock_entry_name` for datablock items. Subclasses `CategoryCollection` and +`CollectionBase` provides a unified dict-like API over an ordered item +list with name-based indexing. All key operations — `__getitem__`, +`__setitem__`, `__delitem__`, `__contains__`, `remove()` — resolve keys +through a single `_key_for(item)` method that returns +`category_entry_name` for category items or `datablock_entry_name` for +datablock items. Subclasses `CategoryCollection` and `DatablockCollection` inherit this consistently. ### 2.2 GuardedBase — Controlled Attribute Access -`GuardedBase` is the root ABC. It enforces that only **declared `@property` -attributes** are accessible publicly: +`GuardedBase` is the root ABC. It enforces that only **declared +`@property` attributes** are accessible publicly: -- **`__getattr__`** rejects any attribute not declared as a `@property` on the - class hierarchy. Shows diagnostics with closest-match suggestions on typos. +- **`__getattr__`** rejects any attribute not declared as a `@property` + on the class hierarchy. Shows diagnostics with closest-match + suggestions on typos. - **`__setattr__`** distinguishes: - **Private** (`_`-prefixed) — always allowed, no diagnostics. - - **Read-only public** (property without setter) — blocked with a clear error. - - **Writable public** (property with setter) — goes through the property - setter, which is where validation happens. - - **Unknown** — blocked with diagnostics showing allowed writable attrs. -- **Parent linkage** — when a `GuardedBase` child is assigned to another, the - child's `_parent` is set automatically, forming an implicit ownership tree. -- **Identity** — every instance gets an `_identity: Identity` object for lazy - CIF-style name resolution (`datablock_entry_name`, `category_code`, - `category_entry_name`) by walking the `_parent` chain. - -**Key design rule:** if a parameter has a public setter, it is writable for the -user. If only a getter — it is read-only. If internal code needs to set it, a -private method (underscore prefix) is used. See § 2.2.1 below for the full -pattern. + - **Read-only public** (property without setter) — blocked with a + clear error. + - **Writable public** (property with setter) — goes through the + property setter, which is where validation happens. + - **Unknown** — blocked with diagnostics showing allowed writable + attrs. +- **Parent linkage** — when a `GuardedBase` child is assigned to + another, the child's `_parent` is set automatically, forming an + implicit ownership tree. +- **Identity** — every instance gets an `_identity: Identity` object for + lazy CIF-style name resolution (`datablock_entry_name`, + `category_code`, `category_entry_name`) by walking the `_parent` + chain. + +**Key design rule:** if a parameter has a public setter, it is writable +for the user. If only a getter — it is read-only. If internal code needs +to set it, a private method (underscore prefix) is used. See § 2.2.1 +below for the full pattern. #### 2.2.1 Public Property Convention — Editable vs Read-Only -Every public parameter or descriptor exposed on a `GuardedBase` subclass follows -one of two patterns: +Every public parameter or descriptor exposed on a `GuardedBase` subclass +follows one of two patterns: | Kind | Getter | Setter | Internal mutation | | ------------- | ------ | ------ | ---------------------------------- | | **Editable** | yes | yes | Via the public setter | | **Read-only** | yes | no | Via a private `_set_` method | -**Editable property** — the user can both read and write the value. The setter -runs through `GuardedBase.__setattr__` and into the property setter, where -validation happens: +**Editable property** — the user can both read and write the value. The +setter runs through `GuardedBase.__setattr__` and into the property +setter, where validation happens: ```python @property @@ -113,11 +119,11 @@ def name(self, new: str) -> None: self._name = new ``` -**Read-only property** — the user can read but cannot assign. Any attempt to set -the attribute is blocked by `GuardedBase.__setattr__` with a clear error -message. If _internal_ code (factory builders, CIF loaders, etc.) needs to set -the value, it calls a private `_set_` method instead of exposing a public -setter: +**Read-only property** — the user can read but cannot assign. Any +attempt to set the attribute is blocked by `GuardedBase.__setattr__` +with a clear error message. If _internal_ code (factory builders, CIF +loaders, etc.) needs to set the value, it calls a private `_set_` +method instead of exposing a public setter: ```python @property @@ -133,12 +139,13 @@ def _set_sample_form(self, value: str) -> None: **Why this matters:** -- `GuardedBase.__setattr__` uses the presence of a setter to decide writability. - Adding a setter "just for internal use" would open the attribute to users. +- `GuardedBase.__setattr__` uses the presence of a setter to decide + writability. Adding a setter "just for internal use" would open the + attribute to users. - Private `_set_` methods keep the public API surface minimal and intention-clear, while remaining greppable and type-safe. -- The pattern avoids string-based dispatch — every mutator has an explicit named - method. +- The pattern avoids string-based dispatch — every mutator has an + explicit named method. ### 2.3 CategoryItem and CategoryCollection @@ -153,8 +160,8 @@ def _set_sample_form(self, value: str) -> None: | Display | `show()` on concrete subclasses | `show()` on concrete subclasses | | Building items | N/A | `add(item)`, `create(**kwargs)` | -**Update priority:** lower values run first. This ensures correct execution -order within a datablock (e.g. background before data). +**Update priority:** lower values run first. This ensures correct +execution order within a datablock (e.g. background before data). ### 2.4 DatablockItem and DatablockCollection @@ -170,8 +177,9 @@ order within a datablock (e.g. background before data). | Dirty flag | `_need_categories_update` | N/A | When any `Parameter.value` is set, it propagates -`_need_categories_update = True` up to the owning `DatablockItem`. Serialisation -(`as_cif`) and plotting trigger `_update_categories()` if the flag is set. +`_need_categories_update = True` up to the owning `DatablockItem`. +Serialisation (`as_cif`) and plotting trigger `_update_categories()` if +the flag is set. ### 2.5 Variable System — Parameters and Descriptors @@ -191,18 +199,19 @@ CIF-bound concrete classes add a `CifHandler` for serialisation: | `NumericDescriptor` | `GenericNumericDescriptor` | Read-only or writable number | | `Parameter` | `GenericParameter` | Fittable numeric value | -**Initialisation rule:** all Parameters/Descriptors are initialised with their -default values from `value_spec` (an `AttributeSpec`) **without any validation** -— we trust internal definitions. Changes go through public property setters, -which run both type and value validation. +**Initialisation rule:** all Parameters/Descriptors are initialised with +their default values from `value_spec` (an `AttributeSpec`) **without +any validation** — we trust internal definitions. Changes go through +public property setters, which run both type and value validation. -**Mixin safety:** Parameter/Descriptor classes must not have init arguments so -they can be used as mixins safely (e.g. `PdTofDataPointMixin`). +**Mixin safety:** Parameter/Descriptor classes must not have init +arguments so they can be used as mixins safely (e.g. +`PdTofDataPointMixin`). ### 2.6 Validation -`AttributeSpec` bundles `default`, `data_type`, `validator`, `allow_none`. -Validators include: +`AttributeSpec` bundles `default`, `data_type`, `validator`, +`allow_none`. Validators include: | Validator | Purpose | | --------------------- | -------------------------------------- | @@ -217,13 +226,14 @@ Validators include: ### 3.1 Experiment Type -An experiment's type is defined by the four enum axes and is **immutable after -creation**. This avoids the complexity of transforming all internal state when -the experiment type changes. The type is stored in an `ExperimentType` category -with four `StringDescriptor`s validated by `MembershipValidator`s. Public -properties are read-only; factory and CIF-loading code use private setters -(`_set_sample_form`, `_set_beam_mode`, `_set_radiation_probe`, -`_set_scattering_type`) during construction only. +An experiment's type is defined by the four enum axes and is **immutable +after creation**. This avoids the complexity of transforming all +internal state when the experiment type changes. The type is stored in +an `ExperimentType` category with four `StringDescriptor`s validated by +`MembershipValidator`s. Public properties are read-only; factory and +CIF-loading code use private setters (`_set_sample_form`, +`_set_beam_mode`, `_set_radiation_probe`, `_set_scattering_type`) during +construction only. ### 3.2 Experiment Hierarchy @@ -245,8 +255,8 @@ Each concrete experiment class carries: ### 3.3 Category Ownership -Every experiment owns its categories as private attributes with public read-only -or read-write properties: +Every experiment owns its categories as private attributes with public +read-only or read-write properties: ```python # Read-only — user cannot replace the object, only modify its contents @@ -265,9 +275,10 @@ experiment.excluded_regions_type = 'default' # triggers ExcludedRegionsFactory. experiment.linked_phases_type = 'default' # triggers LinkedPhasesFactory.create(...) ``` -**Type switching pattern:** `expt.background_type = 'chebyshev'` rather than -`expt.background.type = 'chebyshev'`. This keeps the API at the experiment level -and makes it clear that the entire category object is being replaced. +**Type switching pattern:** `expt.background_type = 'chebyshev'` rather +than `expt.background.type = 'chebyshev'`. This keeps the API at the +experiment level and makes it clear that the entire category object is +being replaced. --- @@ -286,8 +297,8 @@ A `Structure` contains three categories: - `SpaceGroup` — symmetry information (`CategoryItem`) - `AtomSites` — atomic positions collection (`CategoryCollection`) -Symmetry constraints (cell metric, atomic coordinates, ADPs) are applied via the -`crystallography` module during `_update_categories()`. +Symmetry constraints (cell metric, atomic coordinates, ADPs) are applied +via the `crystallography` module during `_update_categories()`. --- @@ -308,13 +319,13 @@ All factories inherit from `FactoryBase`, which provides: | Display | `show_supported(**filters)` | Pretty-print table of type + description | | Tag listing | `supported_tags()` | List of all registered tags | -Each `__init_subclass__` gives every factory its own independent `_registry` and -`_default_rules`. +Each `__init_subclass__` gives every factory its own independent +`_registry` and `_default_rules`. ### 5.2 Default Rules -`_default_rules` maps frozensets of `(axis_name, enum_value)` tuples to tag -strings (preferably enum values for type safety): +`_default_rules` maps frozensets of `(axis_name, enum_value)` tuples to +tag strings (preferably enum values for type safety): ```python class PeakFactory(FactoryBase): @@ -333,13 +344,14 @@ class PeakFactory(FactoryBase): } ``` -Resolution uses **largest-subset matching**: the rule whose frozenset is the -biggest subset of the given conditions wins. `frozenset()` acts as a universal -fallback. +Resolution uses **largest-subset matching**: the rule whose frozenset is +the biggest subset of the given conditions wins. `frozenset()` acts as a +universal fallback. ### 5.3 Metadata on Registered Classes -Every `@Factory.register`-ed class carries three frozen dataclass attributes: +Every `@Factory.register`-ed class carries three frozen dataclass +attributes: ```python @PeakFactory.register @@ -365,8 +377,9 @@ class CwlPseudoVoigt(PeakBase, CwlBroadeningMixin): ### 5.4 Registration Trigger -Concrete classes use `@Factory.register` decorators. To trigger registration, -each package's `__init__.py` must **explicitly import** every concrete class: +Concrete classes use `@Factory.register` decorators. To trigger +registration, each package's `__init__.py` must **explicitly import** +every concrete class: ```python # datablocks/experiment/categories/background/__init__.py @@ -397,12 +410,13 @@ from .line_segment import LineSegmentBackground | `CalculatorFactory` | Calculation engines | `CryspyCalculator`, `CrysfmlCalculator`, `PdffitCalculator` | | `MinimizerFactory` | Minimisers | `LmfitMinimizer`, `DfolsMinimizer`, … | -> **Note:** `ExperimentFactory` and `StructureFactory` are _builder_ factories -> with `from_cif_path`, `from_cif_str`, `from_data_path`, and `from_scratch` -> classmethods. `ExperimentFactory` inherits `FactoryBase` and uses `@register` -> on all four concrete experiment classes; `_resolve_class` looks up the -> registered class via `default_tag()` + `_supported_map()`. `StructureFactory` -> is a plain class without `FactoryBase` inheritance (only one structure type +> **Note:** `ExperimentFactory` and `StructureFactory` are _builder_ +> factories with `from_cif_path`, `from_cif_str`, `from_data_path`, and +> `from_scratch` classmethods. `ExperimentFactory` inherits +> `FactoryBase` and uses `@register` on all four concrete experiment +> classes; `_resolve_class` looks up the registered class via +> `default_tag()` + `_supported_map()`. `StructureFactory` is a plain +> class without `FactoryBase` inheritance (only one structure type > exists today). ### 5.6 Tag Naming Convention @@ -502,9 +516,9 @@ Tags are the user-facing identifiers for selecting types. They must be: | `lmfit (least_squares)` | `LmfitMinimizer` (method=`least_squares`) | | `dfols` | `DfolsMinimizer` | -> **Note:** minimizer variant tags (`lmfit (leastsq)`, `lmfit (least_squares)`) -> are planned but not yet re-implemented after the `FactoryBase` migration. See -> `issues_open.md` for details. +> **Note:** minimizer variant tags (`lmfit (leastsq)`, +> `lmfit (least_squares)`) are planned but not yet re-implemented after +> the `FactoryBase` migration. See `issues_open.md` for details. ### 5.7 Metadata Classification — Which Classes Get What @@ -514,16 +528,17 @@ Tags are the user-facing identifiers for selecting types. They must be: > `compatibility`, and `calculator_support`.** > > **If a `CategoryItem` only exists as a child row inside a -> `CategoryCollection`, it does NOT get these attributes — the collection -> does.** +> `CategoryCollection`, it does NOT get these attributes — the +> collection does.** #### Rationale -A `LineSegment` item (a single background control point) is never selected, -created, or queried by a factory. It is always instantiated internally by its -parent `LineSegmentBackground` collection. The meaningful unit of selection is -the _collection_, not the item. The user picks "line-segment background" (the -collection type), not individual line-segment points. +A `LineSegment` item (a single background control point) is never +selected, created, or queried by a factory. It is always instantiated +internally by its parent `LineSegmentBackground` collection. The +meaningful unit of selection is the _collection_, not the item. The user +picks "line-segment background" (the collection type), not individual +line-segment points. #### Singleton CategoryItems — factory-created (get all three) @@ -601,28 +616,32 @@ collection type), not individual line-segment points. ### 6.1 Calculator -The calculator performs the actual diffraction computation. It is attached -per-experiment on the `ExperimentBase` object. Each experiment auto-resolves its -calculator on first access based on the data category's `calculator_support` -metadata and `CalculatorFactory._default_rules`. The `CalculatorFactory` filters -its registry by `engine_imported` (whether the third-party library is available -in the environment). +The calculator performs the actual diffraction computation. It is +attached per-experiment on the `ExperimentBase` object. Each experiment +auto-resolves its calculator on first access based on the data +category's `calculator_support` metadata and +`CalculatorFactory._default_rules`. The `CalculatorFactory` filters its +registry by `engine_imported` (whether the third-party library is +available in the environment). The experiment exposes the standard switchable-category API: -- `calculator` — read-only property (lazy, auto-resolved on first access) +- `calculator` — read-only property (lazy, auto-resolved on first + access) - `calculator_type` — getter + setter -- `show_supported_calculator_types()` — filtered by data category support +- `show_supported_calculator_types()` — filtered by data category + support - `show_current_calculator_type()` ### 6.2 Minimiser -The minimiser drives the optimisation loop. `MinimizerFactory` creates instances -by tag (e.g. `'lmfit'`, `'dfols'`). +The minimiser drives the optimisation loop. `MinimizerFactory` creates +instances by tag (e.g. `'lmfit'`, `'dfols'`). ### 6.3 Fitter -`Fitter` wraps a minimiser instance and orchestrates the fitting workflow: +`Fitter` wraps a minimiser instance and orchestrates the fitting +workflow: 1. Collect `free_parameters` from structures + experiments. 2. Record start values. @@ -634,10 +653,12 @@ by tag (e.g. `'lmfit'`, `'dfols'`). `Analysis` is bound to a `Project` and provides the high-level API: -- Minimiser selection: `current_minimizer`, `show_available_minimizers()` -- Fit mode: `fit_mode` (`CategoryItem` with a `mode` descriptor validated by - `FitModeEnum`); `'single'` fits each experiment independently, `'joint'` fits - all simultaneously with weights from `joint_fit_experiments`. +- Minimiser selection: `current_minimizer`, + `show_available_minimizers()` +- Fit mode: `fit_mode` (`CategoryItem` with a `mode` descriptor + validated by `FitModeEnum`); `'single'` fits each experiment + independently, `'joint'` fits all simultaneously with weights from + `joint_fit_experiments`. - Joint-fit weights: `joint_fit_experiments` (`CategoryCollection` of per-experiment weight entries); sibling of `fit_mode`, not a child. - Parameter tables: `show_all_params()`, `show_fittable_params()`, @@ -845,26 +866,26 @@ project.experiments['xray_pdf'].peak_profile_type = 'gaussian-damped-sinc' ### 9.1 Naming and CIF Conventions -- Follow CIF naming conventions where possible. Deviate for better API design - when necessary, but keep the spirit of CIF names. +- Follow CIF naming conventions where possible. Deviate for better API + design when necessary, but keep the spirit of CIF names. - Reuse the concept of datablocks and categories from CIF. -- `DatablockItem` = one CIF `data_` block, `DatablockCollection` = set of - blocks. +- `DatablockItem` = one CIF `data_` block, `DatablockCollection` = set + of blocks. - `CategoryItem` = one CIF category, `CategoryCollection` = CIF loop. ### 9.2 Immutability of Experiment Type -The experiment type (the four enum axes) can only be set at creation time. It -cannot be changed afterwards. This avoids the complexity of maintaining -different state transformations when switching between fundamentally different -experiment configurations. +The experiment type (the four enum axes) can only be set at creation +time. It cannot be changed afterwards. This avoids the complexity of +maintaining different state transformations when switching between +fundamentally different experiment configurations. ### 9.3 Category Type Switching -In contrast to experiment type, categories that have multiple implementations -(peak profiles, backgrounds, instruments) can be switched at runtime by the -user. The API pattern uses a type property on the **experiment**, not on the -category itself: +In contrast to experiment type, categories that have multiple +implementations (peak profiles, backgrounds, instruments) can be +switched at runtime by the user. The API pattern uses a type property on +the **experiment**, not on the category itself: ```python # ✅ Correct — type property on the experiment @@ -874,16 +895,16 @@ expt.background_type = 'chebyshev' expt.background.type = 'chebyshev' ``` -This makes it clear that the entire category object is being replaced and -simplifies maintenance. +This makes it clear that the entire category object is being replaced +and simplifies maintenance. ### 9.4 Switchable-Category Convention -Categories whose concrete implementation can be swapped at runtime (background, -peak profile, etc.) are called **switchable categories**. **Every category must -be factory-based** — even if only one implementation exists today. This ensures -a uniform API, consistent discoverability, and makes adding a second -implementation trivial. +Categories whose concrete implementation can be swapped at runtime +(background, peak profile, etc.) are called **switchable categories**. +**Every category must be factory-based** — even if only one +implementation exists today. This ensures a uniform API, consistent +discoverability, and makes adding a second implementation trivial. | Facet | Naming pattern | Example | | --------------- | -------------------------------------------- | ------------------------------------------------ | @@ -894,26 +915,31 @@ implementation trivial. The convention applies universally: -- **Experiment:** `calculator_type`, `background_type`, `peak_profile_type`, - `extinction_type`, `linked_crystal_type`, `excluded_regions_type`, - `linked_phases_type`, `instrument_type`, `data_type`. +- **Experiment:** `calculator_type`, `background_type`, + `peak_profile_type`, `extinction_type`, `linked_crystal_type`, + `excluded_regions_type`, `linked_phases_type`, `instrument_type`, + `data_type`. - **Structure:** `cell_type`, `space_group_type`, `atom_sites_type`. - **Analysis:** `aliases_type`, `constraints_type`, `fit_mode_type`, `joint_fit_experiments_type`. **Design decisions:** -- The **experiment owns** the `_type` setter because switching replaces the - entire category object (`self._background = BackgroundFactory.create(...)`). -- The **experiment owns** the `show_*` methods because they are one-liners that - delegate to `Factory.show_supported(...)` and can pass experiment-specific - context (e.g. `scattering_type`, `beam_mode` for peak filtering). -- Concrete category subclasses provide a public `show()` method for displaying - the current content (not on the base `CategoryItem`/`CategoryCollection`). +- The **experiment owns** the `_type` setter because switching replaces + the entire category object + (`self._background = BackgroundFactory.create(...)`). +- The **experiment owns** the `show_*` methods because they are + one-liners that delegate to `Factory.show_supported(...)` and can pass + experiment-specific context (e.g. `scattering_type`, `beam_mode` for + peak filtering). +- Concrete category subclasses provide a public `show()` method for + displaying the current content (not on the base + `CategoryItem`/`CategoryCollection`). ### 9.5 Discoverable Supported Options -The user can always discover what is supported for the current experiment: +The user can always discover what is supported for the current +experiment: ```python expt.show_supported_peak_profile_types() @@ -935,36 +961,38 @@ project.analysis.show_supported_joint_fit_experiments_types() project.analysis.show_available_minimizers() ``` -Available calculators are filtered by `engine_imported` (whether the library is -installed) and by the experiment's data category `calculator_support` metadata. +Available calculators are filtered by `engine_imported` (whether the +library is installed) and by the experiment's data category +`calculator_support` metadata. ### 9.6 Enums for Finite Value Sets -Every attribute, descriptor, or configuration option that accepts a **finite, -closed set of values** must be represented by a `(str, Enum)` class. This -applies to: +Every attribute, descriptor, or configuration option that accepts a +**finite, closed set of values** must be represented by a `(str, Enum)` +class. This applies to: - Factory tags (§5.6) — e.g. `PeakProfileTypeEnum`, `CalculatorEnum`. - Experiment-axis values — e.g. `SampleFormEnum`, `BeamModeEnum`. - Category descriptors with enumerated choices — e.g. fit mode (`FitModeEnum.SINGLE`, `FitModeEnum.JOINT`). -The enum serves as the **single source of truth** for valid values, their -user-facing string representations, and their descriptions. Benefits: +The enum serves as the **single source of truth** for valid values, +their user-facing string representations, and their descriptions. +Benefits: -- **Autocomplete and typo safety** — IDEs list valid members; misspellings are - caught at assignment time. -- **Greppable** — searching for `FitModeEnum.JOINT` finds every code path that - handles joint fitting. -- **Type-safe dispatch** — `if mode == FitModeEnum.JOINT:` is checked by type - checkers; `if mode == 'joint':` is not. -- **Consistent validation** — use `MembershipValidator` with the enum members - instead of `RegexValidator` with hand-written patterns. +- **Autocomplete and typo safety** — IDEs list valid members; + misspellings are caught at assignment time. +- **Greppable** — searching for `FitModeEnum.JOINT` finds every code + path that handles joint fitting. +- **Type-safe dispatch** — `if mode == FitModeEnum.JOINT:` is checked by + type checkers; `if mode == 'joint':` is not. +- **Consistent validation** — use `MembershipValidator` with the enum + members instead of `RegexValidator` with hand-written patterns. -**Rule:** internal code must compare against enum members, never raw strings. -User-facing setters accept either the enum member or its string value (because -`str(EnumMember) == EnumMember.value` for `(str, Enum)`), but internal dispatch -always uses the enum: +**Rule:** internal code must compare against enum members, never raw +strings. User-facing setters accept either the enum member or its string +value (because `str(EnumMember) == EnumMember.value` for `(str, Enum)`), +but internal dispatch always uses the enum: ```python # ✅ Correct — compare with enum @@ -976,10 +1004,10 @@ if self._fit_mode.mode.value == 'joint': ### 9.7 Flat Category Structure — No Nested Categories -Following CIF conventions, categories are **flat siblings** within their owner -(datablock or analysis object). A category must never be a child of another -category of a different type. Categories can reference each other via IDs, but -the ownership hierarchy is always: +Following CIF conventions, categories are **flat siblings** within their +owner (datablock or analysis object). A category must never be a child +of another category of a different type. Categories can reference each +other via IDs, but the ownership hierarchy is always: ``` Owner (DatablockItem / Analysis) @@ -999,7 +1027,8 @@ Owner **Example — `fit_mode` and `joint_fit_experiments`:** `fit_mode` is a `CategoryItem` holding the active strategy (`'single'` or `'joint'`). `joint_fit_experiments` is a separate `CategoryCollection` holding -per-experiment weights. Both are direct children of `Analysis`, not nested: +per-experiment weights. Both are direct children of `Analysis`, not +nested: ```python # ✅ Correct — sibling categories on Analysis @@ -1022,13 +1051,80 @@ npd 0.7 xrd 0.3 ``` +### 9.8 Property Docstring and Type-Hint Template + +Every public property backed by a private `Parameter`, +`NumericDescriptor`, or `StringDescriptor` attribute must follow the +template below. The `description` field on the descriptor is the +**single source of truth**; docstrings and type hints are mechanically +derived from it. + +**Definitions:** + +| Symbol | Meaning | +| --------- | -------------------------------------------------------------------------------------- | +| `{desc}` | `description` string without trailing period | +| `{units}` | `units` string; omit the `({units})` parenthetical when absent/empty | +| `{Type}` | Descriptor class name: `Parameter`, `NumericDescriptor`, or `StringDescriptor` | +| `{ann}` | Setter value annotation: `float` for numeric descriptors, `str` for string descriptors | + +**Template — writable property:** + +```python +@property +def length_a(self) -> Parameter: + """Length of the a axis of the unit cell (Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ + return self._length_a + +@length_a.setter +def length_a(self, value: float) -> None: + self._length_a.value = value +``` + +**Template — read-only property:** + +```python +@property +def length_a(self) -> Parameter: + """Length of the a axis of the unit cell (Å). + + Reading this property returns the underlying ``Parameter`` + object. + """ + return self._length_a +``` + +**Quick-reference table:** + +| Element | Text | +| ---------------------- | -------------------------------------------------------------------------------------------------------------- | +| Getter summary line | `"""{desc} ({units}).` (or `"""{desc}.` when unitless) | +| Getter body (writable) | `Reading this property returns the underlying ``{Type}`` object. Assigning to it updates the parameter value.` | +| Getter body (readonly) | `Reading this property returns the underlying ``{Type}`` object.` | +| Setter docstring | _(none — not rendered by griffe / MkDocs)_ | +| Getter annotation | `-> {Type}` | +| Setter annotation | `value: {ann}` and `-> None` | + +**Notes:** + +- Getter docstrings have **no** `Args:` or `Returns:` sections. +- Setters have **no** docstring. +- Avoid markdown emphasis (`*a*`) in docstrings; use plain text to stay + in sync with the `description` field. +- The CI tool `pixi run param-consistency-check` validates compliance; + `pixi run param-consistency-fix` auto-fixes violations. + --- ## 10. Issues - **Open:** [`issues_open.md`](issues_open.md) — prioritised backlog. -- **Closed:** [`issues_closed.md`](issues_closed.md) — resolved items for - reference. +- **Closed:** [`issues_closed.md`](issues_closed.md) — resolved items + for reference. When a resolution affects the architecture described above, the relevant sections of this document are updated accordingly. diff --git a/docs/architecture/issues_closed.md b/docs/architecture/issues_closed.md index bc8ea19b..a67edcbe 100644 --- a/docs/architecture/issues_closed.md +++ b/docs/architecture/issues_closed.md @@ -6,26 +6,29 @@ Issues that have been fully resolved. Kept for historical reference. ## Dirty-Flag Guard Was Disabled -**Resolution:** added `_set_value_from_minimizer()` on `GenericDescriptorBase` -that writes `_value` directly (no validation) but sets the dirty flag on the -parent `DatablockItem`. Both `LmfitMinimizer` and `DfolsMinimizer` now use it. -The guard in `DatablockItem._update_categories()` is enabled and skips redundant -updates on the user-facing path (CIF export, plotting). During fitting the guard -is bypassed (`called_by_minimizer=True`) because experiment calculations depend -on structure parameters owned by a different `DatablockItem`. +**Resolution:** added `_set_value_from_minimizer()` on +`GenericDescriptorBase` that writes `_value` directly (no validation) +but sets the dirty flag on the parent `DatablockItem`. Both +`LmfitMinimizer` and `DfolsMinimizer` now use it. The guard in +`DatablockItem._update_categories()` is enabled and skips redundant +updates on the user-facing path (CIF export, plotting). During fitting +the guard is bypassed (`called_by_minimizer=True`) because experiment +calculations depend on structure parameters owned by a different +`DatablockItem`. --- ## Move Calculator from Global to Per-Experiment -**Resolution:** removed the global calculator from `Analysis`. Each experiment -now owns its calculator, auto-resolved on first access from -`CalculatorFactory._default_rules` (maps `scattering_type` → default tag) and -filtered by the data category's `calculator_support` metadata (e.g. `PdCwlData` -→ `{CRYSPY}`, `TotalData` → `{PDFFIT}`). Calculator classes no longer carry -`compatibility` attributes — limitations are expressed on categories. The -experiment exposes the standard switchable-category API: `calculator` -(read-only, lazy), `calculator_type` (getter + setter), +**Resolution:** removed the global calculator from `Analysis`. Each +experiment now owns its calculator, auto-resolved on first access from +`CalculatorFactory._default_rules` (maps `scattering_type` → default +tag) and filtered by the data category's `calculator_support` metadata +(e.g. `PdCwlData` → `{CRYSPY}`, `TotalData` → `{PDFFIT}`). Calculator +classes no longer carry `compatibility` attributes — limitations are +expressed on categories. The experiment exposes the standard +switchable-category API: `calculator` (read-only, lazy), +`calculator_type` (getter + setter), `show_supported_calculator_types()`, `show_current_calculator_type()`. Tutorials, tests, and docs updated. @@ -33,28 +36,32 @@ Tutorials, tests, and docs updated. ## Add Universal Factories for All Categories -**Resolution:** converted every category to use the `FactoryBase` pattern. Each -former single-file category is now a package with `factory.py` (trivial -`FactoryBase` subclass), `default.py` (concrete class with `@register` + -`type_info`), and `__init__.py` (re-exports preserving import compatibility). +**Resolution:** converted every category to use the `FactoryBase` +pattern. Each former single-file category is now a package with +`factory.py` (trivial `FactoryBase` subclass), `default.py` (concrete +class with `@register` + `type_info`), and `__init__.py` (re-exports +preserving import compatibility). -Experiment categories: `Extinction` → `ShelxExtinction` / `ExtinctionFactory` -(tag `shelx`), `LinkedCrystal` / `LinkedCrystalFactory` (tag `default`), -`ExcludedRegions` / `ExcludedRegionsFactory`, `LinkedPhases` / -`LinkedPhasesFactory`, `ExperimentType` / `ExperimentTypeFactory`. +Experiment categories: `Extinction` → `ShelxExtinction` / +`ExtinctionFactory` (tag `shelx`), `LinkedCrystal` / +`LinkedCrystalFactory` (tag `default`), `ExcludedRegions` / +`ExcludedRegionsFactory`, `LinkedPhases` / `LinkedPhasesFactory`, +`ExperimentType` / `ExperimentTypeFactory`. Structure categories: `Cell` / `CellFactory`, `SpaceGroup` / `SpaceGroupFactory`, `AtomSites` / `AtomSitesFactory`. Analysis categories: `Aliases` / `AliasesFactory`, `Constraints` / -`ConstraintsFactory`, `JointFitExperiments` / `JointFitExperimentsFactory`. - -`ShelxExtinction` and `LinkedCrystal` get the full switchable-category API on -`ScExperimentBase` (`extinction_type`, `linked_crystal_type` getter+setter, -`show_supported_*_types()`, `show_current_*_type()`). `ExcludedRegions` and -`LinkedPhases` get the same API on `PdExperimentBase`. `Cell`, `SpaceGroup`, and -`AtomSites` get it on `Structure`. `Aliases` and `Constraints` get it on -`Analysis`. Architecture §3.3, §5.5, §5.7, §9.4, §9.5 updated. Copilot -instructions updated with universal switchable-category scope and -architecture-first workflow rule. Unit tests extended with factory tests for -extinction and linked-crystal. +`ConstraintsFactory`, `JointFitExperiments` / +`JointFitExperimentsFactory`. + +`ShelxExtinction` and `LinkedCrystal` get the full switchable-category +API on `ScExperimentBase` (`extinction_type`, `linked_crystal_type` +getter+setter, `show_supported_*_types()`, `show_current_*_type()`). +`ExcludedRegions` and `LinkedPhases` get the same API on +`PdExperimentBase`. `Cell`, `SpaceGroup`, and `AtomSites` get it on +`Structure`. `Aliases` and `Constraints` get it on `Analysis`. +Architecture §3.3, §5.5, §5.7, §9.4, §9.5 updated. Copilot instructions +updated with universal switchable-category scope and architecture-first +workflow rule. Unit tests extended with factory tests for extinction and +linked-crystal. diff --git a/docs/architecture/issues_open.md b/docs/architecture/issues_open.md index d8bd37a2..05ed175f 100644 --- a/docs/architecture/issues_open.md +++ b/docs/architecture/issues_open.md @@ -1,9 +1,10 @@ # EasyDiffraction — Open Issues -Prioritised list of issues, improvements, and design questions to address. Items -are ordered by a combination of user impact, blocking potential, and -implementation readiness. When an item is fully implemented, remove it from this -file and update `architecture.md` if needed. +Prioritised list of issues, improvements, and design questions to +address. Items are ordered by a combination of user impact, blocking +potential, and implementation readiness. When an item is fully +implemented, remove it from this file and update `architecture.md` if +needed. **Legend:** 🔴 High · 🟡 Medium · 🟢 Low @@ -13,15 +14,16 @@ file and update `architecture.md` if needed. **Type:** Completeness -`save()` serialises all components to CIF files but `load()` is a stub that -raises `NotImplementedError`. Users cannot round-trip a project. +`save()` serialises all components to CIF files but `load()` is a stub +that raises `NotImplementedError`. Users cannot round-trip a project. **Why first:** this is the highest-severity gap. Without it the save -functionality is only half useful — CIF files are written but cannot be read -back. Tutorials that demonstrate save/load are blocked. +functionality is only half useful — CIF files are written but cannot be +read back. Tutorials that demonstrate save/load are blocked. -**Fix:** implement `load()` that reads CIF files from the project directory and -reconstructs structures, experiments, and analysis settings. +**Fix:** implement `load()` that reads CIF files from the project +directory and reconstructs structures, experiments, and analysis +settings. **Depends on:** nothing (standalone). @@ -35,11 +37,12 @@ After the `FactoryBase` migration only `'lmfit'` and `'dfols'` remain as registered tags. The ability to select a specific lmfit algorithm (e.g. `'lmfit (leastsq)'`, `'lmfit (least_squares)'`) raises a `ValueError`. -The root cause is that `FactoryBase` assumes one class ↔ one tag; registering -the same class twice with different constructor arguments is not supported. +The root cause is that `FactoryBase` assumes one class ↔ one tag; +registering the same class twice with different constructor arguments is +not supported. -**Fix:** decide on an approach (thin subclasses, extended registry, or two-level -selection) and implement. Thin subclasses is the quickest. +**Fix:** decide on an approach (thin subclasses, extended registry, or +two-level selection) and implement. Thin subclasses is the quickest. **Planned tags:** @@ -58,8 +61,8 @@ selection) and implement. Thin subclasses is the quickest. | **B. Extend registry to store `(class, kwargs)` tuples** | No extra classes; factory handles variants natively | `_supported_map` changes shape; `TypeInfo` moves from class attribute to registration-time data | | **C. Two-level selection** (`engine` + `algorithm`) | Clean separation; engine maps to class, algorithm is a constructor arg | More complex API (`current_minimizer = ('lmfit', 'least_squares')`); needs new `FactoryBase` protocol | -**Depends on:** nothing (standalone, but should be decided before more factories -adopt variants). +**Depends on:** nothing (standalone, but should be decided before more +factories adopt variants). --- @@ -67,13 +70,14 @@ adopt variants). **Type:** Fragility -`joint_fit_experiments` is created once when `fit_mode` becomes `'joint'`. If -experiments are added, removed, or renamed afterwards, the weight collection is -stale. Joint fitting can fail with missing keys or run with incorrect weights. +`joint_fit_experiments` is created once when `fit_mode` becomes +`'joint'`. If experiments are added, removed, or renamed afterwards, the +weight collection is stale. Joint fitting can fail with missing keys or +run with incorrect weights. -**Fix:** rebuild or validate `joint_fit_experiments` at the start of every joint -fit. At minimum, `fit()` should assert that the weight keys exactly match -`project.experiments.names`. +**Fix:** rebuild or validate `joint_fit_experiments` at the start of +every joint fit. At minimum, `fit()` should assert that the weight keys +exactly match `project.experiments.names`. **Depends on:** nothing. @@ -85,18 +89,20 @@ fit. At minimum, `fit()` should assert that the weight keys exactly match `ConstraintsHandler` is only synchronised from `analysis.aliases` and `analysis.constraints` when the user explicitly calls -`project.analysis.apply_constraints()`. The normal fit / serialisation path -calls `constraints_handler.apply()` directly, so newly added or edited aliases -and constraints can be ignored until that manual sync step happens. +`project.analysis.apply_constraints()`. The normal fit / serialisation +path calls `constraints_handler.apply()` directly, so newly added or +edited aliases and constraints can be ignored until that manual sync +step happens. -**Why high:** this produces silently incorrect results. A user can define -constraints, run a fit, and believe they were applied when the active singleton -still contains stale state from a previous run or no state at all. +**Why high:** this produces silently incorrect results. A user can +define constraints, run a fit, and believe they were applied when the +active singleton still contains stale state from a previous run or no +state at all. **Fix:** before any automatic constraint application, always refresh the -singleton from the current `Aliases` and `Constraints` collections. The sync -should happen inside `Analysis._update_categories()` or inside the constraints -category itself, not only in a user-facing helper method. +singleton from the current `Aliases` and `Constraints` collections. The +sync should happen inside `Analysis._update_categories()` or inside the +constraints category itself, not only in a user-facing helper method. **Depends on:** nothing. @@ -106,10 +112,11 @@ category itself, not only in a user-facing helper method. **Type:** Consistency -`Analysis` owns categories (`Aliases`, `Constraints`, `JointFitExperiments`) but -does not extend `DatablockItem`. Its ad-hoc `_update_categories()` iterates over -a hard-coded list and does not participate in standard category discovery, -parameter enumeration, or CIF serialisation. +`Analysis` owns categories (`Aliases`, `Constraints`, +`JointFitExperiments`) but does not extend `DatablockItem`. Its ad-hoc +`_update_categories()` iterates over a hard-coded list and does not +participate in standard category discovery, parameter enumeration, or +CIF serialisation. **Fix:** make `Analysis` extend `DatablockItem`, or extract a shared `_update_categories()` protocol. @@ -122,20 +129,22 @@ parameter enumeration, or CIF serialisation. **Type:** Correctness + Data safety -`Experiment.data_type` currently validates against all registered data tags -rather than only those compatible with the experiment's `sample_form` / -`scattering_type` / `beam_mode`. This allows users to switch an experiment to an -incompatible data collection class. The setter also replaces the existing data -object with a fresh empty instance, discarding loaded data without warning. +`Experiment.data_type` currently validates against all registered data +tags rather than only those compatible with the experiment's +`sample_form` / `scattering_type` / `beam_mode`. This allows users to +switch an experiment to an incompatible data collection class. The +setter also replaces the existing data object with a fresh empty +instance, discarding loaded data without warning. -**Why high:** the current API can create internally inconsistent experiments and -silently lose measured data, which is especially dangerous for notebook and -tutorial workflows. +**Why high:** the current API can create internally inconsistent +experiments and silently lose measured data, which is especially +dangerous for notebook and tutorial workflows. -**Fix:** filter supported data types through `DataFactory.supported_for(...)` -using the current experiment context, and warn or block when a switch would -discard existing data. If runtime data-type switching is not a real user need, -consider making `data` effectively fixed after experiment creation. +**Fix:** filter supported data types through +`DataFactory.supported_for(...)` using the current experiment context, +and warn or block when a switch would discard existing data. If runtime +data-type switching is not a real user need, consider making `data` +effectively fixed after experiment creation. **Depends on:** nothing. @@ -145,16 +154,17 @@ consider making `data` effectively fixed after experiment creation. **Type:** Fragility -Single-fit mode creates a throw-away `Experiments` collection per experiment, -manually forces `_parent` via `object.__setattr__`, and passes it to `Fitter`. -This bypasses `GuardedBase` parent tracking and is fragile. +Single-fit mode creates a throw-away `Experiments` collection per +experiment, manually forces `_parent` via `object.__setattr__`, and +passes it to `Fitter`. This bypasses `GuardedBase` parent tracking and +is fragile. -**Fix:** make `Fitter.fit()` accept a list of experiment objects (or a single -experiment) instead of requiring an `Experiments` collection. Or add a -`fit_single(experiment)` method. +**Fix:** make `Fitter.fit()` accept a list of experiment objects (or a +single experiment) instead of requiring an `Experiments` collection. Or +add a `fit_single(experiment)` method. -**Depends on:** nothing, but simpler after issue 5 (Analysis refactor) clarifies -the fitting orchestration. +**Depends on:** nothing, but simpler after issue 5 (Analysis refactor) +clarifies the fitting orchestration. --- @@ -162,14 +172,15 @@ the fitting orchestration. **Type:** API safety -`CategoryCollection.create(**kwargs)` accepts arbitrary keyword arguments and -applies them via `setattr`. Typos are silently dropped (GuardedBase logs a -warning but does not raise), so items are created with incorrect defaults. +`CategoryCollection.create(**kwargs)` accepts arbitrary keyword +arguments and applies them via `setattr`. Typos are silently dropped +(GuardedBase logs a warning but does not raise), so items are created +with incorrect defaults. -**Fix:** concrete collection subclasses (e.g. `AtomSites`, `Background`) should -override `create()` with explicit parameters for IDE autocomplete and typo -detection. The base `create(**kwargs)` remains as an internal implementation -detail. +**Fix:** concrete collection subclasses (e.g. `AtomSites`, `Background`) +should override `create()` with explicit parameters for IDE autocomplete +and typo detection. The base `create(**kwargs)` remains as an internal +implementation detail. **Depends on:** nothing. @@ -179,7 +190,8 @@ detail. **Type:** Design improvement -The four current experiment axes will be extended with at least two more: +The four current experiment axes will be extended with at least two +more: | New axis | Options | Enum (proposed) | | ------------------- | ---------------------- | ------------------------ | @@ -187,12 +199,12 @@ The four current experiment axes will be extended with at least two more: | Beam polarisation | unpolarised, polarised | `PolarisationEnum` | These should follow the same `str, Enum` pattern and integrate into -`Compatibility` (new `FrozenSet` fields), `_default_rules`, and `ExperimentType` -(new `StringDescriptor`s with `MembershipValidator`s). +`Compatibility` (new `FrozenSet` fields), `_default_rules`, and +`ExperimentType` (new `StringDescriptor`s with `MembershipValidator`s). -**Migration path:** existing `Compatibility` objects that don't specify the new -fields use `frozenset()` (empty = "any"), so all existing classes remain -compatible without changes. +**Migration path:** existing `Compatibility` objects that don't specify +the new fields use `frozenset()` (empty = "any"), so all existing +classes remain compatible without changes. **Depends on:** nothing. @@ -202,17 +214,18 @@ compatible without changes. **Type:** Maintainability -`Project._update_categories(expt_name)` hard-codes the update order (structures -→ analysis → one experiment). The `_update_priority` system exists on categories -but is not used across datablocks. The `expt_name` parameter means only one -experiment is updated per call, inconsistent with joint-fit workflows. +`Project._update_categories(expt_name)` hard-codes the update order +(structures → analysis → one experiment). The `_update_priority` system +exists on categories but is not used across datablocks. The `expt_name` +parameter means only one experiment is updated per call, inconsistent +with joint-fit workflows. -**Fix:** consider a project-level `_update_priority` on datablocks, or at -minimum document the required update order. For joint fitting, all experiments -should be updateable in a single call. +**Fix:** consider a project-level `_update_priority` on datablocks, or +at minimum document the required update order. For joint fitting, all +experiments should be updateable in a single call. -**Depends on:** benefits from issue 5 (Analysis as DatablockItem) and issue 7 -(fitter refactor). +**Depends on:** benefits from issue 5 (Analysis as DatablockItem) and +issue 7 (fitter refactor). --- @@ -220,17 +233,18 @@ should be updateable in a single call. **Type:** Maintainability -`_update()` is an optional override with a no-op default. A clearer contract -would help contributors: +`_update()` is an optional override with a no-op default. A clearer +contract would help contributors: -- **Active categories** (those that compute something, e.g. `Background`, - `Data`) should have an explicit `_update()` implementation. +- **Active categories** (those that compute something, e.g. + `Background`, `Data`) should have an explicit `_update()` + implementation. - **Passive categories** (those that only store parameters, e.g. `Cell`, `SpaceGroup`) keep the no-op default. The distinction is already implicit in the code; making it explicit in -documentation (and possibly via a naming convention or flag) would reduce -confusion for new contributors. +documentation (and possibly via a naming convention or flag) would +reduce confusion for new contributors. **Depends on:** nothing. @@ -240,10 +254,10 @@ confusion for new contributors. **Type:** Quality -Ensuring every parameter survives a `save()` → `load()` cycle is critical for -reproducibility. A systematic integration test that creates a project, populates -all categories, saves, reloads, and compares all parameter values would -strengthen confidence in the serialisation layer. +Ensuring every parameter survives a `save()` → `load()` cycle is +critical for reproducibility. A systematic integration test that creates +a project, populates all categories, saves, reloads, and compares all +parameter values would strengthen confidence in the serialisation layer. **Depends on:** issue 1 (`Project.load()` implementation). @@ -253,17 +267,18 @@ strengthen confidence in the serialisation layer. **Type:** Performance -Symmetry constraint application (cell metric, atomic coordinates, ADPs) goes -through the public `value` setter for each parameter, setting the dirty flag -repeatedly during what is logically a single batch operation. +Symmetry constraint application (cell metric, atomic coordinates, ADPs) +goes through the public `value` setter for each parameter, setting the +dirty flag repeatedly during what is logically a single batch operation. No correctness issue — the dirty-flag guard handles this correctly. The -redundant sets are a minor inefficiency that only matters if profiling shows it -is a bottleneck. +redundant sets are a minor inefficiency that only matters if profiling +shows it is a bottleneck. **Fix:** introduce a private `_set_value_no_notify()` method on -`GenericDescriptorBase` for internal batch operations, or a context manager / -flag on the owning datablock to suppress notifications during a batch. +`GenericDescriptorBase` for internal batch operations, or a context +manager / flag on the owning datablock to suppress notifications during +a batch. **Depends on:** nothing, but low priority. @@ -273,11 +288,12 @@ flag on the owning datablock to suppress notifications during a batch. **Type:** Performance -The current dirty-flag approach (`_need_categories_update` on `DatablockItem`) -triggers a full update of all categories when any parameter changes. This is -simple and correct. If performance becomes a concern with many categories, a -more granular approach could track which specific categories are dirty. Only -implement when profiling proves it is needed. +The current dirty-flag approach (`_need_categories_update` on +`DatablockItem`) triggers a full update of all categories when any +parameter changes. This is simple and correct. If performance becomes a +concern with many categories, a more granular approach could track which +specific categories are dirty. Only implement when profiling proves it +is needed. **Depends on:** nothing, but low priority. @@ -287,14 +303,16 @@ implement when profiling proves it is needed. **Type:** Correctness -Joint-fit weights currently allow invalid numeric values such as negatives or an -all-zero set. The residual code then normalises by the total weight and applies -`sqrt(weight)`, which can produce division-by-zero or `nan` residuals. +Joint-fit weights currently allow invalid numeric values such as +negatives or an all-zero set. The residual code then normalises by the +total weight and applies `sqrt(weight)`, which can produce +division-by-zero or `nan` residuals. -**Fix:** require weights to be strictly positive, or at minimum validate that -all weights are non-negative and their total is greater than zero before -normalisation. This should fail with a clear user-facing error instead of -letting invalid floating-point values propagate into the minimiser. +**Fix:** require weights to be strictly positive, or at minimum validate +that all weights are non-negative and their total is greater than zero +before normalisation. This should fail with a clear user-facing error +instead of letting invalid floating-point values propagate into the +minimiser. **Depends on:** related to issue 3, but independent. @@ -304,14 +322,16 @@ letting invalid floating-point values propagate into the minimiser. **Type:** Completeness -The current architecture moved calculator selection to the experiment level via -`calculator_type`, but this selection is not written to CIF during `save()` / -`show_as_cif()`. Reloading or exporting a project therefore loses explicit -calculator choices and falls back to auto-resolution. +The current architecture moved calculator selection to the experiment +level via `calculator_type`, but this selection is not written to CIF +during `save()` / `show_as_cif()`. Reloading or exporting a project +therefore loses explicit calculator choices and falls back to +auto-resolution. -**Fix:** serialise `calculator_type` as part of the experiment or analysis -state, and make sure `load()` restores it. The saved project should represent -the exact active calculator configuration, not just a re-derivable default. +**Fix:** serialise `calculator_type` as part of the experiment or +analysis state, and make sure `load()` restores it. The saved project +should represent the exact active calculator configuration, not just a +re-derivable default. **Depends on:** issue 1 (`Project.load()` implementation). diff --git a/docs/api-reference/analysis.md b/docs/docs/api-reference/analysis.md similarity index 100% rename from docs/api-reference/analysis.md rename to docs/docs/api-reference/analysis.md diff --git a/docs/api-reference/core.md b/docs/docs/api-reference/core.md similarity index 100% rename from docs/api-reference/core.md rename to docs/docs/api-reference/core.md diff --git a/docs/api-reference/crystallography.md b/docs/docs/api-reference/crystallography.md similarity index 100% rename from docs/api-reference/crystallography.md rename to docs/docs/api-reference/crystallography.md diff --git a/docs/api-reference/datablocks/experiment.md b/docs/docs/api-reference/datablocks/experiment.md similarity index 100% rename from docs/api-reference/datablocks/experiment.md rename to docs/docs/api-reference/datablocks/experiment.md diff --git a/docs/api-reference/datablocks/structure.md b/docs/docs/api-reference/datablocks/structure.md similarity index 100% rename from docs/api-reference/datablocks/structure.md rename to docs/docs/api-reference/datablocks/structure.md diff --git a/docs/api-reference/display.md b/docs/docs/api-reference/display.md similarity index 100% rename from docs/api-reference/display.md rename to docs/docs/api-reference/display.md diff --git a/docs/api-reference/index.md b/docs/docs/api-reference/index.md similarity index 79% rename from docs/api-reference/index.md rename to docs/docs/api-reference/index.md index 6a6f8be4..351d7f56 100644 --- a/docs/api-reference/index.md +++ b/docs/docs/api-reference/index.md @@ -7,19 +7,20 @@ icon: material/code-braces-box This section contains the reference detailing the functions and modules available in EasyDiffraction: -- [core](core.md) – Contains core utilities and foundational objects used across - the package. -- [crystallography](crystallography.md) – Handles crystallographic calculations, - space groups, and symmetry operations. +- [core](core.md) – Contains core utilities and foundational objects + used across the package. +- [crystallography](crystallography.md) – Handles crystallographic + calculations, space groups, and symmetry operations. - [utils](utils.md) – Miscellaneous utility functions for formatting, decorators, and general helpers. - datablocks - - [experiments](datablocks/experiment.md) – Manages experimental setups and - instrument parameters, as well as the associated diffraction data. + - [experiments](datablocks/experiment.md) – Manages experimental + setups and instrument parameters, as well as the associated + diffraction data. - [structures](datablocks/structure.md) – Defines structures, such as crystallographic structures, and manages their properties. - [display](display.md) – Tools for plotting data and rendering tables. - [project](project.md) – Defines the project and manages its state. -- [analysis](analysis.md) – Provides tools for analyzing diffraction data, - including fitting and minimization. +- [analysis](analysis.md) – Provides tools for analyzing diffraction + data, including fitting and minimization. - [summary](summary.md) – Provides a summary of the project. diff --git a/docs/api-reference/io.md b/docs/docs/api-reference/io.md similarity index 100% rename from docs/api-reference/io.md rename to docs/docs/api-reference/io.md diff --git a/docs/api-reference/project.md b/docs/docs/api-reference/project.md similarity index 100% rename from docs/api-reference/project.md rename to docs/docs/api-reference/project.md diff --git a/docs/api-reference/summary.md b/docs/docs/api-reference/summary.md similarity index 100% rename from docs/api-reference/summary.md rename to docs/docs/api-reference/summary.md diff --git a/docs/api-reference/utils.md b/docs/docs/api-reference/utils.md similarity index 100% rename from docs/api-reference/utils.md rename to docs/docs/api-reference/utils.md diff --git a/docs/docs/assets/images/favicon.png b/docs/docs/assets/images/favicon.png new file mode 100644 index 00000000..27628988 Binary files /dev/null and b/docs/docs/assets/images/favicon.png differ diff --git a/docs/docs/assets/images/logo_dark.svg b/docs/docs/assets/images/logo_dark.svg new file mode 100644 index 00000000..0be819d0 --- /dev/null +++ b/docs/docs/assets/images/logo_dark.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + Logo + + Main circle + + + Inner circles + + + + + + + + + + + Text + + easy + + + diffraction + + + + \ No newline at end of file diff --git a/docs/docs/assets/images/logo_light.svg b/docs/docs/assets/images/logo_light.svg new file mode 100644 index 00000000..84103655 --- /dev/null +++ b/docs/docs/assets/images/logo_light.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + Logo + + Main circle + + + Inner circles + + + + + + + + + + + Text + + easy + + + diffraction + + + + \ No newline at end of file diff --git a/docs/assets/images/user-guide/data-acquisition_2d-raw-data.jpg b/docs/docs/assets/images/user-guide/data-acquisition_2d-raw-data.jpg similarity index 100% rename from docs/assets/images/user-guide/data-acquisition_2d-raw-data.jpg rename to docs/docs/assets/images/user-guide/data-acquisition_2d-raw-data.jpg diff --git a/docs/assets/images/user-guide/data-acquisition_instrument.png b/docs/docs/assets/images/user-guide/data-acquisition_instrument.png similarity index 100% rename from docs/assets/images/user-guide/data-acquisition_instrument.png rename to docs/docs/assets/images/user-guide/data-acquisition_instrument.png diff --git a/docs/assets/images/user-guide/data-analysis_model.png b/docs/docs/assets/images/user-guide/data-analysis_model.png similarity index 100% rename from docs/assets/images/user-guide/data-analysis_model.png rename to docs/docs/assets/images/user-guide/data-analysis_model.png diff --git a/docs/assets/images/user-guide/data-analysis_refinement.png b/docs/docs/assets/images/user-guide/data-analysis_refinement.png similarity index 100% rename from docs/assets/images/user-guide/data-analysis_refinement.png rename to docs/docs/assets/images/user-guide/data-analysis_refinement.png diff --git a/docs/assets/images/user-guide/data-reduction_1d-pattern.png b/docs/docs/assets/images/user-guide/data-reduction_1d-pattern.png similarity index 100% rename from docs/assets/images/user-guide/data-reduction_1d-pattern.png rename to docs/docs/assets/images/user-guide/data-reduction_1d-pattern.png diff --git a/docs/docs/assets/javascripts/extra.js b/docs/docs/assets/javascripts/extra.js new file mode 100644 index 00000000..9596ee07 --- /dev/null +++ b/docs/docs/assets/javascripts/extra.js @@ -0,0 +1,27 @@ +;(function () { + 'use strict' + + // Variables + const header = document.getElementsByTagName('header')[0] + + console.log(window.pageYOffset) + + // Hide-show header shadow + function toggleHeaderShadow() { + if (window.pageYOffset <= 0) { + header.classList.remove('md-header--shadow') + } else { + header.classList.add('md-header--shadow') + } + } + + // Onload + window.onload = function () { + toggleHeaderShadow() + } + + // Onscroll + window.onscroll = function () { + toggleHeaderShadow() + } +})() diff --git a/docs/docs/assets/javascripts/mathjax.js b/docs/docs/assets/javascripts/mathjax.js new file mode 100644 index 00000000..333524ec --- /dev/null +++ b/docs/docs/assets/javascripts/mathjax.js @@ -0,0 +1,33 @@ +window.MathJax = { + tex: { + //inlineMath: [['\\(', '\\)']], + //displayMath: [['\\[', '\\]']], + // Add support for $...$ and \(...\) delimiters + inlineMath: [ + ['$', '$'], + ['\\(', '\\)'], + ], + // Add support for $$...$$ and \[...]\ delimiters + displayMath: [ + ['$$', '$$'], + ['\\[', '\\]'], + ], + processEscapes: true, + processEnvironments: true, + }, + options: { + //ignoreHtmlClass: ".*|", + //processHtmlClass: "arithmatex" + // Skip code blocks only + skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'], + // Only ignore explicit opt-out + ignoreHtmlClass: 'no-mathjax|tex2jax_ignore', + }, +} + +document$.subscribe(() => { + MathJax.startup.output.clearCache() + MathJax.typesetClear() + MathJax.texReset() + MathJax.typesetPromise() +}) diff --git a/docs/docs/assets/stylesheets/extra.css b/docs/docs/assets/stylesheets/extra.css new file mode 100644 index 00000000..a625be80 --- /dev/null +++ b/docs/docs/assets/stylesheets/extra.css @@ -0,0 +1,359 @@ +/*************/ +/* Variables */ +/*************/ + +:root { + --sz-link-color--lightmode: #0184c7; + --sz-hovered-link-color--lightmode: #37bdf9; + --sz-body-background-color--lightmode: #fafafa; + --sz-body-text-color--lightmode: #525252; + --sz-body-heading-color--lightmode: #434343; + --sz-code-background-color--lightmode: #ececec; + + --sz-link-color--darkmode: #37bdf9; + --sz-hovered-link-color--darkmode: #2890c0; + --sz-body-background-color--darkmode: #262626; + --sz-body-text-color--darkmode: #a3a3a3; + --sz-body-heading-color--darkmode: #e5e5e5; + --sz-code-background-color--darkmode: #212121; +} + +/****************/ +/* Color styles */ +/****************/ + +/* Default styles https://github.com/squidfunk/mkdocs-material/blob/master/src/assets/stylesheets/main/_typeset.scss */ + +/* Light mode */ + +/* Default light mode https://github.com/squidfunk/mkdocs-material/blob/master/src/assets/stylesheets/main/_colors.scss */ + +[data-md-color-scheme="default"] { + + /* Primary color shades */ + --md-primary-fg-color: var(--sz-body-background-color--lightmode); /* Navigation background */ + --md-primary-bg-color: var(--sz-body-text-color--lightmode); /* E.g., Header title and icons */ + --md-primary-bg-color--light: var(--sz-body-text-color--lightmode); /* E.g., Header search */ + + /* Accent color shades */ + --md-accent-fg-color: var(--sz-hovered-link-color--lightmode); /* E.g., Hovered `a` elements & copy icon in code */ + + /* Default color shades */ + --md-default-fg-color: var(--sz-body-text-color--lightmode); + --md-default-fg-color--light: var(--sz-body-heading-color--lightmode); /* E.g., `h1` color & TOC viewed items & `$` in code */ + --md-default-fg-color--lighter: var(--sz-body-text-color--lightmode); /* E.g., `¶` sign near `h1-h8` */ + --md-default-fg-color--lightest: var(--sz-body-text-color--lightmode); /* E.g., Copy icon in code */ + --md-default-bg-color: var(--sz-body-background-color--lightmode); + + /* Code color shades */ + --md-code-bg-color: var(--sz-code-background-color--lightmode); + + /* Typeset color shades */ + --md-typeset-color: var(--sz-body-text-color--lightmode); + + /* Typeset `a` color shades */ + --md-typeset-a-color: var(--sz-link-color--lightmode); + + /* Footer color shades */ + --md-footer-fg-color: var(--sz-body-text-color--lightmode); /* E.g., Next -> */ + --md-footer-fg-color--light: var(--sz-body-text-color--lightmode); /* E.g., © 2022 EasyDiffraction, Material for MkDocs */ + --md-footer-fg-color--lighter: var(--sz-body-text-color--lightmode); /* E.g. Made with */ + --md-footer-bg-color: hsla(0, 0%, 0%, 0.0); /* Space with, e.g., Next -> */ + --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.0); /* Space with, e.g., © 2022 EasyDiffraction */ + + /* Custom colors */ + --sz-red-color: #F44336; + --sz-blue-color: #03A9F4; + --sz-green-color: #4CAF50; + --sz-orange-color: #FF9800; + + /* Logo display */ + --md-footer-logo-dark-mode: none; + --md-footer-logo-light-mode: block; +} + +/* Dark mode */ + +/* Default dark mode: https://github.com/squidfunk/mkdocs-material/blob/master/src/assets/stylesheets/palette/_scheme.scss */ + +[data-md-color-scheme="slate"] { + + /* Primary color shades */ + --md-primary-fg-color: var(--sz-body-background-color--darkmode); /* Navigation background */ + --md-primary-bg-color: var(--sz-body-text-color--darkmode); /* E.g., Header title and icons */ + --md-primary-bg-color--light: var(--sz-body-text-color--darkmode); /* E.g., Header search */ + + /* Accent color shades */ + --md-accent-fg-color: var(--sz-hovered-link-color--darkmode); /* E.g., Hovered `a` elements & copy icon in code */ + + /* Default color shades */ + --md-default-fg-color: var(--sz-body-text-color--darkmode); + --md-default-fg-color--light: var(--sz-body-heading-color--darkmode); /* E.g., `h1` color & TOC viewed items & `$` in code */ + --md-default-fg-color--lighter: var(--sz-body-text-color--darkmode); /* E.g., `¶` sign near `h1-h8` */ + --md-default-fg-color--lightest: var(--sz-body-text-color--darkmode); /* E.g., Copy icon in code */ + --md-default-bg-color: var(--sz-body-background-color--darkmode); + + /* Code color shades */ + --md-code-bg-color: var(--sz-code-background-color--darkmode); + + /* Typeset color shades */ + --md-typeset-color: var(--sz-body-text-color--darkmode); + + /* Typeset `a` color shades */ + --md-typeset-a-color: var(--sz-link-color--darkmode); + + /* Footer color shades */ + --md-footer-fg-color: var(--sz-body-text-color--darkmode); /* E.g., Next -> */ + --md-footer-fg-color--light: var(--sz-body-text-color--darkmode); /* E.g., © 2022 EasyDiffraction, Material for MkDocs */ + --md-footer-fg-color--lighter: var(--sz-body-text-color--darkmode); /* E.g. Made with */ + --md-footer-bg-color: hsla(0, 0%, 0%, 0.0); /* Space with, e.g., Next -> */ + --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.0); /* Space with, e.g., © 2022 EasyDiffraction */ + + /* Custom colors */ + --sz-red-color: #EF9A9A; + --sz-blue-color: #81D4FA; + --sz-green-color: #A5D6A7; + --sz-orange-color: #FFCC80; + + /* Logo display */ + --md-footer-logo-dark-mode: block; + --md-footer-logo-light-mode: none; +} + +/*****************/ +/* Custom styles */ +/*****************/ + +/* Logo */ + +#logo_light_mode { + display: var(--md-footer-logo-light-mode); +} + +#logo_dark_mode { + display: var(--md-footer-logo-dark-mode); +} + +/* Customize default styles of MkDocs Material */ + +/* Hide navigation title */ +label.md-nav__title[for="__drawer"] { + height: 0; +} + +/* Hide site title (first topic) while keeping page title and version selector */ +.md-header__topic:first-child .md-ellipsis { + display: none; +} + +/* Increase logo size */ +.md-logo :is(img, svg) { + height: 1.8rem !important; +} + +/* Hide GH repo with counts (top right page corner) */ +.md-header__source { + display: none; +} + +/* Hide GH repo with counts (navigation bar in mobile view) */ +.md-nav__source { + display: none; +} + +/* Ensure all horizontal lines in the navigation list are removed or hidden */ +.md-nav__item { + /* Removes any border starting from the second level */ + border: none !important; + /* Modifies the background color to hide the first horizontal line */ + background-color: var(--md-default-bg-color); +} + +/* Increase TOC (on the right) width */ +.md-nav--secondary { + margin-left: -10px; + margin-right: -4px; +} + +/* */ +.md-nav__item > .md-nav__link { + padding-left: 0.5em; /* Default */ +} + +/* Change line height of the tabel cells */ +.md-typeset td, +.md-typeset th { + line-height: 1.25 !important; +} + +/* Change vertical alignment of the icon inside the tabel cells */ +.md-typeset td .twemoji { + vertical-align: sub !important; +} + +/* Change the width of the primary sidebar */ +/* +.md-sidebar--primary { + width: 240px; +} +*/ + +/* Change the overall width of the page */ +.md-grid { + max-width: 1280px; +} + +/* Needed for mkdocs-jupyter to show download and other buttons on top of the notebook */ +.md-content__button { + position: relative !important; +} + +/* Background color of the search input field */ +.md-search__input { + background-color: var(--md-code-bg-color) !important; +} + +/* Customize default style of mkdocs-jupyter plugin */ + +/* Set the width of the notebook to fill 100% and not reduce by the width of .md-content__button's +Adjust the margins and paddings to fit the defaults in MkDocs Material and do not crop the label in the header +*/ +.jupyter-wrapper { + width: 100% !important; + display: flex !important; +} + +.jp-Notebook { + padding: 0 !important; + margin-top: -3em !important; + + /* Ensure notebook content stretches across the page */ + width: 100% !important; + max-width: 100% !important; + + /* mkdocs-material + some notebook HTML end up as flex */ + align-items: stretch !important; +} + +.jp-Notebook .jp-Cell { + /* Key: flex children often need min-width: 0 to prevent weird shrink */ + width: 100% !important; + max-width: 100% !important; + min-width: 0 !important; + + /* Removes jupyter cell paddings */ + padding-left: 0 !important; +} + +/* Removes jupyter cell prefixes, like In[123]: */ +.prompt, +.jp-InputPrompt, +.jp-OutputPrompt { + display: none !important; +} + +/* Removes jupyter output cell padding to align with input cell text */ +.jp-RenderedText { + padding-left: 0.85em !important; +} + +/* Extra styling the panda dataframes, on top of the style included in the code */ +table.dataframe { + float: left; + margin-left: 0.75em !important; + margin-bottom: 0.5em !important; + font-size: var(--jp-code-font-size) !important; + color: var(--md-primary-bg-color) !important; + /* Allow table cell wrapping in MkDocs-Jupyter outputs */ + /* + table-layout: auto !important; + width: auto !important; + */ +} + +/* Allow wrap for the last column */ +/* +table.dataframe td:last-child, +table.dataframe th:last-child { + white-space: normal !important; + word-break: break-word !important; +} +*/ + +/* Custom styles for the CIF files */ + +.cif { + padding-left: 1em; + padding-right: 1em; + padding-top: 1px; + padding-bottom: 1px; + background-color: var(--md-code-bg-color); + font-size: small; +} +.red { + color: var(--sz-red-color); +} +.green { + color: var(--sz-green-color); +} +.blue { + color: var(--sz-blue-color); +} +.orange { + color: var(--sz-orange-color); +} +.grey { + color: grey; +} + +/**********/ +/* Labels */ +/**********/ + +.label-cif { + padding-top: 0.5ex; + padding-bottom: 0.5ex; + padding-left: 0.9ex; + padding-right: 0.9ex; + border-radius: 1ex; + color: var(--md-default-fg-color) !important; + background-color: var(--md-code-bg-color); +} + +p .label-cif, li .label-cif { + vertical-align: 5%; + font-size: 12px; +} + +.label-cif:hover { + color: white !important; +} + +.label-experiment { + padding-top: 0.25ex; + padding-bottom: 0.6ex; + padding-left: 0.9ex; + padding-right: 0.9ex; + border-radius: 1ex; + color: var(--md-default-fg-color) !important; + background-color: rgba(55, 189, 249, 0.1); +} + +p .label-experiment, li .label-experiment { + vertical-align: 5%; + font-size: 12px; +} + +h1 .label-experiment { + padding-top: 0.05ex; + padding-bottom: 0.4ex; + padding-left: 0.9ex; + padding-right: 0.9ex; + border-radius: 0.75ex; + color: var(--md-default-fg-color) !important; + background-color: rgba(55, 189, 249, 0.1); +} + +.label-experiment:hover { + color: white !important; +} diff --git a/docs/index.md b/docs/docs/index.md similarity index 53% rename from docs/index.md rename to docs/docs/index.md index a34d3023..73c35423 100644 --- a/docs/index.md +++ b/docs/docs/index.md @@ -4,17 +4,18 @@ Here is a brief overview of the main documentation sections: -- [:material-information-slab-circle: Introduction](introduction/index.md) – - Provides an overview of EasyDiffraction, including its purpose, licensing, - latest release details, and contact information. -- [:material-cog-box: Installation & Setup](installation-and-setup/index.md) – - Guides users through system requirements, environment configuration, and the - installation process. -- [:material-book-open-variant: User Guide](user-guide/index.md) – Covers core - concepts, key terminology, workflow steps, and essential parameters for - effective use of EasyDiffraction. +- [:material-information-slab-circle: Introduction](introduction/index.md) + – Provides an overview of EasyDiffraction, including its purpose, + licensing, latest release details, and contact information. +- [:material-cog-box: Installation & Setup](installation-and-setup/index.md) + – Guides users through system requirements, environment configuration, + and the installation process. +- [:material-book-open-variant: User Guide](user-guide/index.md) – + Covers core concepts, key terminology, workflow steps, and essential + parameters for effective use of EasyDiffraction. - [:material-school: Tutorials](tutorials/index.md) – Offers practical, - step-by-step examples demonstrating common workflows and data analysis tasks. -- [:material-code-braces-box: API Reference](api-reference/index.md) – An - auto-generated reference detailing the available functions and modules in - EasyDiffraction. + step-by-step examples demonstrating common workflows and data analysis + tasks. +- [:material-code-braces-box: API Reference](api-reference/index.md) – + An auto-generated reference detailing the available functions and + modules in EasyDiffraction. diff --git a/docs/installation-and-setup/index.md b/docs/docs/installation-and-setup/index.md similarity index 80% rename from docs/installation-and-setup/index.md rename to docs/docs/installation-and-setup/index.md index 80b64235..0891a55f 100644 --- a/docs/installation-and-setup/index.md +++ b/docs/docs/installation-and-setup/index.md @@ -6,16 +6,16 @@ icon: material/cog-box ## Requirements -EasyDiffraction is a cross-platform Python library compatible with **Python 3.11 -through 3.13**. +EasyDiffraction is a cross-platform Python library compatible with +**Python 3.11 through 3.13**. Make sure Python is installed on your system before proceeding with the installation. ## Environment Setup optional { #environment-setup data-toc-label="Environment Setup" } -We recommend using a **virtual environment** to isolate dependencies and avoid -conflicts with system-wide packages. If any issues arise, you can simply delete -and recreate the environment. +We recommend using a **virtual environment** to isolate dependencies and +avoid conflicts with system-wide packages. If any issues arise, you can +simply delete and recreate the environment. #### Creating and Activating a Virtual Environment: @@ -76,22 +76,23 @@ and recreate the environment. ### Installing from PyPI recommended { #from-pypi data-toc-label="Installing from PyPI" } -EasyDiffraction is available on **PyPI (Python Package Index)** and can be -installed using `pip`. We strongly recommend installing it within a virtual -environment, as described in the [Environment Setup](#environment-setup) -section. +EasyDiffraction is available on **PyPI (Python Package Index)** and can +be installed using `pip`. We strongly recommend installing it within a +virtual environment, as described in the +[Environment Setup](#environment-setup) section. We recommend installing the latest release of EasyDiffraction with the -`visualization` extras, which include optional dependencies used for simplified -visualization of charts and tables. This can be especially useful for running -the Jupyter Notebook examples. To do so, use the following command: +`visualization` extras, which include optional dependencies used for +simplified visualization of charts and tables. This can be especially +useful for running the Jupyter Notebook examples. To do so, use the +following command: ```bash pip install 'easydiffraction[visualization]' ``` -If only the core functionality is needed, the library can be installed simply -with: +If only the core functionality is needed, the library can be installed +simply with: ```bash pip install easydiffraction @@ -117,8 +118,8 @@ pip show easydiffraction ### Installing from GitHub -Installing unreleased versions is generally not recommended but may be useful -for testing. +Installing unreleased versions is generally not recommended but may be +useful for testing. To install EasyDiffraction from, e.g., the `develop` branch of GitHub: @@ -134,18 +135,20 @@ pip install 'easydiffraction[visualization] @ git+https://github.com/easyscience ## How to Run Tutorials -EasyDiffraction includes a collection of **Jupyter Notebook examples** that -demonstrate key functionality. These tutorials serve as **step-by-step guides** -to help users understand the diffraction data analysis workflow. +EasyDiffraction includes a collection of **Jupyter Notebook examples** +that demonstrate key functionality. These tutorials serve as +**step-by-step guides** to help users understand the diffraction data +analysis workflow. They are available as **static HTML pages** in the -[:material-school: Tutorials](../tutorials/index.md) section. You can also run -them interactively in two ways: +[:material-school: Tutorials](../tutorials/index.md) section. You can +also run them interactively in two ways: - **Run Locally** – Download the notebook via the :material-download: **Download** button and run it on your computer. -- **Run Online** – Use the :google-colab: **Open in Google Colab** button to run - the tutorial directly in your browser (no setup required). +- **Run Online** – Use the :google-colab: **Open in Google Colab** + button to run the tutorial directly in your browser (no setup + required). !!! note @@ -154,8 +157,9 @@ them interactively in two ways: ### Run Tutorials Locally -To run tutorials locally, install **Jupyter Notebook** or **JupyterLab**. Here -are the steps to follow in the case of **Jupyter Notebook**: +To run tutorials locally, install **Jupyter Notebook** or +**JupyterLab**. Here are the steps to follow in the case of **Jupyter +Notebook**: - Install Jupyter Notebook and IPython kernel: ```bash @@ -177,32 +181,34 @@ are the steps to follow in the case of **Jupyter Notebook**: ```bash http://localhost:8888/ ``` -- Open one of the `*.ipynb` files and select the `EasyDiffraction Python kernel` - to get started. +- Open one of the `*.ipynb` files and select the + `EasyDiffraction Python kernel` to get started. ### Run Tutorials via Google Colab -**Google Colab** lets you run Jupyter Notebooks in the cloud without any local -installation. +**Google Colab** lets you run Jupyter Notebooks in the cloud without any +local installation. To use Google Colab: - Ensure you have a **Google account**. -- Go to the **[:material-school: Tutorials](../tutorials/index.md)** section. -- Click the :google-colab: **Open in Google Colab** button on any tutorial. +- Go to the **[:material-school: Tutorials](../tutorials/index.md)** + section. +- Click the :google-colab: **Open in Google Colab** button on any + tutorial. -This is the fastest way to start experimenting with EasyDiffraction, without -setting up Python on your system. +This is the fastest way to start experimenting with EasyDiffraction, +without setting up Python on your system. ## Installing with Pixi alternative { #installing-with-pixi data-toc-label="Installing with Pixi" } -[Pixi](https://pixi.sh) is a modern package and environment manager for Python -and Conda-compatible packages. It simplifies dependency management, environment -isolation, and reproducibility. +[Pixi](https://pixi.sh) is a modern package and environment manager for +Python and Conda-compatible packages. It simplifies dependency +management, environment isolation, and reproducibility. The following simple steps provide an alternative setup method for -EasyDiffraction using Pixi, replacing the traditional virtual environment -approach. +EasyDiffraction using Pixi, replacing the traditional virtual +environment approach. diff --git a/docs/introduction/index.md b/docs/docs/introduction/index.md similarity index 90% rename from docs/introduction/index.md rename to docs/docs/introduction/index.md index 2996386b..a2b42b59 100644 --- a/docs/introduction/index.md +++ b/docs/docs/introduction/index.md @@ -8,21 +8,22 @@ icon: material/information-slab-circle **EasyDiffraction** is scientific software for calculating diffraction patterns -based on structural models and refining model parameters against experimental -data. +based on structural models and refining model parameters against +experimental data. -It is available as both a cross-platform desktop application and a Python -library. +It is available as both a cross-platform desktop application and a +Python library. -This documentation covers the usage of the EasyDiffraction Python library. +This documentation covers the usage of the EasyDiffraction Python +library. For the graphical user interface (GUI) version, refer to the [GUI documentation](https://docs.easydiffraction.org/app). ## EasyScience EasyDiffraction is developed using the -[EasyScience framework](https://easyscience.software), which provides tools -for +[EasyScience framework](https://easyscience.software), which provides +tools for building modular and flexible scientific libraries and applications. ## License @@ -35,27 +36,28 @@ EasyDiffraction is released under the The latest version of the EasyDiffraction Python library is [{{ vars.release_version }}](https://github.com/easyscience/diffraction-lib/releases/latest). -For a complete list of new features, bug fixes, and improvements, see the +For a complete list of new features, bug fixes, and improvements, see +the [GitHub Releases page](https://github.com/easyscience/diffraction-lib/releases). ## Citation -If you use EasyDiffraction in your work, please cite the specific version you -used. +If you use EasyDiffraction in your work, please cite the specific +version you used. All official releases of the EasyDiffraction library are archived on Zenodo, each with a version-specific Digital Object Identifier (DOI). -Citation details in various styles (e.g., APA, MLA) and formats (e.g., BibTeX, -JSON) +Citation details in various styles (e.g., APA, MLA) and formats (e.g., +BibTeX, JSON) are available on the [Zenodo archive page](https://doi.org/10.5281/zenodo.16806521). ## Contributing -We welcome contributions from the community! EasyDiffraction is intended to be a -community-driven, open-source project supported by a diverse group of -contributors. +We welcome contributions from the community! EasyDiffraction is intended +to be a community-driven, open-source project supported by a diverse +group of contributors. The project is maintained by the [European Spallation Source (ESS)](https://ess.eu). diff --git a/docs/docs/tutorials/ed-1.ipynb b/docs/docs/tutorials/ed-1.ipynb new file mode 100644 index 00000000..9b46deb0 --- /dev/null +++ b/docs/docs/tutorials/ed-1.ipynb @@ -0,0 +1,203 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: LBCO, HRPT\n", + "\n", + "This minimalistic example is designed to show how Rietveld refinement\n", + "can be performed when both the crystal structure and experiment\n", + "parameters are defined using CIF files.\n", + "\n", + "For this example, constant-wavelength neutron powder diffraction data\n", + "for La0.5Ba0.5CoO3 from HRPT at PSI is used.\n", + "\n", + "It does not contain any advanced features or options, and includes no\n", + "comments or explanations—these can be found in the other tutorials.\n", + "Default values are used for all parameters if not specified. Only\n", + "essential and self-explanatory code is provided.\n", + "\n", + "The example is intended for users who are already familiar with the\n", + "EasyDiffraction library and want to quickly get started with a simple\n", + "refinement. It is also useful for those who want to see what a\n", + "refinement might look like in code. For a more detailed explanation of\n", + "the code, please refer to the other tutorials." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Step 1: Define Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "# Create minimal project without name and description\n", + "project = ed.Project()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Step 2: Define Crystal Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "# Download CIF file from repository\n", + "structure_path = ed.download_data(id=1, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add_from_cif_path(structure_path)" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "## Step 3: Define Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "# Download CIF file from repository\n", + "expt_path = ed.download_data(id=2, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_cif_path(expt_path)" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Step 4: Perform Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "# Start refinement. All parameters, which have standard uncertainties\n", + "# in the input CIF files, are refined by default.\n", + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "# Show fit results summary\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.show_names()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "16", + "metadata": {}, + "source": [ + "## Step 5: Show Project Summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17", + "metadata": {}, + "outputs": [], + "source": [ + "project.summary.show_report()" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-1.py b/docs/docs/tutorials/ed-1.py similarity index 100% rename from tutorials/ed-1.py rename to docs/docs/tutorials/ed-1.py diff --git a/docs/docs/tutorials/ed-10.ipynb b/docs/docs/tutorials/ed-10.ipynb new file mode 100644 index 00000000..d6596816 --- /dev/null +++ b/docs/docs/tutorials/ed-10.ipynb @@ -0,0 +1,222 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Pair Distribution Function: Ni, NPD\n", + "\n", + "This example demonstrates a pair distribution function (PDF) analysis\n", + "of Ni, based on data collected from a constant wavelength neutron\n", + "powder diffraction experiment.\n", + "\n", + "The dataset is taken from:\n", + "https://github.com/diffpy/cmi_exchange/tree/main/cmi_scripts/fitNiPDF" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "project = ed.Project()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.create(name='ni')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['ni'].space_group.name_h_m = 'F m -3 m'\n", + "project.structures['ni'].space_group.it_coordinate_system_code = '1'\n", + "project.structures['ni'].cell.length_a = 3.52387\n", + "project.structures['ni'].atom_sites.create(\n", + " label='Ni',\n", + " type_symbol='Ni',\n", + " fract_x=0.0,\n", + " fract_y=0.0,\n", + " fract_z=0.0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.5,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "## Add Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = ed.download_data(id=6, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_data_path(\n", + " name='pdf',\n", + " data_path=data_path,\n", + " sample_form='powder',\n", + " beam_mode='constant wavelength',\n", + " radiation_probe='neutron',\n", + " scattering_type='total',\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['pdf'].linked_phases.create(id='ni', scale=1.0)\n", + "project.experiments['pdf'].peak.damp_q = 0\n", + "project.experiments['pdf'].peak.broad_q = 0.03\n", + "project.experiments['pdf'].peak.cutoff_q = 27.0\n", + "project.experiments['pdf'].peak.sharp_delta_1 = 0.0\n", + "project.experiments['pdf'].peak.sharp_delta_2 = 2.0\n", + "project.experiments['pdf'].peak.damp_particle_diameter = 0" + ] + }, + { + "cell_type": "markdown", + "id": "12", + "metadata": {}, + "source": [ + "## Select Fitting Parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['ni'].cell.length_a.free = True\n", + "project.structures['ni'].atom_sites['Ni'].b_iso.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['pdf'].linked_phases['ni'].scale.free = True\n", + "project.experiments['pdf'].peak.broad_q.free = True\n", + "project.experiments['pdf'].peak.sharp_delta_2.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "## Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "## Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='pdf', show_residual=True)" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-10.py b/docs/docs/tutorials/ed-10.py similarity index 100% rename from tutorials/ed-10.py rename to docs/docs/tutorials/ed-10.py diff --git a/docs/docs/tutorials/ed-11.ipynb b/docs/docs/tutorials/ed-11.ipynb new file mode 100644 index 00000000..ddacaac7 --- /dev/null +++ b/docs/docs/tutorials/ed-11.ipynb @@ -0,0 +1,254 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Pair Distribution Function: Si, NPD\n", + "\n", + "This example demonstrates a pair distribution function (PDF) analysis\n", + "of Si, based on data collected from a time-of-flight neutron powder\n", + "diffraction experiment at NOMAD at SNS." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "project = ed.Project()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Set Plotting Engine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "# Keep the auto-selected engine. Alternatively, you can uncomment the\n", + "# line below to explicitly set the engine to the required one.\n", + "# project.plotter.engine = 'plotly'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "# Set global plot range for plots\n", + "project.plotter.x_max = 40" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "## Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.create(name='si')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure = project.structures['si']\n", + "structure.space_group.name_h_m.value = 'F d -3 m'\n", + "structure.space_group.it_coordinate_system_code = '1'\n", + "structure.cell.length_a = 5.43146\n", + "structure.atom_sites.create(\n", + " label='Si',\n", + " type_symbol='Si',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.5,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Add Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = ed.download_data(id=5, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_data_path(\n", + " name='nomad',\n", + " data_path=data_path,\n", + " sample_form='powder',\n", + " beam_mode='time-of-flight',\n", + " radiation_probe='neutron',\n", + " scattering_type='total',\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "experiment = project.experiments['nomad']\n", + "experiment.linked_phases.create(id='si', scale=1.0)\n", + "experiment.peak.damp_q = 0.02\n", + "experiment.peak.broad_q = 0.03\n", + "experiment.peak.cutoff_q = 35.0\n", + "experiment.peak.sharp_delta_1 = 0.0\n", + "experiment.peak.sharp_delta_2 = 4.0\n", + "experiment.peak.damp_particle_diameter = 0" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "## Select Fitting Parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['si'].cell.length_a.free = True\n", + "project.structures['si'].atom_sites['Si'].b_iso.free = True\n", + "experiment.linked_phases['si'].scale.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.peak.damp_q.free = True\n", + "experiment.peak.broad_q.free = True\n", + "experiment.peak.sharp_delta_1.free = True\n", + "experiment.peak.sharp_delta_2.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "18", + "metadata": {}, + "source": [ + "## Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "20", + "metadata": {}, + "source": [ + "## Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='nomad', show_residual=False)" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-11.py b/docs/docs/tutorials/ed-11.py similarity index 100% rename from tutorials/ed-11.py rename to docs/docs/tutorials/ed-11.py diff --git a/docs/docs/tutorials/ed-12.ipynb b/docs/docs/tutorials/ed-12.ipynb new file mode 100644 index 00000000..a6394390 --- /dev/null +++ b/docs/docs/tutorials/ed-12.ipynb @@ -0,0 +1,303 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Pair Distribution Function: NaCl, XRD\n", + "\n", + "This example demonstrates a pair distribution function (PDF) analysis\n", + "of NaCl, based on data collected from an X-ray powder diffraction\n", + "experiment.\n", + "\n", + "The dataset is taken from:\n", + "https://github.com/diffpy/add2019-diffpy-cmi/tree/master" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "project = ed.Project()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Set Plotting Engine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "# Keep the auto-selected engine. Alternatively, you can uncomment the\n", + "# line below to explicitly set the engine to the required one.\n", + "# project.plotter.engine = 'plotly'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "# Set global plot range for plots\n", + "project.plotter.x_min = 2.0\n", + "project.plotter.x_max = 30.0" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "## Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.create(name='nacl')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['nacl'].space_group.name_h_m = 'F m -3 m'\n", + "project.structures['nacl'].space_group.it_coordinate_system_code = '1'\n", + "project.structures['nacl'].cell.length_a = 5.62\n", + "project.structures['nacl'].atom_sites.create(\n", + " label='Na',\n", + " type_symbol='Na',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=1.0,\n", + ")\n", + "project.structures['nacl'].atom_sites.create(\n", + " label='Cl',\n", + " type_symbol='Cl',\n", + " fract_x=0.5,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='b',\n", + " b_iso=1.0,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Add Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = ed.download_data(id=4, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_data_path(\n", + " name='xray_pdf',\n", + " data_path=data_path,\n", + " sample_form='powder',\n", + " beam_mode='constant wavelength',\n", + " radiation_probe='xray',\n", + " scattering_type='total',\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['xray_pdf'].show_supported_peak_profile_types()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['xray_pdf'].show_current_peak_profile_type()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['xray_pdf'].peak_profile_type = 'gaussian-damped-sinc'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['xray_pdf'].peak.damp_q = 0.03\n", + "project.experiments['xray_pdf'].peak.broad_q = 0\n", + "project.experiments['xray_pdf'].peak.cutoff_q = 21\n", + "project.experiments['xray_pdf'].peak.sharp_delta_1 = 0\n", + "project.experiments['xray_pdf'].peak.sharp_delta_2 = 5\n", + "project.experiments['xray_pdf'].peak.damp_particle_diameter = 0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['xray_pdf'].linked_phases.create(id='nacl', scale=0.5)" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "## Select Fitting Parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['nacl'].cell.length_a.free = True\n", + "project.structures['nacl'].atom_sites['Na'].b_iso.free = True\n", + "project.structures['nacl'].atom_sites['Cl'].b_iso.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['xray_pdf'].linked_phases['nacl'].scale.free = True\n", + "project.experiments['xray_pdf'].peak.damp_q.free = True\n", + "project.experiments['xray_pdf'].peak.sharp_delta_2.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "22", + "metadata": {}, + "source": [ + "## Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "24", + "metadata": {}, + "source": [ + "## Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='xray_pdf')" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-12.py b/docs/docs/tutorials/ed-12.py similarity index 100% rename from tutorials/ed-12.py rename to docs/docs/tutorials/ed-12.py diff --git a/docs/docs/tutorials/ed-13.ipynb b/docs/docs/tutorials/ed-13.ipynb new file mode 100644 index 00000000..d35f3628 --- /dev/null +++ b/docs/docs/tutorials/ed-13.ipynb @@ -0,0 +1,2923 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Fitting Powder Diffraction data\n", + "\n", + "This notebook guides you through the Rietveld refinement of crystal\n", + "structures using simulated powder diffraction data. It consists of two\n", + "parts:\n", + "- Introduction: A simple reference fit using silicon (Si) crystal\n", + " structure.\n", + "- Exercise: A more complex fit using La₀.₅Ba₀.₅CoO₃ (LBCO) crystal\n", + " structure.\n", + "\n", + "## 🛠️ Import Library\n", + "\n", + "We start by importing the necessary library for the analysis. In this\n", + "notebook, we use the EasyDiffraction library. As mentioned in the\n", + "introduction to EasyScience, EasyDiffraction is built on that\n", + "framework and offers a high-level interface focused specifically for\n", + "diffraction analysis.\n", + "\n", + "This notebook is self-contained and designed for hands-on learning.\n", + "However, if you're interested in exploring more advanced features or\n", + "learning about additional capabilities of the EasyDiffraction library,\n", + "please refer to the official documentation:\n", + "https://docs.easydiffraction.org/lib\n", + "\n", + "Depending on your requirements, you may choose to import only specific\n", + "classes. However, for the sake of simplicity in this notebook, we will\n", + "import the entire library." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/first-steps/#importing-easydiffraction)\n", + "for more details about importing the EasyDiffraction library and its\n", + "components." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## 📘 Introduction: Simple Reference Fit – Si\n", + "\n", + "Before diving into the more complex fitting exercise with the\n", + "La₀.₅Ba₀.₅CoO₃ (LBCO) crystal structure, let's start with a simpler\n", + "example using the silicon (Si) crystal structure. This will help us\n", + "understand the basic concepts and steps involved in fitting a crystal\n", + "structure using powder diffraction data.\n", + "\n", + "For this part of the notebook, we will use the powder diffraction data\n", + "previously simulated using the Si crystal structure.\n", + "\n", + "### 📦 Create a Project – 'reference'\n", + "\n", + "In EasyDiffraction, a project serves as a container for all\n", + "information related to the analysis of a specific experiment or set of\n", + "experiments. It enables you to organize your data, experiments,\n", + "crystal structures, and fitting parameters in an organized manner. You\n", + "can think of it as a folder containing all the essential details about\n", + "your analysis. The project also allows us to visualize both the\n", + "measured and calculated diffraction patterns, among other things." + ] + }, + { + "cell_type": "markdown", + "id": "4", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/project/)\n", + "for more details about creating a project and its purpose in the\n", + "analysis workflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "project_1 = ed.Project(name='reference')" + ] + }, + { + "cell_type": "markdown", + "id": "6", + "metadata": {}, + "source": [ + "You can set the title and description of the project to provide\n", + "context and information about the analysis being performed. This is\n", + "useful for documentation purposes and helps others (or yourself in the\n", + "future) understand the purpose of the project at a glance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.info.title = 'Reference Silicon Fit'\n", + "project_1.info.description = 'Fitting simulated powder diffraction pattern of Si.'" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "### 🔬 Create an Experiment\n", + "\n", + "An experiment represents a specific diffraction measurement performed\n", + "on a specific sample using a particular instrument. It contains\n", + "details about the measured data, instrument parameters, and other\n", + "relevant information." + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/)\n", + "for more details about experiments and their purpose in the analysis\n", + "workflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "data_dir = 'data'\n", + "file_name = 'reduced_Si.xye'\n", + "si_xye_path = f'{data_dir}/{file_name}'" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "Uncomment the following cell if your data reduction failed and the\n", + "reduced data file is missing. In this case, you can download our\n", + "pre-generated reduced data file from the EasyDiffraction repository.\n", + "The `download_data` function will not overwrite an existing file\n", + "unless you set `overwrite=True`, so it's safe to run even if the\n", + "file is already present." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "si_xye_path = ed.download_data(id=17, destination=data_dir)" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "Now we can create the experiment and load the measured data. In this\n", + "case, the experiment is defined as a powder diffraction measurement\n", + "using time-of-flight neutrons. The measured data is loaded from a file\n", + "containing the reduced diffraction pattern of Si from the data\n", + "reduction notebook." + ] + }, + { + "cell_type": "markdown", + "id": "14", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#defining-an-experiment-manually)\n", + "for more details about different types of experiments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.experiments.add_from_data_path(\n", + " name='sim_si',\n", + " data_path=si_xye_path,\n", + " sample_form='powder',\n", + " beam_mode='time-of-flight',\n", + " radiation_probe='neutron',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "16", + "metadata": {}, + "source": [ + "#### Inspect Measured Data\n", + "\n", + "After creating the experiment, we can examine the measured data. The\n", + "measured data consists of a diffraction pattern having time-of-flight\n", + "(TOF) values and corresponding intensities. The TOF values are given\n", + "in microseconds (μs), and the intensities are in arbitrary units.\n", + "\n", + "The data is stored in XYE format, a simple text format containing\n", + "three columns: TOF, intensity, and intensity error (if available)." + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#measured-data-category)\n", + "for more details about the measured data and its format.\n", + "\n", + "To visualize the measured data, we can use the `plot_meas` method of\n", + "the project. Before plotting, we need to set the plotting engine to\n", + "'plotly', which provides interactive visualizations." + ] + }, + { + "cell_type": "markdown", + "id": "18", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://easyscience.github.io/diffraction-lib/user-guide/first-steps/#supported-plotters)\n", + "for more details about setting the plotting engine." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19", + "metadata": {}, + "outputs": [], + "source": [ + "# Keep the auto-selected engine. Alternatively, you can uncomment the\n", + "# line below to explicitly set the engine to the required one.\n", + "# project.plotter.engine = 'plotly'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.plot_meas(expt_name='sim_si')" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "If you zoom in on the highest TOF peak (around 120,000 μs), you will\n", + "notice that it has a broad and unusual shape. This distortion, along\n", + "with additional effects on the low TOF peaks, is most likely an\n", + "artifact related to the simplifications made during the simulation\n", + "and/or reduction process and is currently under investigation.\n", + "However, this is outside the scope of this school. Therefore, we will\n", + "simply exclude both the low and high TOF regions from the analysis by\n", + "adding an excluded regions to the experiment.\n", + "\n", + "In real experiments, it is often necessary to exclude certain regions\n", + "from the measured data. For example, the direct beam can significantly\n", + "increase the background at very low angles, making those parts of the\n", + "diffractogram unreliable. Additionally, sample environment components\n", + "may introduce unwanted peaks. In such cases, excluding specific\n", + "regions is often simpler and more effective than modeling them with an\n", + "additional sample phase." + ] + }, + { + "cell_type": "markdown", + "id": "22", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#excluded-regions-category)\n", + "for more details about excluding regions from the measured data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.experiments['sim_si'].excluded_regions.create(id='1', start=0, end=55000)\n", + "project_1.experiments['sim_si'].excluded_regions.create(id='2', start=105500, end=200000)" + ] + }, + { + "cell_type": "markdown", + "id": "24", + "metadata": {}, + "source": [ + "To visualize the effect of excluding the high TOF region, we can plot\n", + "the measured data again. The excluded region will be omitted from the\n", + "plot and is not used in the fitting process." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.plot_meas(expt_name='sim_si')" + ] + }, + { + "cell_type": "markdown", + "id": "26", + "metadata": {}, + "source": [ + "#### Set Instrument Parameters\n", + "\n", + "After the experiment is created and measured data is loaded, we need\n", + "to set the instrument parameters.\n", + "\n", + "In this type of experiment, the instrument parameters define how the\n", + "measured data is converted between d-spacing and time-of-flight (TOF)\n", + "during the data reduction process as well as the angular position of\n", + "the detector. So, we put values based on those from the reduction.\n", + "These values can be found in the header of the corresponding .XYE\n", + "file. Their names are `two_theta` and `DIFC`, which stand for the\n", + "two-theta angle and the linear conversion factor from d-spacing to\n", + "TOF, respectively.\n", + "\n", + "You can set them manually, but it is more convenient to use the\n", + "`get_value_from_xye_header` function from the EasyDiffraction library." + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#instrument-category)\n", + "for more details about the instrument parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.experiments['sim_si'].instrument.setup_twotheta_bank = ed.get_value_from_xye_header(\n", + " si_xye_path, 'two_theta'\n", + ")\n", + "project_1.experiments['sim_si'].instrument.calib_d_to_tof_linear = ed.get_value_from_xye_header(\n", + " si_xye_path, 'DIFC'\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "Before proceeding, let's take a quick look at the concept of\n", + "parameters in EasyDiffraction, which is similar to the parameter\n", + "concept in EasyScience. The current version of EasyDiffraction is\n", + "transitioning to reuse the parameter system from EasyScience.\n", + "\n", + "That is, every parameter is an object, which has different attributes,\n", + "such as `value`, `units`, etc. To display the parameter of interest,\n", + "you can simply print the parameter object.\n", + "\n", + "For example, to display the linear conversion factor from d-spacing to\n", + "TOF, which is the `calib_d_to_tof_linear` parameter, you can do the\n", + "following:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "print(project_1.experiments['sim_si'].instrument.calib_d_to_tof_linear)" + ] + }, + { + "cell_type": "markdown", + "id": "31", + "metadata": {}, + "source": [ + "The `value` attribute represents the current value of the parameter as\n", + "a float. You can access it directly by using the `value` attribute of\n", + "the parameter. This is useful when you want to use the parameter value\n", + "in calculations or when you want to assign it to another parameter.\n", + "For example, to get only the value of the same parameter as floating\n", + "point number, but not the whole object, you can do the following:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "print(project_1.experiments['sim_si'].instrument.calib_d_to_tof_linear.value)" + ] + }, + { + "cell_type": "markdown", + "id": "33", + "metadata": {}, + "source": [ + "Note that to set the value of the parameter, you can simply assign a\n", + "new value to the parameter object without using the `value` attribute,\n", + "as we did above." + ] + }, + { + "cell_type": "markdown", + "id": "34", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/parameters/)\n", + "for more details about parameters in EasyDiffraction and their\n", + "attributes." + ] + }, + { + "cell_type": "markdown", + "id": "35", + "metadata": {}, + "source": [ + "#### Set Peak Profile Parameters\n", + "\n", + "The next set of parameters is needed to define the peak profile used\n", + "in the fitting process. The peak profile describes the shape of the\n", + "diffraction peaks. They include parameters for the broadening and\n", + "asymmetry of the peaks.\n", + "\n", + "There are several commonly used peak profile functions:\n", + "- **Gaussian**: Describes peaks with a symmetric bell-shaped curve,\n", + " often used when instrumental broadening dominates. [Click for more\n", + " details.](https://mantidproject.github.io/docs-versioned/v6.1.0/fitting/fitfunctions/Gaussian.html)\n", + "- **Lorentzian**: Produces narrower central peaks with longer tails,\n", + " frequently used to model size broadening effects. [Click for more\n", + " details.](https://mantidproject.github.io/docs-versioned/v6.1.0/fitting/fitfunctions/Lorentzian.html)\n", + "- **Pseudo-Voigt**: A linear combination of Gaussian and Lorentzian\n", + " components, providing flexibility to represent real diffraction\n", + " peaks. [Click for more\n", + " details.](https://mantidproject.github.io/docs-versioned/v6.1.0/fitting/fitfunctions/PseudoVoigt.html)\n", + "- **Pseudo-Voigt convoluted with Ikeda-Carpenter**: Incorporates the\n", + " asymmetry introduced by the neutron pulse shape in time-of-flight\n", + " instruments. This is a common choice for TOF neutron powder\n", + " diffraction data. [Click for more\n", + " details.](https://docs.mantidproject.org/v6.1.0/fitting/fitfunctions/IkedaCarpenterPV.html)\n", + "\n", + "Here, we use a pseudo-Voigt peak profile function with Ikeda-Carpenter\n", + "asymmetry.\n", + "\n", + "The parameter values are typically determined experimentally on the\n", + "same instrument and under the same configuration as the data being\n", + "analyzed, using measurements of a standard sample. In our case, the Si\n", + "sample serves as this standard reference. We will refine the peak\n", + "profile parameters here, and these refined values will be used as\n", + "starting points for the more complex fit in the next part of the\n", + "notebook. For this initial fit, we will provide reasonable physical\n", + "guesses as starting values." + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#peak-category)\n", + "for more details about the peak profile types." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.experiments['sim_si'].peak_profile_type = 'pseudo-voigt * ikeda-carpenter'\n", + "project_1.experiments['sim_si'].peak.broad_gauss_sigma_0 = 69498\n", + "project_1.experiments['sim_si'].peak.broad_gauss_sigma_1 = -55578\n", + "project_1.experiments['sim_si'].peak.broad_gauss_sigma_2 = 14560\n", + "project_1.experiments['sim_si'].peak.broad_mix_beta_0 = 0.0019\n", + "project_1.experiments['sim_si'].peak.broad_mix_beta_1 = 0.0137\n", + "project_1.experiments['sim_si'].peak.asym_alpha_0 = -0.0055\n", + "project_1.experiments['sim_si'].peak.asym_alpha_1 = 0.0147" + ] + }, + { + "cell_type": "markdown", + "id": "38", + "metadata": {}, + "source": [ + "#### Set Background\n", + "\n", + "The background of the diffraction pattern represents the portion of\n", + "the pattern that is not related to the crystal structure of the\n", + "sample. It's rather represents noise and other sources of scattering\n", + "that can affect the measured intensities. This includes contributions\n", + "from the instrument, the sample holder, the sample environment, and\n", + "other sources of incoherent scattering.\n", + "\n", + "The background can be modeled in various ways. In this example, we\n", + "will use a simple line segment background, which is a common approach\n", + "for powder diffraction data. The background intensity at any point is\n", + "defined by linear interpolation between neighboring points. The\n", + "background points are selected to span the range of the diffraction\n", + "pattern while avoiding the peaks.\n", + "\n", + "We will add several background points at specific TOF values (in μs)\n", + "and corresponding intensity values. These points are chosen to\n", + "represent the background level in the diffraction pattern free from\n", + "any peaks.\n", + "\n", + "The background points are added using the `add` method of the\n", + "`background` object. The `x` parameter represents the TOF value, and\n", + "the `y` parameter represents the intensity value at that TOF.\n", + "\n", + "Let's set all the background points at a constant value of 0.01, which\n", + "can be roughly estimated by the eye, and we will refine them later\n", + "during the fitting process." + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#background-category)\n", + "for more details about the background and its types." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.experiments['sim_si'].background_type = 'line-segment'\n", + "project_1.experiments['sim_si'].background.create(id='1', x=50000, y=0.01)\n", + "project_1.experiments['sim_si'].background.create(id='2', x=60000, y=0.01)\n", + "project_1.experiments['sim_si'].background.create(id='3', x=70000, y=0.01)\n", + "project_1.experiments['sim_si'].background.create(id='4', x=80000, y=0.01)\n", + "project_1.experiments['sim_si'].background.create(id='5', x=90000, y=0.01)\n", + "project_1.experiments['sim_si'].background.create(id='6', x=100000, y=0.01)\n", + "project_1.experiments['sim_si'].background.create(id='7', x=110000, y=0.01)" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "### 🧩 Create a Structure – Si\n", + "\n", + "After setting up the experiment, we need to create a structure that\n", + "describes the crystal structure of the sample being analyzed.\n", + "\n", + "In this case, we will create a structure for silicon (Si) with a\n", + "cubic crystal structure. The structure contains information about\n", + "the space group, lattice parameters, atomic positions of the atoms in\n", + "the unit cell, atom types, occupancies and atomic displacement\n", + "parameters. The structure is essential for the fitting process, as\n", + "it is used to calculate the expected diffraction pattern.\n", + "\n", + "EasyDiffraction refines the crystal structure of the sample, but does\n", + "not solve it. Therefore, we need a good starting point with reasonable\n", + "structural parameters.\n", + "\n", + "Here, we define the Si structure as a cubic structure. As this is a\n", + "cubic structure, we only need to define the single lattice parameter,\n", + "which is the length of the unit cell edge. The Si crystal structure\n", + "has a single atom in the unit cell, which is located at the origin (0,\n", + "0, 0) of the unit cell. The symmetry of this site is defined by the\n", + "Wyckoff letter 'a'. The atomic displacement parameter defines the\n", + "thermal vibrations of the atoms in the unit cell and is presented as\n", + "an isotropic parameter (B_iso).\n", + "\n", + "Sometimes, the initial crystal structure parameters can be obtained\n", + "from one of the crystallographic databases, like for example the\n", + "Crystallography Open Database (COD). In this case, we use the COD\n", + "entry for silicon as a reference for the initial crystal structure\n", + "model: https://www.crystallography.net/cod/4507226.html\n", + "\n", + "Usually, the crystal structure parameters are provided in a CIF file\n", + "format, which is a standard format for crystallographic data. An\n", + "example of a CIF file for silicon is shown below. The CIF file\n", + "contains the space group information, unit cell parameters, and atomic\n", + "positions." + ] + }, + { + "cell_type": "markdown", + "id": "42", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/data-format/)\n", + "for more details about the CIF format and its use in EasyDiffraction." + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "```\n", + "data_si\n", + "\n", + "_space_group.name_H-M_alt \"F d -3 m\"\n", + "_space_group.IT_coordinate_system_code 2\n", + "\n", + "_cell.length_a 5.43\n", + "_cell.length_b 5.43\n", + "_cell.length_c 5.43\n", + "_cell.angle_alpha 90.0\n", + "_cell.angle_beta 90.0\n", + "_cell.angle_gamma 90.0\n", + "\n", + "loop_\n", + "_atom_site.label\n", + "_atom_site.type_symbol\n", + "_atom_site.fract_x\n", + "_atom_site.fract_y\n", + "_atom_site.fract_z\n", + "_atom_site.wyckoff_letter\n", + "_atom_site.occupancy\n", + "_atom_site.ADP_type\n", + "_atom_site.B_iso_or_equiv\n", + "Si Si 0 0 0 a 1.0 Biso 0.89\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "44", + "metadata": {}, + "source": [ + "As with adding the experiment in the previous step, we will create a\n", + "default structure and then modify its parameters to match the Si\n", + "structure." + ] + }, + { + "cell_type": "markdown", + "id": "45", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/structure/)\n", + "for more details about structures and their purpose in the data\n", + "analysis workflow." + ] + }, + { + "cell_type": "markdown", + "id": "46", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.structures.create(name='si')" + ] + }, + { + "cell_type": "markdown", + "id": "48", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "markdown", + "id": "49", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/structure/#space-group-category)\n", + "for more details about the space group." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.structures['si'].space_group.name_h_m = 'F d -3 m'\n", + "project_1.structures['si'].space_group.it_coordinate_system_code = '2'" + ] + }, + { + "cell_type": "markdown", + "id": "51", + "metadata": {}, + "source": [ + "#### Set Lattice Parameters" + ] + }, + { + "cell_type": "markdown", + "id": "52", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/structure/#cell-category)\n", + "for more details about the unit cell parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.structures['si'].cell.length_a = 5.43" + ] + }, + { + "cell_type": "markdown", + "id": "54", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "markdown", + "id": "55", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/structure/#atom-sites-category)\n", + "for more details about the atom sites category." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.structures['si'].atom_sites.create(\n", + " label='Si',\n", + " type_symbol='Si',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.89,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "57", + "metadata": {}, + "source": [ + "### 🔗 Assign Structure to Experiment\n", + "\n", + "Now we need to assign, or link, this structure to the experiment\n", + "created above. This linked crystallographic phase will be used to\n", + "calculate the expected diffraction pattern based on the crystal\n", + "structure defined in the structure." + ] + }, + { + "cell_type": "markdown", + "id": "58", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#linked-phases-category)\n", + "for more details about linking a structure to an experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.experiments['sim_si'].linked_phases.create(id='si', scale=1.0)" + ] + }, + { + "cell_type": "markdown", + "id": "60", + "metadata": {}, + "source": [ + "### 🚀 Analyze and Fit the Data\n", + "\n", + "After setting up the experiment and structure, we can now analyze\n", + "the measured diffraction pattern and perform the fit. Building on the\n", + "analogies from the EasyScience library and the previous notebooks, we\n", + "can say that all the parameters we introduced earlier — those defining\n", + "the structure (crystal structure parameters) and the experiment\n", + "(instrument, background, and peak profile parameters) — together form\n", + "the complete set of parameters that can be refined during the fitting\n", + "process.\n", + "\n", + "Unlike in the previous analysis notebooks, we will not create a\n", + "**math_model** object here. The mathematical model used to calculate\n", + "the expected diffraction pattern is already defined in the library and\n", + "will be applied automatically during the fitting process." + ] + }, + { + "cell_type": "markdown", + "id": "61", + "metadata": { + "title": "**Reminder:**" + }, + "source": [ + "\n", + "The fitting process involves comparing the measured diffraction\n", + "pattern with the calculated diffraction pattern based on the crystal\n", + "structure and instrument parameters. The goal is to adjust the\n", + "parameters of the structure and the experiment to minimize the\n", + "difference between the measured and calculated diffraction patterns.\n", + "This is done by refining the parameters of the structure and the\n", + "instrument settings to achieve a better fit." + ] + }, + { + "cell_type": "markdown", + "id": "62", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/analysis/#minimization-optimization)\n", + "for more details about the fitting process in EasyDiffraction." + ] + }, + { + "cell_type": "markdown", + "id": "63", + "metadata": {}, + "source": [ + "#### Set Fit Parameters\n", + "\n", + "To perform the fit, we need to specify the refinement parameters.\n", + "These are the parameters that will be adjusted during the fitting\n", + "process to minimize the difference between the measured and calculated\n", + "diffraction patterns. This is done by setting the `free` attribute of\n", + "the corresponding parameters to `True`.\n", + "\n", + "Note: setting `param.free = True` is equivalent to using `param.fixed\n", + "= False` in the EasyScience library.\n", + "\n", + "We will refine the scale factor of the Si phase, the intensities of\n", + "the background points as well as the peak profile parameters. The\n", + "structure parameters of the Si phase will not be refined, as this\n", + "sample is considered a reference sample with known parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.experiments['sim_si'].linked_phases['si'].scale.free = True\n", + "\n", + "for line_segment in project_1.experiments['sim_si'].background:\n", + " line_segment.y.free = True\n", + "\n", + "project_1.experiments['sim_si'].peak.broad_gauss_sigma_0.free = True\n", + "project_1.experiments['sim_si'].peak.broad_gauss_sigma_1.free = True\n", + "project_1.experiments['sim_si'].peak.broad_gauss_sigma_2.free = True\n", + "project_1.experiments['sim_si'].peak.broad_mix_beta_0.free = True\n", + "project_1.experiments['sim_si'].peak.broad_mix_beta_1.free = True\n", + "project_1.experiments['sim_si'].peak.asym_alpha_0.free = True\n", + "project_1.experiments['sim_si'].peak.asym_alpha_1.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "65", + "metadata": {}, + "source": [ + "#### Show Free Parameters\n", + "\n", + "We can check which parameters are free to be refined by calling the\n", + "`show_free_params` method of the `analysis` object of the project." + ] + }, + { + "cell_type": "markdown", + "id": "66", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://easyscience.github.io/diffraction-lib/user-guide/first-steps/#available-parameters)\n", + "for more details on how to\n", + "- show all parameters of the project,\n", + "- show all fittable parameters, and\n", + "- show only free parameters of the project." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "68", + "metadata": {}, + "source": [ + "#### Visualize Diffraction Patterns\n", + "\n", + "Before performing the fit, we can visually compare the measured\n", + "diffraction pattern with the calculated diffraction pattern based on\n", + "the initial parameters of the structure and the instrument. This\n", + "provides an indication of how well the initial parameters match the\n", + "measured data. The `plot_meas_vs_calc` method of the project allows\n", + "this comparison." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.plot_meas_vs_calc(expt_name='sim_si')" + ] + }, + { + "cell_type": "markdown", + "id": "70", + "metadata": {}, + "source": [ + "#### Run Fitting\n", + "\n", + "We can now perform the fit using the `fit` method of the `analysis`\n", + "object of the project." + ] + }, + { + "cell_type": "markdown", + "id": "71", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/analysis/#perform-fit)\n", + "for more details about the fitting process." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.analysis.fit()\n", + "project_1.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "73", + "metadata": {}, + "source": [ + "#### Check Fit Results\n", + "\n", + "You can see that the agreement between the measured and calculated\n", + "diffraction patterns is now much improved and that the intensities of\n", + "the calculated peaks align much better with the measured peaks. To\n", + "check the quality of the fit numerically, we can look at the\n", + "goodness-of-fit χ² value and the reliability R-factors. The χ² value\n", + "is a measure of how well the calculated diffraction pattern matches\n", + "the measured pattern, and it is calculated as the sum of the squared\n", + "differences between the measured and calculated intensities, divided\n", + "by the number of data points. Ideally, the χ² value should be close to\n", + "1, indicating a good fit." + ] + }, + { + "cell_type": "markdown", + "id": "74", + "metadata": {}, + "source": [ + "#### Visualize Fit Results\n", + "\n", + "After the fit is completed, we can plot the comparison between the\n", + "measured and calculated diffraction patterns again to see how well the\n", + "fit improved the agreement between the two. The calculated diffraction\n", + "pattern is now based on the refined parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.plot_meas_vs_calc(expt_name='sim_si')" + ] + }, + { + "cell_type": "markdown", + "id": "76", + "metadata": {}, + "source": [ + "#### TOF vs d-spacing\n", + "\n", + "The diffraction pattern is typically analyzed and plotted in the\n", + "time-of-flight (TOF) axis, which represents the time it takes for\n", + "neutrons to travel from the sample to the detector. However, it is\n", + "sometimes more convenient to visualize the diffraction pattern in the\n", + "d-spacing axis, which represents the distance between planes in the\n", + "crystal lattice.\n", + "\n", + "The conversion from d-spacing to TOF was already introduced in the\n", + "data reduction notebook. As a reminder, the two are related through\n", + "the instrument calibration parameters according to the equation:\n", + "\n", + "$$ \\text{TOF} = \\text{offset} + \\text{linear} \\cdot d + \\text{quad}\n", + "\\cdot d^{2}, $$\n", + "\n", + "where `offset`, `linear`, and `quad` are calibration parameters.\n", + "\n", + "In our case, only the `linear` term is used (the\n", + "`calib_d_to_tof_linear` parameter we set earlier). The `offset` and\n", + "`quad` terms were not part of the data reduction and are therefore set\n", + "to 0 by default.\n", + "\n", + "The `plot_meas_vs_calc` method of the project allows us to plot the\n", + "measured and calculated diffraction patterns in the d-spacing axis by\n", + "setting the `d_spacing` parameter to `True`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.plot_meas_vs_calc(expt_name='sim_si', x='d_spacing')" + ] + }, + { + "cell_type": "markdown", + "id": "78", + "metadata": {}, + "source": [ + "As you can see, the calculated diffraction pattern now matches the\n", + "measured pattern much more closely. Typically, additional experimental\n", + "parameters are included in the refinement process to further improve\n", + "the fit. In this example, the structural parameters are not refined\n", + "because the Si crystal structure is a well-known standard reference\n", + "used to calibrate both the instrument and the experimental setup. The\n", + "refined experimental parameters obtained here will then be applied\n", + "when fitting the crystal structures of other materials.\n", + "\n", + "In the next part of the notebook, we will move to a more advanced case\n", + "and fit a more complex crystal structure: La₀.₅Ba₀.₅CoO₃ (LBCO).\n", + "\n", + "#### Save Project\n", + "\n", + "Before moving on, we can save the project to disk for later use. This\n", + "will preserve the entire project structure, including experiments,\n", + "structures, and fitting results. The project is saved into a\n", + "directory specified by the `dir_path` attribute of the project object." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.save_as(dir_path='powder_diffraction_Si')" + ] + }, + { + "cell_type": "markdown", + "id": "80", + "metadata": {}, + "source": [ + "## 💪 Exercise: Complex Fit – LBCO\n", + "\n", + "Now that you have a basic understanding of the fitting process, we\n", + "will undertake a more complex fit of the La₀.₅Ba₀.₅CoO₃ (LBCO) crystal\n", + "structure using simulated powder diffraction data from the data\n", + "reduction notebook.\n", + "\n", + "You can use the same approach as in the previous part of the notebook,\n", + "but this time we will refine a more complex crystal structure LBCO\n", + "with multiple atoms in the unit cell.\n", + "\n", + "### 📦 Exercise 1: Create a Project\n", + "\n", + "Create a new project for the LBCO fit." + ] + }, + { + "cell_type": "markdown", + "id": "81", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "82", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can use the same approach as in the previous part of the notebook,\n", + "but this time we will create a new project for the LBCO fit." + ] + }, + { + "cell_type": "markdown", + "id": "83", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2 = ed.Project(name='main')\n", + "project_2.info.title = 'La0.5Ba0.5CoO3 Fit'\n", + "project_2.info.description = 'Fitting simulated powder diffraction pattern of La0.5Ba0.5CoO3.'" + ] + }, + { + "cell_type": "markdown", + "id": "85", + "metadata": {}, + "source": [ + "### 🔬 Exercise 2: Define an Experiment\n", + "\n", + "#### Exercise 2.1: Create an Experiment\n", + "\n", + "Create an experiment within the new project and load the reduced\n", + "diffraction pattern for LBCO." + ] + }, + { + "cell_type": "markdown", + "id": "86", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "87", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can use the same approach as in the previous part of the notebook,\n", + "but this time you need to use the data file for LBCO." + ] + }, + { + "cell_type": "markdown", + "id": "88", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "data_dir = 'data'\n", + "file_name = 'reduced_LBCO.xye'\n", + "lbco_xye_path = f'{data_dir}/{file_name}'\n", + "\n", + "# Uncomment the following line if your data reduction failed and the\n", + "# reduced data file is missing.\n", + "lbco_xye_path = ed.download_data(id=18, destination=data_dir)\n", + "\n", + "project_2.experiments.add_from_data_path(\n", + " name='sim_lbco',\n", + " data_path=lbco_xye_path,\n", + " sample_form='powder',\n", + " beam_mode='time-of-flight',\n", + " radiation_probe='neutron',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "90", + "metadata": {}, + "source": [ + "#### Exercise 2.1: Inspect Measured Data\n", + "\n", + "Check the measured data of the LBCO experiment. Are there any peaks\n", + "with the shape similar to those excluded in the Si fit? If so, exclude\n", + "them from this analysis as well." + ] + }, + { + "cell_type": "markdown", + "id": "91", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "92", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can use the `plot_meas` method of the project to visualize the\n", + "measured diffraction pattern. You can also use the `excluded_regions`\n", + "attribute of the experiment to exclude specific regions from the\n", + "analysis as we did in the previous part of the notebook." + ] + }, + { + "cell_type": "markdown", + "id": "93", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.plot_meas(expt_name='sim_lbco')\n", + "\n", + "project_2.experiments['sim_lbco'].excluded_regions.create(id='1', start=0, end=55000)\n", + "project_2.experiments['sim_lbco'].excluded_regions.create(id='2', start=105500, end=200000)\n", + "\n", + "project_2.plot_meas(expt_name='sim_lbco')" + ] + }, + { + "cell_type": "markdown", + "id": "95", + "metadata": {}, + "source": [ + "#### Exercise 2.2: Set Instrument Parameters\n", + "\n", + "Set the instrument parameters for the LBCO experiment." + ] + }, + { + "cell_type": "markdown", + "id": "96", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "97", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the values from the data reduction process for the LBCO and\n", + "follow the same approach as in the previous part of the notebook." + ] + }, + { + "cell_type": "markdown", + "id": "98", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "99", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.experiments['sim_lbco'].instrument.setup_twotheta_bank = ed.get_value_from_xye_header(\n", + " lbco_xye_path, 'two_theta'\n", + ")\n", + "project_2.experiments['sim_lbco'].instrument.calib_d_to_tof_linear = ed.get_value_from_xye_header(\n", + " lbco_xye_path, 'DIFC'\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "100", + "metadata": {}, + "source": [ + "#### Exercise 2.3: Set Peak Profile Parameters\n", + "\n", + "Set the peak profile parameters for the LBCO experiment." + ] + }, + { + "cell_type": "markdown", + "id": "101", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "102", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the values from the\n", + "previous part of the notebook. You can either manually copy the values\n", + "from the Si fit or use the `value` attribute of the parameters from\n", + "the Si experiment to set the initial values for the LBCO experiment.\n", + "This will help us to have a good starting point for the fit." + ] + }, + { + "cell_type": "markdown", + "id": "103", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "104", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "# # Create a reference to the peak profile parameters from the Si\n", + "sim_si_peak = project_1.experiments['sim_si'].peak\n", + "project_2.experiments['sim_lbco'].peak_profile_type = 'pseudo-voigt * ikeda-carpenter'\n", + "project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_0 = sim_si_peak.broad_gauss_sigma_0.value\n", + "project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_1 = sim_si_peak.broad_gauss_sigma_1.value\n", + "project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_2 = sim_si_peak.broad_gauss_sigma_2.value\n", + "project_2.experiments['sim_lbco'].peak.broad_mix_beta_0 = sim_si_peak.broad_mix_beta_0.value\n", + "project_2.experiments['sim_lbco'].peak.broad_mix_beta_1 = sim_si_peak.broad_mix_beta_1.value\n", + "project_2.experiments['sim_lbco'].peak.asym_alpha_0 = sim_si_peak.asym_alpha_0.value\n", + "project_2.experiments['sim_lbco'].peak.asym_alpha_1 = sim_si_peak.asym_alpha_1.value" + ] + }, + { + "cell_type": "markdown", + "id": "105", + "metadata": {}, + "source": [ + "#### Exercise 2.4: Set Background\n", + "\n", + "Set the background points for the LBCO experiment. What would you\n", + "suggest as the initial intensity value for the background points?" + ] + }, + { + "cell_type": "markdown", + "id": "106", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "107", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the same approach as in the previous part of the notebook, but\n", + "this time you need to set the background points for the LBCO\n", + "experiment. You can zoom in on the measured diffraction pattern to\n", + "determine the approximate background level." + ] + }, + { + "cell_type": "markdown", + "id": "108", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "109", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.experiments['sim_lbco'].background_type = 'line-segment'\n", + "project_2.experiments['sim_lbco'].background.create(id='1', x=50000, y=0.2)\n", + "project_2.experiments['sim_lbco'].background.create(id='2', x=60000, y=0.2)\n", + "project_2.experiments['sim_lbco'].background.create(id='3', x=70000, y=0.2)\n", + "project_2.experiments['sim_lbco'].background.create(id='4', x=80000, y=0.2)\n", + "project_2.experiments['sim_lbco'].background.create(id='5', x=90000, y=0.2)\n", + "project_2.experiments['sim_lbco'].background.create(id='6', x=100000, y=0.2)\n", + "project_2.experiments['sim_lbco'].background.create(id='7', x=110000, y=0.2)" + ] + }, + { + "cell_type": "markdown", + "id": "110", + "metadata": {}, + "source": [ + "### 🧩 Exercise 3: Define a Structure – LBCO\n", + "\n", + "The LBSO structure is not as simple as the Si one, as it contains\n", + "multiple atoms in the unit cell. It is not in COD, so we give you the\n", + "structural parameters in CIF format to create the structure.\n", + "\n", + "Note that those parameters are not necessarily the most accurate ones,\n", + "but they are a good starting point for the fit. The aim of the study\n", + "is to refine the LBCO lattice parameters." + ] + }, + { + "cell_type": "markdown", + "id": "111", + "metadata": {}, + "source": [ + "```\n", + "data_lbco\n", + "\n", + "_space_group.name_H-M_alt \"P m -3 m\"\n", + "_space_group.IT_coordinate_system_code 1\n", + "\n", + "_cell.length_a 3.89\n", + "_cell.length_b 3.89\n", + "_cell.length_c 3.89\n", + "_cell.angle_alpha 90.0\n", + "_cell.angle_beta 90.0\n", + "_cell.angle_gamma 90.0\n", + "\n", + "loop_\n", + "_atom_site.label\n", + "_atom_site.type_symbol\n", + "_atom_site.fract_x\n", + "_atom_site.fract_y\n", + "_atom_site.fract_z\n", + "_atom_site.wyckoff_letter\n", + "_atom_site.occupancy\n", + "_atom_site.ADP_type\n", + "_atom_site.B_iso_or_equiv\n", + "La La 0.0 0.0 0.0 a 0.5 Biso 0.95\n", + "Ba Ba 0.0 0.0 0.0 a 0.5 Biso 0.95\n", + "Co Co 0.5 0.5 0.5 b 1.0 Biso 0.80\n", + "O O 0.0 0.5 0.5 c 1.0 Biso 1.66\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "112", + "metadata": {}, + "source": [ + "Note that the `occupancy` of the La and Ba atoms is 0.5\n", + "and those atoms are located in the same position (0, 0, 0) in the unit\n", + "cell. This means that an extra attribute `occupancy` needs to be set\n", + "for those atoms later in the structure.\n", + "\n", + "We model the La/Ba site using the virtual crystal approximation. In\n", + "this approach, the scattering is taken as a weighted average of La and\n", + "Ba. This reproduces the average diffraction pattern well but does not\n", + "capture certain real-world effects.\n", + "\n", + "The edge cases are:\n", + "- **Random distribution**. La and Ba atoms are placed randomly. The\n", + " Bragg peaks still match the average structure, but the pattern also\n", + " shows extra background (diffuse scattering) between the peaks, but\n", + " this is usually neglected in the analysis.\n", + "- **Perfect ordering**. La and Ba arrange themselves in a regular\n", + " pattern, creating a larger repeating unit. This gives rise to extra\n", + " peaks (\"superlattice reflections\") and changes the intensity of\n", + " some existing peaks.\n", + "- **Virtual crystal approximation (our model)**. We replace the site\n", + " with a single \"virtual atom\" that averages La and Ba. This gives\n", + " the correct average Bragg peaks but leaves out the extra background\n", + " of the random case and the extra peaks of the ordered case." + ] + }, + { + "cell_type": "markdown", + "id": "113", + "metadata": {}, + "source": [ + "#### Exercise 3.1: Create Structure\n", + "\n", + "Add a structure for LBCO to the project. The structure\n", + "parameters will be set in the next exercises." + ] + }, + { + "cell_type": "markdown", + "id": "114", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "115", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can use the same approach as in the previous part of the notebook,\n", + "but this time you need to use the name corresponding to the LBCO\n", + "structure, e.g. 'lbco'." + ] + }, + { + "cell_type": "markdown", + "id": "116", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "117", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.structures.create(name='lbco')" + ] + }, + { + "cell_type": "markdown", + "id": "118", + "metadata": {}, + "source": [ + "#### Exercise 3.2: Set Space Group\n", + "\n", + "Set the space group for the LBCO structure." + ] + }, + { + "cell_type": "markdown", + "id": "119", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "120", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the space group name and IT coordinate system code from the CIF\n", + "data." + ] + }, + { + "cell_type": "markdown", + "id": "121", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "122", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.structures['lbco'].space_group.name_h_m = 'P m -3 m'\n", + "project_2.structures['lbco'].space_group.it_coordinate_system_code = '1'" + ] + }, + { + "cell_type": "markdown", + "id": "123", + "metadata": {}, + "source": [ + "#### Exercise 3.3: Set Lattice Parameters\n", + "\n", + "Set the lattice parameters for the LBCO structure." + ] + }, + { + "cell_type": "markdown", + "id": "124", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "125", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the lattice parameters from the CIF data." + ] + }, + { + "cell_type": "markdown", + "id": "126", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "127", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.structures['lbco'].cell.length_a = 3.88" + ] + }, + { + "cell_type": "markdown", + "id": "128", + "metadata": {}, + "source": [ + "#### Exercise 3.4: Set Atom Sites\n", + "\n", + "Set the atom sites for the LBCO structure." + ] + }, + { + "cell_type": "markdown", + "id": "129", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "130", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the atom sites from the CIF data. You can use the `add` method of\n", + "the `atom_sites` attribute of the structure to add the atom sites." + ] + }, + { + "cell_type": "markdown", + "id": "131", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "132", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.structures['lbco'].atom_sites.create(\n", + " label='La',\n", + " type_symbol='La',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.95,\n", + " occupancy=0.5,\n", + ")\n", + "project_2.structures['lbco'].atom_sites.create(\n", + " label='Ba',\n", + " type_symbol='Ba',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.95,\n", + " occupancy=0.5,\n", + ")\n", + "project_2.structures['lbco'].atom_sites.create(\n", + " label='Co',\n", + " type_symbol='Co',\n", + " fract_x=0.5,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='b',\n", + " b_iso=0.80,\n", + ")\n", + "project_2.structures['lbco'].atom_sites.create(\n", + " label='O',\n", + " type_symbol='O',\n", + " fract_x=0,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='c',\n", + " b_iso=1.66,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "133", + "metadata": {}, + "source": [ + "### 🔗 Exercise 4: Assign Structure to Experiment\n", + "\n", + "Now assign the LBCO structure to the experiment created above." + ] + }, + { + "cell_type": "markdown", + "id": "134", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "135", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the `linked_phases` attribute of the experiment to link the\n", + "crystal structure." + ] + }, + { + "cell_type": "markdown", + "id": "136", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "137", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.experiments['sim_lbco'].linked_phases.create(id='lbco', scale=1.0)" + ] + }, + { + "cell_type": "markdown", + "id": "138", + "metadata": {}, + "source": [ + "### 🚀 Exercise 5: Analyze and Fit the Data\n", + "\n", + "#### Exercise 5.1: Set Fit Parameters\n", + "\n", + "Select the initial set of parameters to be refined during the fitting\n", + "process." + ] + }, + { + "cell_type": "markdown", + "id": "139", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "140", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can start with the scale factor and the background points, as in\n", + "the Si fit." + ] + }, + { + "cell_type": "markdown", + "id": "141", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "142", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.experiments['sim_lbco'].linked_phases['lbco'].scale.free = True\n", + "\n", + "for line_segment in project_2.experiments['sim_lbco'].background:\n", + " line_segment.y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "143", + "metadata": {}, + "source": [ + "#### Exercise 5.2: Run Fitting\n", + "\n", + "Visualize the measured and calculated diffraction patterns before\n", + "fitting and then run the fitting process." + ] + }, + { + "cell_type": "markdown", + "id": "144", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "145", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the `plot_meas_vs_calc` method of the project to visualize the\n", + "measured and calculated diffraction patterns before fitting. Then, use\n", + "the `fit` method of the `analysis` object of the project to perform\n", + "the fitting process." + ] + }, + { + "cell_type": "markdown", + "id": "146", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "147", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.plot_meas_vs_calc(expt_name='sim_lbco')\n", + "\n", + "project_2.analysis.fit()\n", + "project_2.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "148", + "metadata": {}, + "source": [ + "#### Exercise 5.3: Find the Misfit in the Fit\n", + "\n", + "Visualize the measured and calculated diffraction patterns after the\n", + "fit. As you can see, the fit shows noticeable discrepancies. If you\n", + "zoom in on different regions of the pattern, you will observe that all\n", + "the calculated peaks are shifted to the left.\n", + "\n", + "What could be the reason for the misfit?" + ] + }, + { + "cell_type": "markdown", + "id": "149", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "150", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Consider the following options:\n", + "1. The conversion parameters from TOF to d-spacing are not correct.\n", + "2. The lattice parameters of the LBCO phase are not correct.\n", + "3. The peak profile parameters are not correct.\n", + "4. The background points are not correct." + ] + }, + { + "cell_type": "markdown", + "id": "151", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "markdown", + "id": "152", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "\n", + "1. ❌ The conversion parameters from TOF to d-spacing were set based\n", + "on the data reduction step. While they are specific to each dataset\n", + "and thus differ from those used for the Si data, the full reduction\n", + "workflow has already been validated with the Si fit. Therefore, they\n", + "are not the cause of the misfit in this case.\n", + "2. ✅ The lattice parameters of the LBCO phase were set based on the\n", + "CIF data, which is a good starting point, but they are not necessarily\n", + "as accurate as needed for the fit. The lattice parameters may need to\n", + "be refined.\n", + "3. ❌ The peak profile parameters do not change the position of the\n", + "peaks, but rather their shape.\n", + "4. ❌ The background points affect the background level, but not the\n", + "peak positions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "153", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.plot_meas_vs_calc(expt_name='sim_lbco')" + ] + }, + { + "cell_type": "markdown", + "id": "154", + "metadata": {}, + "source": [ + "#### Exercise 5.4: Refine the LBCO Lattice Parameter\n", + "\n", + "To improve the fit, refine the lattice parameters of the LBCO phase." + ] + }, + { + "cell_type": "markdown", + "id": "155", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "156", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "To achieve this, we will set the `free` attribute of the `length_a`\n", + "parameter of the LBCO cell to `True`.\n", + "\n", + "LBCO has a cubic crystal structure (space group `P m -3 m`), which\n", + "means that `length_b` and `length_c` are constrained to be equal to\n", + "`length_a`. Therefore, only `length_a` needs to be refined; the other\n", + "two will be updated automatically. All cell angles are fixed at 90°,\n", + "so they do not require refinement." + ] + }, + { + "cell_type": "markdown", + "id": "157", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "158", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.structures['lbco'].cell.length_a.free = True\n", + "\n", + "project_2.analysis.fit()\n", + "project_2.analysis.show_fit_results()\n", + "\n", + "project_2.plot_meas_vs_calc(expt_name='sim_lbco')" + ] + }, + { + "cell_type": "markdown", + "id": "159", + "metadata": {}, + "source": [ + "One of the main goals of this study was to refine the lattice\n", + "parameter of the LBCO phase. As shown in the updated fit results, the\n", + "overall fit has improved significantly, even though the change in cell\n", + "length is less than 1% of the initial value. This demonstrates how\n", + "even a small adjustment to the lattice parameter can have a\n", + "substantial impact on the quality of the fit." + ] + }, + { + "cell_type": "markdown", + "id": "160", + "metadata": {}, + "source": [ + "#### Exercise 5.5: Visualize the Fit Results in d-spacing\n", + "\n", + "Plot measured vs calculated diffraction patterns in d-spacing instead\n", + "of TOF." + ] + }, + { + "cell_type": "markdown", + "id": "161", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "162", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the `plot_meas_vs_calc` method of the project and set the\n", + "`d_spacing` parameter to `True`." + ] + }, + { + "cell_type": "markdown", + "id": "163", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "164", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing')" + ] + }, + { + "cell_type": "markdown", + "id": "165", + "metadata": {}, + "source": [ + "#### Exercise 5.6: Refine the Peak Profile Parameters\n", + "\n", + "As you can see, the fit is now relatively good and the peak positions\n", + "are much closer to the measured data.\n", + "\n", + "The peak profile parameters were not refined, and their starting\n", + "values were set based on the previous fit of the Si standard sample.\n", + "Although these starting values are reasonable and provide a good\n", + "starting point for the fit, they are not necessarily optimal for the\n", + "LBCO phase. This can be seen while inspecting the individual peaks in\n", + "the diffraction pattern. For example, the calculated curve does not\n", + "perfectly describe the peak at about 1.38 Å, as can be seen below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "166", + "metadata": {}, + "outputs": [], + "source": [ + "project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing', x_min=1.35, x_max=1.40)" + ] + }, + { + "cell_type": "markdown", + "id": "167", + "metadata": {}, + "source": [ + "The peak profile parameters are determined based on both the\n", + "instrument and the sample characteristics, so they can vary when\n", + "analyzing different samples on the same instrument. Therefore, it is\n", + "better to refine them as well.\n", + "\n", + "Select the peak profile parameters to be refined during the fitting\n", + "process." + ] + }, + { + "cell_type": "markdown", + "id": "168", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "169", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can set the `free` attribute of the peak profile parameters to\n", + "`True` to allow the fitting process to adjust them. You can use the\n", + "same approach as in the previous part of the notebook, but this time\n", + "you will refine the peak profile parameters of the LBCO phase." + ] + }, + { + "cell_type": "markdown", + "id": "170", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "171", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_0.free = True\n", + "project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_1.free = True\n", + "project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_2.free = True\n", + "project_2.experiments['sim_lbco'].peak.broad_mix_beta_0.free = True\n", + "project_2.experiments['sim_lbco'].peak.broad_mix_beta_1.free = True\n", + "project_2.experiments['sim_lbco'].peak.asym_alpha_0.free = True\n", + "project_2.experiments['sim_lbco'].peak.asym_alpha_1.free = True\n", + "\n", + "project_2.analysis.fit()\n", + "project_2.analysis.show_fit_results()\n", + "\n", + "project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing', x_min=1.35, x_max=1.40)" + ] + }, + { + "cell_type": "markdown", + "id": "172", + "metadata": {}, + "source": [ + "#### Exercise 5.7: Find Undefined Features\n", + "\n", + "After refining the lattice parameter and the peak profile parameters,\n", + "the fit is significantly improved, but inspect the diffraction pattern\n", + "again. Are you noticing anything undefined?" + ] + }, + { + "cell_type": "markdown", + "id": "173", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "174", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "While the fit is now significantly better, there are still some\n", + "unexplained peaks in the diffraction pattern. These peaks are not\n", + "accounted for by the LBCO phase. For example, if you zoom in on the\n", + "region around 1.6 Å (or 95,000 μs), you will notice that the rightmost\n", + "peak is not explained by the LBCO phase at all." + ] + }, + { + "cell_type": "markdown", + "id": "175", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "176", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing', x_min=1.53, x_max=1.7)" + ] + }, + { + "cell_type": "markdown", + "id": "177", + "metadata": {}, + "source": [ + "#### Exercise 5.8: Identify the Cause of the Unexplained Peaks\n", + "\n", + "Analyze the residual peaks that remain after refining the LBCO phase\n", + "and the peak-profile parameters. Based on their positions and\n", + "characteristics, decide which potential cause best explains the\n", + "misfit." + ] + }, + { + "cell_type": "markdown", + "id": "178", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "179", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Consider the following options:\n", + "1. The LBCO phase is not correctly modeled.\n", + "2. The LBCO phase is not the only phase present in the sample.\n", + "3. The data reduction process introduced artifacts.\n", + "4. The studied sample is not LBCO, but rather a different phase." + ] + }, + { + "cell_type": "markdown", + "id": "180", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "markdown", + "id": "181", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "1. ❌ In principle, this could be the case, as sometimes the presence\n", + "of extra peaks in the diffraction pattern can indicate lower symmetry\n", + "than the one used in the model, or that the model is not complete.\n", + "However, in this case, the LBCO phase is correctly modeled based on\n", + "the CIF data.\n", + "2. ✅ The unexplained peaks are due to the presence of an impurity\n", + "phase in the sample, which is not included in the current model.\n", + "3. ❌ The data reduction process is not likely to introduce such\n", + "specific peaks, as it is tested and verified in the previous part of\n", + "the notebook.\n", + "4. ❌ This could also be the case in real experiments, but in this\n", + "case, we know that the sample is LBCO, as it was simulated based on\n", + "the CIF data." + ] + }, + { + "cell_type": "markdown", + "id": "182", + "metadata": {}, + "source": [ + "#### Exercise 5.9: Identify the impurity phase\n", + "\n", + "Use the positions of the unexplained peaks to identify the most likely\n", + "secondary phase present in the sample." + ] + }, + { + "cell_type": "markdown", + "id": "183", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "184", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Check the positions of the unexplained peaks in the diffraction\n", + "pattern. Compare them with the known diffraction patterns in the\n", + "previous part of the notebook." + ] + }, + { + "cell_type": "markdown", + "id": "185", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "markdown", + "id": "186", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "The unexplained peaks are likely due to the presence of a small amount\n", + "of Si in the LBCO sample. In real experiments, it might happen, e.g.,\n", + "because the sample holder was not cleaned properly after the Si\n", + "experiment.\n", + "\n", + "You can visalize both the patterns of the Si and LBCO phases to\n", + "confirm this hypothesis." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "187", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_1.plot_meas_vs_calc(expt_name='sim_si', x='d_spacing', x_min=1, x_max=1.7)\n", + "project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing', x_min=1, x_max=1.7)" + ] + }, + { + "cell_type": "markdown", + "id": "188", + "metadata": {}, + "source": [ + "#### Exercise 5.10: Create a Second Structure – Si as Impurity\n", + "\n", + "Create a second structure for the Si phase, which is the impurity\n", + "phase identified in the previous step. Link this structure to the\n", + "LBCO experiment." + ] + }, + { + "cell_type": "markdown", + "id": "189", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "190", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can use the same approach as in the previous part of the notebook,\n", + "but this time you need to create a structure for Si and link it to\n", + "the LBCO experiment." + ] + }, + { + "cell_type": "markdown", + "id": "191", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "192", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "# Set Space Group\n", + "project_2.structures.create(name='si')\n", + "project_2.structures['si'].space_group.name_h_m = 'F d -3 m'\n", + "project_2.structures['si'].space_group.it_coordinate_system_code = '2'\n", + "\n", + "# Set Lattice Parameters\n", + "project_2.structures['si'].cell.length_a = 5.43\n", + "\n", + "# Set Atom Sites\n", + "project_2.structures['si'].atom_sites.create(\n", + " label='Si',\n", + " type_symbol='Si',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.89,\n", + ")\n", + "\n", + "# Assign Structure to Experiment\n", + "project_2.experiments['sim_lbco'].linked_phases.create(id='si', scale=1.0)" + ] + }, + { + "cell_type": "markdown", + "id": "193", + "metadata": {}, + "source": [ + "#### Exercise 5.11: Refine the Scale of the Si Phase\n", + "\n", + "Visualize the measured diffraction pattern and the calculated\n", + "diffraction pattern. Check if the Si phase is contributing to the\n", + "calculated diffraction pattern. Refine the scale factor of the Si\n", + "phase to improve the fit." + ] + }, + { + "cell_type": "markdown", + "id": "194", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "195", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can use the `plot_meas_vs_calc` method of the project to visualize\n", + "the patterns. Then, set the `free` attribute of the `scale` parameter\n", + "of the Si phase to `True` to allow the fitting process to adjust the\n", + "scale factor." + ] + }, + { + "cell_type": "markdown", + "id": "196", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "197", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "# Before optimizing the parameters, we can visualize the measured\n", + "# diffraction pattern and the calculated diffraction pattern based on\n", + "# the two phases: LBCO and Si.\n", + "project_2.plot_meas_vs_calc(expt_name='sim_lbco')\n", + "\n", + "# As you can see, the calculated pattern is now the sum of both phases,\n", + "# and Si peaks are visible in the calculated pattern. However, their\n", + "# intensities are much too high. Therefore, we need to refine the scale\n", + "# factor of the Si phase.\n", + "project_2.experiments['sim_lbco'].linked_phases['si'].scale.free = True\n", + "\n", + "# Now we can perform the fit with both phases included.\n", + "project_2.analysis.fit()\n", + "project_2.analysis.show_fit_results()\n", + "\n", + "# Let's plot the measured diffraction pattern and the calculated\n", + "# diffraction pattern both for the full range and for a zoomed-in region\n", + "# around the previously unexplained peak near 95,000 μs. The calculated\n", + "# pattern will be the sum of the two phases.\n", + "project_2.plot_meas_vs_calc(expt_name='sim_lbco')\n", + "project_2.plot_meas_vs_calc(expt_name='sim_lbco', x_min=88000, x_max=101000)" + ] + }, + { + "cell_type": "markdown", + "id": "198", + "metadata": {}, + "source": [ + "All previously unexplained peaks are now accounted for in the pattern,\n", + "and the fit is improved. Some discrepancies in the peak intensities\n", + "remain, but further improvements would require more advanced data\n", + "reduction and analysis, which are beyond the scope of this school.\n", + "\n", + "To review the analysis results, you can generate and print a summary\n", + "report using the `show_report()` method, as demonstrated in the cell\n", + "below. The report includes parameters related to the structure and\n", + "the experiment, such as the refined unit cell parameter `a` of LBCO.\n", + "\n", + "Information about the crystal or magnetic structure, along with\n", + "experimental details, fitting quality, and other relevant data, is\n", + "often submitted to crystallographic journals as part of a scientific\n", + "publication. It can also be deposited in crystallographic databases\n", + "when relevant." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "199", + "metadata": {}, + "outputs": [], + "source": [ + "project_2.summary.show_report()" + ] + }, + { + "cell_type": "markdown", + "id": "200", + "metadata": {}, + "source": [ + "Finally, we save the project to disk to preserve the current state of\n", + "the analysis." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "201", + "metadata": {}, + "outputs": [], + "source": [ + "project_2.save_as(dir_path='powder_diffraction_LBCO_Si')" + ] + }, + { + "cell_type": "markdown", + "id": "202", + "metadata": {}, + "source": [ + "#### Final Remarks\n", + "\n", + "In this part of the notebook, you learned how to use EasyDiffraction\n", + "to refine lattice parameters of a more complex crystal structure,\n", + "La₀.₅Ba₀.₅CoO₃ (LBCO).\n", + "\n", + "In real experiments, you might also refine\n", + "additional parameters, such as atomic positions, occupancies, and\n", + "atomic displacement factors, to achieve an even better fit. For our\n", + "purposes, we'll stop here, as the goal was to give you a starting\n", + "point for analyzing more complex crystal structures with\n", + "EasyDiffraction." + ] + }, + { + "cell_type": "markdown", + "id": "203", + "metadata": {}, + "source": [ + "## 🎁 Bonus\n", + "\n", + "Congratulations — you've now completed the diffraction data analysis\n", + "part of the DMSC Summer School!\n", + "\n", + "If you'd like to keep exploring, the EasyDiffraction library offers\n", + "many additional tutorials and examples on the official documentation\n", + "site: 👉 https://docs.easydiffraction.org/lib/tutorials/\n", + "\n", + "Besides the Python package, EasyDiffraction also comes with a\n", + "graphical user interface (GUI) that lets you perform similar analyses\n", + "without writing code. To be fair, it's not *quite* feature-complete\n", + "compared to the Python library yet — but we're working on it! 🚧\n", + "\n", + "If you prefer a point-and-click interface over coding, the GUI\n", + "provides a user-friendly way to analyze diffraction data. You can\n", + "download it as a standalone application here: 👉\n", + "https://easydiffraction.org\n", + "\n", + "We'd love to hear your feedback on EasyDiffraction — both the library\n", + "and the GUI! 💬" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "tags,title,-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-13.py b/docs/docs/tutorials/ed-13.py similarity index 100% rename from tutorials/ed-13.py rename to docs/docs/tutorials/ed-13.py diff --git a/docs/docs/tutorials/ed-14.ipynb b/docs/docs/tutorials/ed-14.ipynb new file mode 100644 index 00000000..25927d6a --- /dev/null +++ b/docs/docs/tutorials/ed-14.ipynb @@ -0,0 +1,319 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: Tb2TiO7, HEiDi\n", + "\n", + "Crystal structure refinement of Tb2TiO7 using single crystal neutron\n", + "diffraction data from HEiDi at FRM II." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Step 1: Define Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "# Create minimal project without name and description\n", + "project = ed.Project()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Step 2: Define Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "# Download CIF file from repository\n", + "structure_path = ed.download_data(id=20, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add_from_cif_path(structure_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.show_names()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "structure = project.structures['tbti']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites['Tb'].b_iso = 0.0\n", + "structure.atom_sites['Ti'].b_iso = 0.0\n", + "structure.atom_sites['O1'].b_iso = 0.0\n", + "structure.atom_sites['O2'].b_iso = 0.0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11", + "metadata": {}, + "outputs": [], + "source": [ + "structure.show_as_cif()" + ] + }, + { + "cell_type": "markdown", + "id": "12", + "metadata": {}, + "source": [ + "## Step 3: Define Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = ed.download_data(id=19, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_data_path(\n", + " name='heidi',\n", + " data_path=data_path,\n", + " sample_form='single crystal',\n", + " beam_mode='constant wavelength',\n", + " radiation_probe='neutron',\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "experiment = project.experiments['heidi'] # TODO: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.linked_crystal.id = 'tbti'\n", + "experiment.linked_crystal.scale = 1.0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.instrument.setup_wavelength = 0.793" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.extinction.mosaicity = 29820\n", + "experiment.extinction.radius = 30" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "## Step 4: Perform Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='heidi')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.linked_crystal.scale.free = True\n", + "experiment.extinction.radius.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.show_as_cif()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "# Start refinement. All parameters, which have standard uncertainties\n", + "# in the input CIF files, are refined by default.\n", + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "# Show fit results summary\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.show_as_cif()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.show_names()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='heidi')" + ] + }, + { + "cell_type": "markdown", + "id": "28", + "metadata": {}, + "source": [ + "## Step 5: Show Project Summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29", + "metadata": {}, + "outputs": [], + "source": [ + "project.summary.show_report()" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-14.py b/docs/docs/tutorials/ed-14.py similarity index 100% rename from tutorials/ed-14.py rename to docs/docs/tutorials/ed-14.py diff --git a/docs/docs/tutorials/ed-15.ipynb b/docs/docs/tutorials/ed-15.ipynb new file mode 100644 index 00000000..a426f2ee --- /dev/null +++ b/docs/docs/tutorials/ed-15.ipynb @@ -0,0 +1,296 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: Taurine, SENJU\n", + "\n", + "Crystal structure refinement of Taurine using time-of-flight single\n", + "crystal neutron diffraction data from SENJU at J-PARC." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Step 1: Define Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "# Create minimal project without name and description\n", + "project = ed.Project()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Step 2: Define Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "# Download CIF file from repository\n", + "structure_path = ed.download_data(id=21, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add_from_cif_path(structure_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.show_names()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "structure = project.structures['taurine']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "# structure.show_as_cif()" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Step 3: Define Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = ed.download_data(id=22, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_data_path(\n", + " name='senju',\n", + " data_path=data_path,\n", + " sample_form='single crystal',\n", + " beam_mode='time-of-flight',\n", + " radiation_probe='neutron',\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "experiment = project.experiments['senju']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.linked_crystal.id = 'taurine'\n", + "experiment.linked_crystal.scale = 1.0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.extinction.mosaicity = 1000.0\n", + "experiment.extinction.radius = 100.0" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "## Step 4: Perform Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='senju')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.linked_crystal.scale.free = True\n", + "experiment.extinction.radius.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "# experiment.show_as_cif()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "# Start refinement. All parameters, which have standard uncertainties\n", + "# in the input CIF files, are refined by default.\n", + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "# Show fit results summary\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "# experiment.show_as_cif()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.show_names()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='senju')" + ] + }, + { + "cell_type": "markdown", + "id": "26", + "metadata": {}, + "source": [ + "## Step 5: Show Project Summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27", + "metadata": {}, + "outputs": [], + "source": [ + "project.summary.show_report()" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-15.py b/docs/docs/tutorials/ed-15.py similarity index 100% rename from tutorials/ed-15.py rename to docs/docs/tutorials/ed-15.py diff --git a/docs/docs/tutorials/ed-16.ipynb b/docs/docs/tutorials/ed-16.ipynb new file mode 100644 index 00000000..688297e6 --- /dev/null +++ b/docs/docs/tutorials/ed-16.ipynb @@ -0,0 +1,623 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Joint Refinement: Si, Bragg + PDF\n", + "\n", + "This example demonstrates a joint refinement of the Si crystal\n", + "structure combining Bragg diffraction and pair distribution function\n", + "(PDF) analysis. The Bragg experiment uses time-of-flight neutron\n", + "powder diffraction data from SEPD at Argonne, while the PDF\n", + "experiment uses data from NOMAD at SNS. A single shared Si structure\n", + "is refined simultaneously against both datasets." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import ExperimentFactory\n", + "from easydiffraction import Project\n", + "from easydiffraction import StructureFactory\n", + "from easydiffraction import download_data" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Define Structure\n", + "\n", + "A single Si structure is shared between the Bragg and PDF\n", + "experiments. Structural parameters refined against both datasets\n", + "simultaneously.\n", + "\n", + "#### Create Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "structure = StructureFactory.from_scratch(name='si')" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "structure.space_group.name_h_m = 'F d -3 m'\n", + "structure.space_group.it_coordinate_system_code = '1'" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a = 5.42" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites.create(\n", + " label='Si',\n", + " type_symbol='Si',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.2,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Define Experiments\n", + "\n", + "Two experiments are defined: one for Bragg diffraction and one for\n", + "PDF analysis. Both are linked to the same Si structure.\n", + "\n", + "### Experiment 1: Bragg (SEPD, TOF)\n", + "\n", + "#### Download Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "bragg_data_path = download_data(id=7, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "bragg_expt = ExperimentFactory.from_data_path(\n", + " name='sepd', data_path=bragg_data_path, beam_mode='time-of-flight'\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "bragg_expt.instrument.setup_twotheta_bank = 144.845\n", + "bragg_expt.instrument.calib_d_to_tof_offset = -9.2\n", + "bragg_expt.instrument.calib_d_to_tof_linear = 7476.91\n", + "bragg_expt.instrument.calib_d_to_tof_quad = -1.54" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "bragg_expt.peak_profile_type = 'pseudo-voigt * ikeda-carpenter'\n", + "bragg_expt.peak.broad_gauss_sigma_0 = 5.0\n", + "bragg_expt.peak.broad_gauss_sigma_1 = 45.0\n", + "bragg_expt.peak.broad_gauss_sigma_2 = 1.0\n", + "bragg_expt.peak.broad_mix_beta_0 = 0.04221\n", + "bragg_expt.peak.broad_mix_beta_1 = 0.00946\n", + "bragg_expt.peak.asym_alpha_0 = 0.0\n", + "bragg_expt.peak.asym_alpha_1 = 0.5971" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "bragg_expt.background_type = 'line-segment'\n", + "for x in range(0, 35000, 5000):\n", + " bragg_expt.background.create(id=str(x), x=x, y=200)" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "bragg_expt.linked_phases.create(id='si', scale=13.0)" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "### Experiment 2: PDF (NOMAD, TOF)\n", + "\n", + "#### Download Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "pdf_data_path = download_data(id=5, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "25", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "pdf_expt = ExperimentFactory.from_data_path(\n", + " name='nomad',\n", + " data_path=pdf_data_path,\n", + " beam_mode='time-of-flight',\n", + " scattering_type='total',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "#### Set Peak Profile (PDF Parameters)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], + "source": [ + "pdf_expt.peak.damp_q = 0.02\n", + "pdf_expt.peak.broad_q = 0.02\n", + "pdf_expt.peak.cutoff_q = 35.0\n", + "pdf_expt.peak.sharp_delta_1 = 0.001\n", + "pdf_expt.peak.sharp_delta_2 = 4.0\n", + "pdf_expt.peak.damp_particle_diameter = 0" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "pdf_expt.linked_phases.create(id='si', scale=1.0)" + ] + }, + { + "cell_type": "markdown", + "id": "31", + "metadata": {}, + "source": [ + "## Define Project\n", + "\n", + "The project object manages the shared structure, both experiments,\n", + "and the analysis.\n", + "\n", + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()" + ] + }, + { + "cell_type": "markdown", + "id": "33", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add(structure)" + ] + }, + { + "cell_type": "markdown", + "id": "35", + "metadata": {}, + "source": [ + "#### Add Experiments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add(bragg_expt)\n", + "project.experiments.add(pdf_expt)" + ] + }, + { + "cell_type": "markdown", + "id": "37", + "metadata": {}, + "source": [ + "## Perform Analysis\n", + "\n", + "This section shows the joint analysis process. The calculator is\n", + "auto-resolved per experiment: CrysPy for Bragg, PDFfit for PDF.\n", + "\n", + "#### Set Fit Mode and Weights" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit_mode.mode = 'joint'\n", + "project.analysis.joint_fit_experiments.create(id='sepd', weight=0.7)\n", + "project.analysis.joint_fit_experiments.create(id='nomad', weight=0.3)" + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": {}, + "source": [ + "#### Set Minimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated (Before Fit)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', show_residual=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='nomad', show_residual=False)" + ] + }, + { + "cell_type": "markdown", + "id": "44", + "metadata": {}, + "source": [ + "#### Set Fitting Parameters\n", + "\n", + "Shared structural parameters are refined against both datasets\n", + "simultaneously." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a.free = True\n", + "structure.atom_sites['Si'].b_iso.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "46", + "metadata": {}, + "source": [ + "Bragg experiment parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], + "source": [ + "bragg_expt.linked_phases['si'].scale.free = True\n", + "bragg_expt.instrument.calib_d_to_tof_offset.free = True\n", + "bragg_expt.peak.broad_gauss_sigma_0.free = True\n", + "bragg_expt.peak.broad_gauss_sigma_1.free = True\n", + "bragg_expt.peak.broad_gauss_sigma_2.free = True\n", + "for point in bragg_expt.background:\n", + " point.y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "48", + "metadata": {}, + "source": [ + "PDF experiment parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "pdf_expt.linked_phases['si'].scale.free = True\n", + "pdf_expt.peak.damp_q.free = True\n", + "pdf_expt.peak.broad_q.free = True\n", + "pdf_expt.peak.sharp_delta_1.free = True\n", + "pdf_expt.peak.sharp_delta_2.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "50", + "metadata": {}, + "source": [ + "#### Show Free Parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "52", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "54", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated (After Fit)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', show_residual=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='nomad', show_residual=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-16.py b/docs/docs/tutorials/ed-16.py similarity index 100% rename from tutorials/ed-16.py rename to docs/docs/tutorials/ed-16.py diff --git a/docs/docs/tutorials/ed-2.ipynb b/docs/docs/tutorials/ed-2.ipynb new file mode 100644 index 00000000..0a4af0f3 --- /dev/null +++ b/docs/docs/tutorials/ed-2.ipynb @@ -0,0 +1,344 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: LBCO, HRPT\n", + "\n", + "This minimalistic example is designed to show how Rietveld refinement\n", + "can be performed when both the crystal structure and experiment are\n", + "defined directly in code. Only the experimentally measured data is\n", + "loaded from an external file.\n", + "\n", + "For this example, constant-wavelength neutron powder diffraction data\n", + "for La0.5Ba0.5CoO3 from HRPT at PSI is used.\n", + "\n", + "It does not contain any advanced features or options, and includes no\n", + "comments or explanations—these can be found in the other tutorials.\n", + "Default values are used for all parameters if not specified. Only\n", + "essential and self-explanatory code is provided.\n", + "\n", + "The example is intended for users who are already familiar with the\n", + "EasyDiffraction library and want to quickly get started with a simple\n", + "refinement. It is also useful for those who want to see what a\n", + "refinement might look like in code. For a more detailed explanation of\n", + "the code, please refer to the other tutorials." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Step 1: Define Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "project = ed.Project()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Step 2: Define Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.create(name='lbco')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "structure = project.structures['lbco']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure.space_group.name_h_m = 'P m -3 m'\n", + "structure.space_group.it_coordinate_system_code = '1'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a = 3.88" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites.create(\n", + " label='La',\n", + " type_symbol='La',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.5,\n", + " occupancy=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Ba',\n", + " type_symbol='Ba',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.5,\n", + " occupancy=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Co',\n", + " type_symbol='Co',\n", + " fract_x=0.5,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='b',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O',\n", + " type_symbol='O',\n", + " fract_x=0,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='c',\n", + " b_iso=0.5,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Step 3: Define Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = ed.download_data(id=3, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_data_path(\n", + " name='hrpt',\n", + " data_path=data_path,\n", + " sample_form='powder',\n", + " beam_mode='constant wavelength',\n", + " radiation_probe='neutron',\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "experiment = project.experiments['hrpt']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.instrument.setup_wavelength = 1.494\n", + "experiment.instrument.calib_twotheta_offset = 0.6" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.peak.broad_gauss_u = 0.1\n", + "experiment.peak.broad_gauss_v = -0.1\n", + "experiment.peak.broad_gauss_w = 0.1\n", + "experiment.peak.broad_lorentz_y = 0.1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.background.create(id='1', x=10, y=170)\n", + "experiment.background.create(id='2', x=30, y=170)\n", + "experiment.background.create(id='3', x=50, y=170)\n", + "experiment.background.create(id='4', x=110, y=170)\n", + "experiment.background.create(id='5', x=165, y=170)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.excluded_regions.create(id='1', start=0, end=5)\n", + "experiment.excluded_regions.create(id='2', start=165, end=180)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.linked_phases.create(id='lbco', scale=10.0)" + ] + }, + { + "cell_type": "markdown", + "id": "20", + "metadata": {}, + "source": [ + "## Step 4: Perform Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a.free = True\n", + "\n", + "structure.atom_sites['La'].b_iso.free = True\n", + "structure.atom_sites['Ba'].b_iso.free = True\n", + "structure.atom_sites['Co'].b_iso.free = True\n", + "structure.atom_sites['O'].b_iso.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "experiment.instrument.calib_twotheta_offset.free = True\n", + "\n", + "experiment.peak.broad_gauss_u.free = True\n", + "experiment.peak.broad_gauss_v.free = True\n", + "experiment.peak.broad_gauss_w.free = True\n", + "experiment.peak.broad_lorentz_y.free = True\n", + "\n", + "experiment.background['1'].y.free = True\n", + "experiment.background['2'].y.free = True\n", + "experiment.background['3'].y.free = True\n", + "experiment.background['4'].y.free = True\n", + "experiment.background['5'].y.free = True\n", + "\n", + "experiment.linked_phases['lbco'].scale.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-2.py b/docs/docs/tutorials/ed-2.py similarity index 100% rename from tutorials/ed-2.py rename to docs/docs/tutorials/ed-2.py diff --git a/docs/docs/tutorials/ed-3.ipynb b/docs/docs/tutorials/ed-3.ipynb new file mode 100644 index 00000000..0fadb404 --- /dev/null +++ b/docs/docs/tutorials/ed-3.ipynb @@ -0,0 +1,1805 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: LBCO, HRPT\n", + "\n", + "This example demonstrates how to use the EasyDiffraction API in a\n", + "simplified, user-friendly manner that closely follows the GUI workflow\n", + "for a Rietveld refinement of La0.5Ba0.5CoO3 crystal structure using\n", + "constant wavelength neutron powder diffraction data from HRPT at PSI.\n", + "\n", + "It is intended for users with minimal programming experience who want\n", + "to learn how to perform standard crystal structure fitting using\n", + "diffraction data. This script covers creating a project, adding\n", + "crystal structures and experiments, performing analysis, and refining\n", + "parameters.\n", + "\n", + "Only a single import of `easydiffraction` is required, and all\n", + "operations are performed through high-level components of the\n", + "`project` object, such as `project.structures`,\n", + "`project.experiments`, and `project.analysis`. The `project` object is\n", + "the main container for all information." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Step 1: Create a Project\n", + "\n", + "This section explains how to create a project and define its metadata." + ] + }, + { + "cell_type": "markdown", + "id": "4", + "metadata": {}, + "source": [ + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "project = ed.Project(name='lbco_hrpt')" + ] + }, + { + "cell_type": "markdown", + "id": "6", + "metadata": {}, + "source": [ + "#### Set Project Metadata" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "project.info.title = 'La0.5Ba0.5CoO3 at HRPT@PSI'\n", + "project.info.description = \"\"\"This project demonstrates a standard\n", + "refinement of La0.5Ba0.5CoO3, which crystallizes in a perovskite-type\n", + "structure, using neutron powder diffraction data collected in constant\n", + "wavelength mode at the HRPT diffractometer (PSI).\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "#### Show Project Metadata as CIF" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "project.info.show_as_cif()" + ] + }, + { + "cell_type": "markdown", + "id": "10", + "metadata": {}, + "source": [ + "#### Save Project\n", + "\n", + "When saving the project for the first time, you need to specify the\n", + "directory path. In the example below, the project is saved to a\n", + "temporary location defined by the system." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11", + "metadata": {}, + "outputs": [], + "source": [ + "project.save_as(dir_path='lbco_hrpt', temporary=True)" + ] + }, + { + "cell_type": "markdown", + "id": "12", + "metadata": {}, + "source": [ + "#### Set Up Data Plotter" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "Show supported plotting engines." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "project.plotter.show_supported_engines()" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "Show current plotting configuration." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "project.plotter.show_config()" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "Set plotting engine." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "# Keep the auto-selected engine. Alternatively, you can uncomment the\n", + "# line below to explicitly set the engine to the required one.\n", + "# project.plotter.engine = 'plotly'" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "## Step 2: Define Structure\n", + "\n", + "This section shows how to add structures and modify their\n", + "parameters." + ] + }, + { + "cell_type": "markdown", + "id": "20", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.create(name='lbco')" + ] + }, + { + "cell_type": "markdown", + "id": "22", + "metadata": {}, + "source": [ + "#### Show Defined Structures\n", + "\n", + "Show the names of the crystal structures added. These names are used\n", + "to access the structure using the syntax:\n", + "`project.structures[name]`. All structure parameters can be accessed\n", + "via the `project` object." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.show_names()" + ] + }, + { + "cell_type": "markdown", + "id": "24", + "metadata": {}, + "source": [ + "#### Set Space Group\n", + "\n", + "Modify the default space group parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].space_group.name_h_m = 'P m -3 m'\n", + "project.structures['lbco'].space_group.it_coordinate_system_code = '1'" + ] + }, + { + "cell_type": "markdown", + "id": "26", + "metadata": {}, + "source": [ + "#### Set Unit Cell\n", + "\n", + "Modify the default unit cell parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].cell.length_a = 3.88" + ] + }, + { + "cell_type": "markdown", + "id": "28", + "metadata": {}, + "source": [ + "#### Set Atom Sites\n", + "\n", + "Add atom sites to the structure." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].atom_sites.create(\n", + " label='La',\n", + " type_symbol='La',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.5,\n", + " occupancy=0.5,\n", + ")\n", + "project.structures['lbco'].atom_sites.create(\n", + " label='Ba',\n", + " type_symbol='Ba',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.5,\n", + " occupancy=0.5,\n", + ")\n", + "project.structures['lbco'].atom_sites.create(\n", + " label='Co',\n", + " type_symbol='Co',\n", + " fract_x=0.5,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='b',\n", + " b_iso=0.5,\n", + ")\n", + "project.structures['lbco'].atom_sites.create(\n", + " label='O',\n", + " type_symbol='O',\n", + " fract_x=0,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='c',\n", + " b_iso=0.5,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "30", + "metadata": {}, + "source": [ + "#### Show Structure as CIF" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].show_as_cif()" + ] + }, + { + "cell_type": "markdown", + "id": "32", + "metadata": {}, + "source": [ + "#### Show Structure Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].show()" + ] + }, + { + "cell_type": "markdown", + "id": "34", + "metadata": {}, + "source": [ + "#### Save Project State\n", + "\n", + "Save the project state after adding the structure. This ensures\n", + "that all changes are stored and can be accessed later. The project\n", + "state is saved in the directory specified during project creation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35", + "metadata": {}, + "outputs": [], + "source": [ + "project.save()" + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "## Step 3: Define Experiment\n", + "\n", + "This section shows how to add experiments, configure their parameters,\n", + "and link the structures defined in the previous step." + ] + }, + { + "cell_type": "markdown", + "id": "37", + "metadata": {}, + "source": [ + "#### Download Measured Data\n", + "\n", + "Download the data file from the EasyDiffraction repository on GitHub." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = ed.download_data(id=3, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": {}, + "source": [ + "#### Add Diffraction Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_data_path(\n", + " name='hrpt',\n", + " data_path=data_path,\n", + " sample_form='powder',\n", + " beam_mode='constant wavelength',\n", + " radiation_probe='neutron',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "#### Show Defined Experiments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.show_names()" + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "#### Show Measured Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas(expt_name='hrpt')" + ] + }, + { + "cell_type": "markdown", + "id": "45", + "metadata": {}, + "source": [ + "#### Set Instrument\n", + "\n", + "Modify the default instrument parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].instrument.setup_wavelength = 1.494\n", + "project.experiments['hrpt'].instrument.calib_twotheta_offset = 0.6" + ] + }, + { + "cell_type": "markdown", + "id": "47", + "metadata": {}, + "source": [ + "#### Set Peak Profile\n", + "\n", + "Show supported peak profile types." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].show_supported_peak_profile_types()" + ] + }, + { + "cell_type": "markdown", + "id": "49", + "metadata": {}, + "source": [ + "Show the current peak profile type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].show_current_peak_profile_type()" + ] + }, + { + "cell_type": "markdown", + "id": "51", + "metadata": {}, + "source": [ + "Select the desired peak profile type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].peak_profile_type = 'pseudo-voigt'" + ] + }, + { + "cell_type": "markdown", + "id": "53", + "metadata": {}, + "source": [ + "Modify default peak profile parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].peak.broad_gauss_u = 0.1\n", + "project.experiments['hrpt'].peak.broad_gauss_v = -0.1\n", + "project.experiments['hrpt'].peak.broad_gauss_w = 0.1\n", + "project.experiments['hrpt'].peak.broad_lorentz_x = 0\n", + "project.experiments['hrpt'].peak.broad_lorentz_y = 0.1" + ] + }, + { + "cell_type": "markdown", + "id": "55", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "markdown", + "id": "56", + "metadata": {}, + "source": [ + "Show supported background types." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].show_supported_background_types()" + ] + }, + { + "cell_type": "markdown", + "id": "58", + "metadata": {}, + "source": [ + "Show current background type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].show_current_background_type()" + ] + }, + { + "cell_type": "markdown", + "id": "60", + "metadata": {}, + "source": [ + "Select the desired background type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].background_type = 'line-segment'" + ] + }, + { + "cell_type": "markdown", + "id": "62", + "metadata": {}, + "source": [ + "Add background points." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].background.create(id='10', x=10, y=170)\n", + "project.experiments['hrpt'].background.create(id='30', x=30, y=170)\n", + "project.experiments['hrpt'].background.create(id='50', x=50, y=170)\n", + "project.experiments['hrpt'].background.create(id='110', x=110, y=170)\n", + "project.experiments['hrpt'].background.create(id='165', x=165, y=170)" + ] + }, + { + "cell_type": "markdown", + "id": "64", + "metadata": {}, + "source": [ + "Show current background points." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].background.show()" + ] + }, + { + "cell_type": "markdown", + "id": "66", + "metadata": {}, + "source": [ + "#### Set Linked Phases\n", + "\n", + "Link the structure defined in the previous step to the experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].linked_phases.create(id='lbco', scale=10.0)" + ] + }, + { + "cell_type": "markdown", + "id": "68", + "metadata": {}, + "source": [ + "#### Show Experiment as CIF" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].show_as_cif()" + ] + }, + { + "cell_type": "markdown", + "id": "70", + "metadata": {}, + "source": [ + "#### Save Project State" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71", + "metadata": {}, + "outputs": [], + "source": [ + "project.save()" + ] + }, + { + "cell_type": "markdown", + "id": "72", + "metadata": {}, + "source": [ + "## Step 4: Perform Analysis\n", + "\n", + "This section explains the analysis process, including how to set up\n", + "calculation and fitting engines.\n", + "\n", + "#### Set Calculator\n", + "\n", + "Show supported calculation engines for this experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "73", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].show_supported_calculator_types()" + ] + }, + { + "cell_type": "markdown", + "id": "74", + "metadata": {}, + "source": [ + "Show current calculation engine for this experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].show_current_calculator_type()" + ] + }, + { + "cell_type": "markdown", + "id": "76", + "metadata": {}, + "source": [ + "Select the desired calculation engine." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].calculator_type = 'cryspy'" + ] + }, + { + "cell_type": "markdown", + "id": "78", + "metadata": {}, + "source": [ + "#### Show Calculated Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_calc(expt_name='hrpt')" + ] + }, + { + "cell_type": "markdown", + "id": "80", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "81", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "83", + "metadata": {}, + "source": [ + "#### Show Parameters\n", + "\n", + "Show all parameters of the project." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84", + "metadata": {}, + "outputs": [], + "source": [ + "# project.analysis.show_all_params()" + ] + }, + { + "cell_type": "markdown", + "id": "85", + "metadata": {}, + "source": [ + "Show all fittable parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_fittable_params()" + ] + }, + { + "cell_type": "markdown", + "id": "87", + "metadata": {}, + "source": [ + "Show only free parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "89", + "metadata": {}, + "source": [ + "Show how to access parameters in the code." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90", + "metadata": {}, + "outputs": [], + "source": [ + "# project.analysis.how_to_access_parameters()" + ] + }, + { + "cell_type": "markdown", + "id": "91", + "metadata": {}, + "source": [ + "#### Set Fit Mode\n", + "\n", + "Show supported fit modes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_supported_fit_mode_types()" + ] + }, + { + "cell_type": "markdown", + "id": "93", + "metadata": {}, + "source": [ + "Show current fit mode." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_current_fit_mode_type()" + ] + }, + { + "cell_type": "markdown", + "id": "95", + "metadata": {}, + "source": [ + "Select desired fit mode." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit_mode.mode = 'single'" + ] + }, + { + "cell_type": "markdown", + "id": "97", + "metadata": {}, + "source": [ + "#### Set Minimizer\n", + "\n", + "Show supported fitting engines." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_available_minimizers()" + ] + }, + { + "cell_type": "markdown", + "id": "99", + "metadata": {}, + "source": [ + "Show current fitting engine." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "100", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_current_minimizer()" + ] + }, + { + "cell_type": "markdown", + "id": "101", + "metadata": {}, + "source": [ + "Select desired fitting engine." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "102", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "103", + "metadata": {}, + "source": [ + "### Perform Fit 1/5\n", + "\n", + "Set structure parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "104", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].cell.length_a.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "105", + "metadata": {}, + "source": [ + "Set experiment parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "106", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].linked_phases['lbco'].scale.free = True\n", + "project.experiments['hrpt'].instrument.calib_twotheta_offset.free = True\n", + "project.experiments['hrpt'].background['10'].y.free = True\n", + "project.experiments['hrpt'].background['30'].y.free = True\n", + "project.experiments['hrpt'].background['50'].y.free = True\n", + "project.experiments['hrpt'].background['110'].y.free = True\n", + "project.experiments['hrpt'].background['165'].y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "107", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "108", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "109", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "110", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "111", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "112", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "113", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "114", + "metadata": {}, + "source": [ + "#### Save Project State" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "115", + "metadata": {}, + "outputs": [], + "source": [ + "project.save_as(dir_path='lbco_hrpt', temporary=True)" + ] + }, + { + "cell_type": "markdown", + "id": "116", + "metadata": {}, + "source": [ + "### Perform Fit 2/5\n", + "\n", + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "117", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].peak.broad_gauss_u.free = True\n", + "project.experiments['hrpt'].peak.broad_gauss_v.free = True\n", + "project.experiments['hrpt'].peak.broad_gauss_w.free = True\n", + "project.experiments['hrpt'].peak.broad_lorentz_y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "118", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "119", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "120", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "121", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "122", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "123", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "124", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "125", + "metadata": {}, + "source": [ + "#### Save Project State" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "126", + "metadata": {}, + "outputs": [], + "source": [ + "project.save_as(dir_path='lbco_hrpt', temporary=True)" + ] + }, + { + "cell_type": "markdown", + "id": "127", + "metadata": {}, + "source": [ + "### Perform Fit 3/5\n", + "\n", + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "128", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].atom_sites['La'].b_iso.free = True\n", + "project.structures['lbco'].atom_sites['Ba'].b_iso.free = True\n", + "project.structures['lbco'].atom_sites['Co'].b_iso.free = True\n", + "project.structures['lbco'].atom_sites['O'].b_iso.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "129", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "130", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "131", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "132", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "133", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "134", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "135", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "136", + "metadata": {}, + "source": [ + "#### Save Project State" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "137", + "metadata": {}, + "outputs": [], + "source": [ + "project.save_as(dir_path='lbco_hrpt', temporary=True)" + ] + }, + { + "cell_type": "markdown", + "id": "138", + "metadata": {}, + "source": [ + "### Perform Fit 4/5\n", + "\n", + "#### Set Constraints\n", + "\n", + "Set aliases for parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "139", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.aliases.create(\n", + " label='biso_La',\n", + " param_uid=project.structures['lbco'].atom_sites['La'].b_iso.uid,\n", + ")\n", + "project.analysis.aliases.create(\n", + " label='biso_Ba',\n", + " param_uid=project.structures['lbco'].atom_sites['Ba'].b_iso.uid,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "140", + "metadata": {}, + "source": [ + "Set constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "141", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.constraints.create(lhs_alias='biso_Ba', rhs_expr='biso_La')" + ] + }, + { + "cell_type": "markdown", + "id": "142", + "metadata": {}, + "source": [ + "Show defined constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "143", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_constraints()" + ] + }, + { + "cell_type": "markdown", + "id": "144", + "metadata": {}, + "source": [ + "Show free parameters before applying constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "145", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "146", + "metadata": {}, + "source": [ + "Apply constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "147", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.apply_constraints()" + ] + }, + { + "cell_type": "markdown", + "id": "148", + "metadata": {}, + "source": [ + "Show free parameters after applying constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "149", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "150", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "151", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "152", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "153", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "154", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "155", + "metadata": {}, + "source": [ + "#### Save Project State" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "156", + "metadata": {}, + "outputs": [], + "source": [ + "project.save_as(dir_path='lbco_hrpt', temporary=True)" + ] + }, + { + "cell_type": "markdown", + "id": "157", + "metadata": {}, + "source": [ + "### Perform Fit 5/5\n", + "\n", + "#### Set Constraints\n", + "\n", + "Set more aliases for parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "158", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.aliases.create(\n", + " label='occ_La',\n", + " param_uid=project.structures['lbco'].atom_sites['La'].occupancy.uid,\n", + ")\n", + "project.analysis.aliases.create(\n", + " label='occ_Ba',\n", + " param_uid=project.structures['lbco'].atom_sites['Ba'].occupancy.uid,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "159", + "metadata": {}, + "source": [ + "Set more constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "160", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.constraints.create(\n", + " lhs_alias='occ_Ba',\n", + " rhs_expr='1 - occ_La',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "161", + "metadata": {}, + "source": [ + "Show defined constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "162", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_constraints()" + ] + }, + { + "cell_type": "markdown", + "id": "163", + "metadata": {}, + "source": [ + "Apply constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "164", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.apply_constraints()" + ] + }, + { + "cell_type": "markdown", + "id": "165", + "metadata": {}, + "source": [ + "Set structure parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "166", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].atom_sites['La'].occupancy.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "167", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "168", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "169", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "170", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "171", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "172", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "173", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "174", + "metadata": {}, + "source": [ + "#### Save Project State" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "175", + "metadata": {}, + "outputs": [], + "source": [ + "project.save_as(dir_path='lbco_hrpt', temporary=True)" + ] + }, + { + "cell_type": "markdown", + "id": "176", + "metadata": {}, + "source": [ + "## Step 5: Summary\n", + "\n", + "This final section shows how to review the results of the analysis." + ] + }, + { + "cell_type": "markdown", + "id": "177", + "metadata": {}, + "source": [ + "#### Show Project Summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "178", + "metadata": {}, + "outputs": [], + "source": [ + "project.summary.show_report()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "179", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-3.py b/docs/docs/tutorials/ed-3.py similarity index 100% rename from tutorials/ed-3.py rename to docs/docs/tutorials/ed-3.py diff --git a/docs/docs/tutorials/ed-4.ipynb b/docs/docs/tutorials/ed-4.ipynb new file mode 100644 index 00000000..0b784054 --- /dev/null +++ b/docs/docs/tutorials/ed-4.ipynb @@ -0,0 +1,705 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: PbSO4, NPD + XRD\n", + "\n", + "This example demonstrates a more advanced use of the EasyDiffraction\n", + "library by explicitly creating and configuring structures and\n", + "experiments before adding them to a project. It could be more suitable\n", + "for users who are interested in creating custom workflows. This\n", + "tutorial provides minimal explanation and is intended for users\n", + "already familiar with EasyDiffraction.\n", + "\n", + "The tutorial covers a Rietveld refinement of PbSO4 crystal structure\n", + "based on the joint fit of both X-ray and neutron diffraction data." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import ExperimentFactory\n", + "from easydiffraction import Project\n", + "from easydiffraction import StructureFactory\n", + "from easydiffraction import download_data" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Define Structure\n", + "\n", + "This section shows how to add structures and modify their\n", + "parameters.\n", + "\n", + "#### Create Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "structure = StructureFactory.from_scratch(name='pbso4')" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "structure.space_group.name_h_m = 'P n m a'" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a = 8.47\n", + "structure.cell.length_b = 5.39\n", + "structure.cell.length_c = 6.95" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "structure.atom_sites.create(\n", + " label='Pb',\n", + " type_symbol='Pb',\n", + " fract_x=0.1876,\n", + " fract_y=0.25,\n", + " fract_z=0.167,\n", + " wyckoff_letter='c',\n", + " b_iso=1.37,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='S',\n", + " type_symbol='S',\n", + " fract_x=0.0654,\n", + " fract_y=0.25,\n", + " fract_z=0.684,\n", + " wyckoff_letter='c',\n", + " b_iso=0.3777,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O1',\n", + " type_symbol='O',\n", + " fract_x=0.9082,\n", + " fract_y=0.25,\n", + " fract_z=0.5954,\n", + " wyckoff_letter='c',\n", + " b_iso=1.9764,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O2',\n", + " type_symbol='O',\n", + " fract_x=0.1935,\n", + " fract_y=0.25,\n", + " fract_z=0.5432,\n", + " wyckoff_letter='c',\n", + " b_iso=1.4456,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O3',\n", + " type_symbol='O',\n", + " fract_x=0.0811,\n", + " fract_y=0.0272,\n", + " fract_z=0.8086,\n", + " wyckoff_letter='d',\n", + " b_iso=1.2822,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Define Experiments\n", + "\n", + "This section shows how to add experiments, configure their parameters,\n", + "and link the structures defined in the previous step.\n", + "\n", + "### Experiment 1: npd\n", + "\n", + "#### Download Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path1 = download_data(id=13, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "expt1 = ExperimentFactory.from_data_path(\n", + " name='npd',\n", + " data_path=data_path1,\n", + " radiation_probe='neutron',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "expt1.instrument.setup_wavelength = 1.91\n", + "expt1.instrument.calib_twotheta_offset = -0.1406" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "expt1.peak.broad_gauss_u = 0.139\n", + "expt1.peak.broad_gauss_v = -0.412\n", + "expt1.peak.broad_gauss_w = 0.386\n", + "expt1.peak.broad_lorentz_x = 0\n", + "expt1.peak.broad_lorentz_y = 0.088" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "markdown", + "id": "20", + "metadata": {}, + "source": [ + "Select the background type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "expt1.background_type = 'line-segment'" + ] + }, + { + "cell_type": "markdown", + "id": "22", + "metadata": {}, + "source": [ + "Add background points." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "for id, x, y in [\n", + " ('1', 11.0, 206.1624),\n", + " ('2', 15.0, 194.75),\n", + " ('3', 20.0, 194.505),\n", + " ('4', 30.0, 188.4375),\n", + " ('5', 50.0, 207.7633),\n", + " ('6', 70.0, 201.7002),\n", + " ('7', 120.0, 244.4525),\n", + " ('8', 153.0, 226.0595),\n", + "]:\n", + " expt1.background.create(id=id, x=x, y=y)" + ] + }, + { + "cell_type": "markdown", + "id": "24", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "expt1.linked_phases.create(id='pbso4', scale=1.5)" + ] + }, + { + "cell_type": "markdown", + "id": "26", + "metadata": {}, + "source": [ + "### Experiment 2: xrd\n", + "\n", + "#### Download Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27", + "metadata": {}, + "outputs": [], + "source": [ + "data_path2 = download_data(id=16, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "28", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29", + "metadata": {}, + "outputs": [], + "source": [ + "expt2 = ExperimentFactory.from_data_path(\n", + " name='xrd',\n", + " data_path=data_path2,\n", + " radiation_probe='xray',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "30", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31", + "metadata": {}, + "outputs": [], + "source": [ + "expt2.instrument.setup_wavelength = 1.540567\n", + "expt2.instrument.calib_twotheta_offset = -0.05181" + ] + }, + { + "cell_type": "markdown", + "id": "32", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33", + "metadata": {}, + "outputs": [], + "source": [ + "expt2.peak.broad_gauss_u = 0.304138\n", + "expt2.peak.broad_gauss_v = -0.112622\n", + "expt2.peak.broad_gauss_w = 0.021272\n", + "expt2.peak.broad_lorentz_x = 0\n", + "expt2.peak.broad_lorentz_y = 0.057691" + ] + }, + { + "cell_type": "markdown", + "id": "34", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "markdown", + "id": "35", + "metadata": {}, + "source": [ + "Select background type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36", + "metadata": {}, + "outputs": [], + "source": [ + "expt2.background_type = 'chebyshev'" + ] + }, + { + "cell_type": "markdown", + "id": "37", + "metadata": {}, + "source": [ + "Add background points." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "for id, x, y in [\n", + " ('1', 0, 119.195),\n", + " ('2', 1, 6.221),\n", + " ('3', 2, -45.725),\n", + " ('4', 3, 8.119),\n", + " ('5', 4, 54.552),\n", + " ('6', 5, -20.661),\n", + "]:\n", + " expt2.background.create(id=id, order=x, coef=y)" + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "expt2.linked_phases.create(id='pbso4', scale=0.001)" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "## Define Project\n", + "\n", + "The project object is used to manage structures, experiments, and\n", + "analysis.\n", + "\n", + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()" + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add(structure)" + ] + }, + { + "cell_type": "markdown", + "id": "45", + "metadata": {}, + "source": [ + "#### Add Experiments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add(expt1)\n", + "project.experiments.add(expt2)" + ] + }, + { + "cell_type": "markdown", + "id": "47", + "metadata": {}, + "source": [ + "## Perform Analysis\n", + "\n", + "This section outlines the analysis process, including how to configure\n", + "calculation and fitting engines.\n", + "\n", + "#### Set Fit Mode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit_mode.mode = 'joint'" + ] + }, + { + "cell_type": "markdown", + "id": "49", + "metadata": {}, + "source": [ + "#### Set Minimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "51", + "metadata": {}, + "source": [ + "#### Set Fitting Parameters\n", + "\n", + "Set structure parameters to be optimized." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a.free = True\n", + "structure.cell.length_b.free = True\n", + "structure.cell.length_c.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "53", + "metadata": {}, + "source": [ + "Set experiment parameters to be optimized." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54", + "metadata": {}, + "outputs": [], + "source": [ + "expt1.linked_phases['pbso4'].scale.free = True\n", + "\n", + "expt1.instrument.calib_twotheta_offset.free = True\n", + "\n", + "expt1.peak.broad_gauss_u.free = True\n", + "expt1.peak.broad_gauss_v.free = True\n", + "expt1.peak.broad_gauss_w.free = True\n", + "expt1.peak.broad_lorentz_y.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55", + "metadata": {}, + "outputs": [], + "source": [ + "expt2.linked_phases['pbso4'].scale.free = True\n", + "\n", + "expt2.instrument.calib_twotheta_offset.free = True\n", + "\n", + "expt2.peak.broad_gauss_u.free = True\n", + "expt2.peak.broad_gauss_v.free = True\n", + "expt2.peak.broad_gauss_w.free = True\n", + "expt2.peak.broad_lorentz_y.free = True\n", + "\n", + "for term in expt2.background:\n", + " term.coef.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "56", + "metadata": {}, + "source": [ + "#### Perform Fit" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "58", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='npd', x_min=35.5, x_max=38.3, show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='xrd', x_min=29.0, x_max=30.4, show_residual=True)" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-4.py b/docs/docs/tutorials/ed-4.py similarity index 100% rename from tutorials/ed-4.py rename to docs/docs/tutorials/ed-4.py diff --git a/docs/docs/tutorials/ed-5.ipynb b/docs/docs/tutorials/ed-5.ipynb new file mode 100644 index 00000000..2ee5ec1a --- /dev/null +++ b/docs/docs/tutorials/ed-5.ipynb @@ -0,0 +1,638 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: Co2SiO4, D20\n", + "\n", + "This example demonstrates a Rietveld refinement of Co2SiO4 crystal\n", + "structure using constant wavelength neutron powder diffraction data\n", + "from D20 at ILL." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import ExperimentFactory\n", + "from easydiffraction import Project\n", + "from easydiffraction import StructureFactory\n", + "from easydiffraction import download_data" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Define Structure\n", + "\n", + "This section shows how to add structures and modify their\n", + "parameters.\n", + "\n", + "#### Create Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "structure = StructureFactory.from_scratch(name='cosio')" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "structure.space_group.name_h_m = 'P n m a'\n", + "structure.space_group.it_coordinate_system_code = 'abc'" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a = 10.3\n", + "structure.cell.length_b = 6.0\n", + "structure.cell.length_c = 4.8" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites.create(\n", + " label='Co1',\n", + " type_symbol='Co',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Co2',\n", + " type_symbol='Co',\n", + " fract_x=0.279,\n", + " fract_y=0.25,\n", + " fract_z=0.985,\n", + " wyckoff_letter='c',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Si',\n", + " type_symbol='Si',\n", + " fract_x=0.094,\n", + " fract_y=0.25,\n", + " fract_z=0.429,\n", + " wyckoff_letter='c',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O1',\n", + " type_symbol='O',\n", + " fract_x=0.091,\n", + " fract_y=0.25,\n", + " fract_z=0.771,\n", + " wyckoff_letter='c',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O2',\n", + " type_symbol='O',\n", + " fract_x=0.448,\n", + " fract_y=0.25,\n", + " fract_z=0.217,\n", + " wyckoff_letter='c',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O3',\n", + " type_symbol='O',\n", + " fract_x=0.164,\n", + " fract_y=0.032,\n", + " fract_z=0.28,\n", + " wyckoff_letter='d',\n", + " b_iso=0.5,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Define Experiment\n", + "\n", + "This section shows how to add experiments, configure their parameters,\n", + "and link the structures defined in the previous step.\n", + "\n", + "#### Download Measured Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = download_data(id=12, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "expt = ExperimentFactory.from_data_path(name='d20', data_path=data_path)" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "expt.instrument.setup_wavelength = 1.87\n", + "expt.instrument.calib_twotheta_offset = 0.1" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.broad_gauss_u = 0.3\n", + "expt.peak.broad_gauss_v = -0.5\n", + "expt.peak.broad_gauss_w = 0.4" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "expt.background.create(id='1', x=8, y=500)\n", + "expt.background.create(id='2', x=9, y=500)\n", + "expt.background.create(id='3', x=10, y=500)\n", + "expt.background.create(id='4', x=11, y=500)\n", + "expt.background.create(id='5', x=12, y=500)\n", + "expt.background.create(id='6', x=15, y=500)\n", + "expt.background.create(id='7', x=25, y=500)\n", + "expt.background.create(id='8', x=30, y=500)\n", + "expt.background.create(id='9', x=50, y=500)\n", + "expt.background.create(id='10', x=70, y=500)\n", + "expt.background.create(id='11', x=90, y=500)\n", + "expt.background.create(id='12', x=110, y=500)\n", + "expt.background.create(id='13', x=130, y=500)\n", + "expt.background.create(id='14', x=150, y=500)" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "expt.linked_phases.create(id='cosio', scale=1.0)" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "## Define Project\n", + "\n", + "The project object is used to manage the structure, experiment, and\n", + "analysis.\n", + "\n", + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()" + ] + }, + { + "cell_type": "markdown", + "id": "25", + "metadata": {}, + "source": [ + "#### Set Plotting Engine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "# Keep the auto-selected engine. Alternatively, you can uncomment the\n", + "# line below to explicitly set the engine to the required one.\n", + "# project.plotter.engine = 'plotly'" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add(structure)" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "#### Add Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add(expt)" + ] + }, + { + "cell_type": "markdown", + "id": "31", + "metadata": {}, + "source": [ + "## Perform Analysis\n", + "\n", + "This section shows the analysis process, including how to set up\n", + "calculation and fitting engines.\n", + "\n", + "#### Set Minimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "33", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='d20', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='d20', x_min=41, x_max=54, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "#### Set Free Parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a.free = True\n", + "structure.cell.length_b.free = True\n", + "structure.cell.length_c.free = True\n", + "\n", + "structure.atom_sites['Co2'].fract_x.free = True\n", + "structure.atom_sites['Co2'].fract_z.free = True\n", + "structure.atom_sites['Si'].fract_x.free = True\n", + "structure.atom_sites['Si'].fract_z.free = True\n", + "structure.atom_sites['O1'].fract_x.free = True\n", + "structure.atom_sites['O1'].fract_z.free = True\n", + "structure.atom_sites['O2'].fract_x.free = True\n", + "structure.atom_sites['O2'].fract_z.free = True\n", + "structure.atom_sites['O3'].fract_x.free = True\n", + "structure.atom_sites['O3'].fract_y.free = True\n", + "structure.atom_sites['O3'].fract_z.free = True\n", + "\n", + "structure.atom_sites['Co1'].b_iso.free = True\n", + "structure.atom_sites['Co2'].b_iso.free = True\n", + "structure.atom_sites['Si'].b_iso.free = True\n", + "structure.atom_sites['O1'].b_iso.free = True\n", + "structure.atom_sites['O2'].b_iso.free = True\n", + "structure.atom_sites['O3'].b_iso.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "expt.linked_phases['cosio'].scale.free = True\n", + "\n", + "expt.instrument.calib_twotheta_offset.free = True\n", + "\n", + "expt.peak.broad_gauss_u.free = True\n", + "expt.peak.broad_gauss_v.free = True\n", + "expt.peak.broad_gauss_w.free = True\n", + "expt.peak.broad_lorentz_y.free = True\n", + "\n", + "for point in expt.background:\n", + " point.y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": {}, + "source": [ + "#### Set Constraints\n", + "\n", + "Set aliases for parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.aliases.create(\n", + " label='biso_Co1',\n", + " param_uid=project.structures['cosio'].atom_sites['Co1'].b_iso.uid,\n", + ")\n", + "project.analysis.aliases.create(\n", + " label='biso_Co2',\n", + " param_uid=project.structures['cosio'].atom_sites['Co2'].b_iso.uid,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "Set constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.constraints.create(\n", + " lhs_alias='biso_Co2',\n", + " rhs_expr='biso_Co1',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "Apply constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.apply_constraints()" + ] + }, + { + "cell_type": "markdown", + "id": "45", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "47", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='d20', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='d20', x_min=41, x_max=54, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "50", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "This final section shows how to review the results of the analysis." + ] + }, + { + "cell_type": "markdown", + "id": "51", + "metadata": {}, + "source": [ + "#### Show Project Summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52", + "metadata": {}, + "outputs": [], + "source": [ + "project.summary.show_report()" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-5.py b/docs/docs/tutorials/ed-5.py similarity index 100% rename from tutorials/ed-5.py rename to docs/docs/tutorials/ed-5.py diff --git a/docs/docs/tutorials/ed-6.ipynb b/docs/docs/tutorials/ed-6.ipynb new file mode 100644 index 00000000..599c09eb --- /dev/null +++ b/docs/docs/tutorials/ed-6.ipynb @@ -0,0 +1,849 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: HS, HRPT\n", + "\n", + "This example demonstrates a Rietveld refinement of HS crystal\n", + "structure using constant wavelength neutron powder diffraction data\n", + "from HRPT at PSI." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import ExperimentFactory\n", + "from easydiffraction import Project\n", + "from easydiffraction import StructureFactory\n", + "from easydiffraction import download_data" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Define Structure\n", + "\n", + "This section shows how to add structures and modify their\n", + "parameters.\n", + "\n", + "#### Create Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "structure = StructureFactory.from_scratch(name='hs')" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "structure.space_group.name_h_m = 'R -3 m'\n", + "structure.space_group.it_coordinate_system_code = 'h'" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": { + "lines_to_next_cell": 2 + }, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a = 6.9\n", + "structure.cell.length_c = 14.1" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites.create(\n", + " label='Zn',\n", + " type_symbol='Zn',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0.5,\n", + " wyckoff_letter='b',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Cu',\n", + " type_symbol='Cu',\n", + " fract_x=0.5,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='e',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O',\n", + " type_symbol='O',\n", + " fract_x=0.21,\n", + " fract_y=-0.21,\n", + " fract_z=0.06,\n", + " wyckoff_letter='h',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Cl',\n", + " type_symbol='Cl',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0.197,\n", + " wyckoff_letter='c',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='H',\n", + " type_symbol='2H',\n", + " fract_x=0.13,\n", + " fract_y=-0.13,\n", + " fract_z=0.08,\n", + " wyckoff_letter='h',\n", + " b_iso=0.5,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Define Experiment\n", + "\n", + "This section shows how to add experiments, configure their parameters,\n", + "and link the structures defined in the previous step.\n", + "\n", + "#### Download Measured Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = download_data(id=11, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "expt = ExperimentFactory.from_data_path(name='hrpt', data_path=data_path)" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "expt.instrument.setup_wavelength = 1.89\n", + "expt.instrument.calib_twotheta_offset = 0.0" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.broad_gauss_u = 0.1\n", + "expt.peak.broad_gauss_v = -0.2\n", + "expt.peak.broad_gauss_w = 0.2\n", + "expt.peak.broad_lorentz_x = 0.0\n", + "expt.peak.broad_lorentz_y = 0" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "expt.background.create(id='1', x=4.4196, y=500)\n", + "expt.background.create(id='2', x=6.6207, y=500)\n", + "expt.background.create(id='3', x=10.4918, y=500)\n", + "expt.background.create(id='4', x=15.4634, y=500)\n", + "expt.background.create(id='5', x=45.6041, y=500)\n", + "expt.background.create(id='6', x=74.6844, y=500)\n", + "expt.background.create(id='7', x=103.4187, y=500)\n", + "expt.background.create(id='8', x=121.6311, y=500)\n", + "expt.background.create(id='9', x=159.4116, y=500)" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "expt.linked_phases.create(id='hs', scale=0.5)" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "## Define Project\n", + "\n", + "The project object is used to manage the structure, experiment, and\n", + "analysis.\n", + "\n", + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()" + ] + }, + { + "cell_type": "markdown", + "id": "25", + "metadata": {}, + "source": [ + "#### Set Plotting Engine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "# Keep the auto-selected engine. Alternatively, you can uncomment the\n", + "# line below to explicitly set the engine to the required one.\n", + "# project.plotter.engine = 'plotly'" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add(structure)" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "#### Add Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add(expt)" + ] + }, + { + "cell_type": "markdown", + "id": "31", + "metadata": {}, + "source": [ + "## Perform Analysis\n", + "\n", + "This section shows the analysis process, including how to set up\n", + "calculation and fitting engines.\n", + "\n", + "#### Set Minimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "33", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "### Perform Fit 1/5\n", + "\n", + "Set parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a.free = True\n", + "structure.cell.length_c.free = True\n", + "\n", + "expt.linked_phases['hs'].scale.free = True\n", + "expt.instrument.calib_twotheta_offset.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "38", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "40", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "46", + "metadata": {}, + "source": [ + "### Perform Fit 2/5\n", + "\n", + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.broad_gauss_u.free = True\n", + "expt.peak.broad_gauss_v.free = True\n", + "expt.peak.broad_gauss_w.free = True\n", + "expt.peak.broad_lorentz_x.free = True\n", + "\n", + "for point in expt.background:\n", + " point.y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "48", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "50", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "53", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "56", + "metadata": {}, + "source": [ + "### Perform Fit 3/5\n", + "\n", + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites['O'].fract_x.free = True\n", + "structure.atom_sites['O'].fract_z.free = True\n", + "structure.atom_sites['Cl'].fract_z.free = True\n", + "structure.atom_sites['H'].fract_x.free = True\n", + "structure.atom_sites['H'].fract_z.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "58", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "60", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "63", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "66", + "metadata": {}, + "source": [ + "### Perform Fit 4/5\n", + "\n", + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites['Zn'].b_iso.free = True\n", + "structure.atom_sites['Cu'].b_iso.free = True\n", + "structure.atom_sites['O'].b_iso.free = True\n", + "structure.atom_sites['Cl'].b_iso.free = True\n", + "structure.atom_sites['H'].b_iso.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "68", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "70", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "73", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "76", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "This final section shows how to review the results of the analysis." + ] + }, + { + "cell_type": "markdown", + "id": "77", + "metadata": {}, + "source": [ + "#### Show Project Summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78", + "metadata": {}, + "outputs": [], + "source": [ + "project.summary.show_report()" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-6.py b/docs/docs/tutorials/ed-6.py similarity index 100% rename from tutorials/ed-6.py rename to docs/docs/tutorials/ed-6.py diff --git a/docs/docs/tutorials/ed-7.ipynb b/docs/docs/tutorials/ed-7.ipynb new file mode 100644 index 00000000..869dc723 --- /dev/null +++ b/docs/docs/tutorials/ed-7.ipynb @@ -0,0 +1,741 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: Si, SEPD\n", + "\n", + "This example demonstrates a Rietveld refinement of Si crystal\n", + "structure using time-of-flight neutron powder diffraction data from\n", + "SEPD at Argonne." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import ExperimentFactory\n", + "from easydiffraction import Project\n", + "from easydiffraction import StructureFactory\n", + "from easydiffraction import download_data" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Define Structure\n", + "\n", + "This section shows how to add structures and modify their\n", + "parameters.\n", + "\n", + "#### Create Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "structure = StructureFactory.from_scratch(name='si')" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "structure.space_group.name_h_m = 'F d -3 m'\n", + "structure.space_group.it_coordinate_system_code = '2'" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a = 5.431" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites.create(\n", + " label='Si',\n", + " type_symbol='Si',\n", + " fract_x=0.125,\n", + " fract_y=0.125,\n", + " fract_z=0.125,\n", + " b_iso=0.5,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Define Experiment\n", + "\n", + "This section shows how to add experiments, configure their\n", + "parameters, and link the structures defined in the previous step.\n", + "\n", + "#### Download Measured Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = download_data(id=7, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "expt = ExperimentFactory.from_data_path(\n", + " name='sepd', data_path=data_path, beam_mode='time-of-flight'\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "expt.instrument.setup_twotheta_bank = 144.845\n", + "expt.instrument.calib_d_to_tof_offset = 0.0\n", + "expt.instrument.calib_d_to_tof_linear = 7476.91\n", + "expt.instrument.calib_d_to_tof_quad = -1.54" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak_profile_type = 'pseudo-voigt * ikeda-carpenter'\n", + "expt.peak.broad_gauss_sigma_0 = 3.0\n", + "expt.peak.broad_gauss_sigma_1 = 40.0\n", + "expt.peak.broad_gauss_sigma_2 = 2.0\n", + "expt.peak.broad_mix_beta_0 = 0.04221\n", + "expt.peak.broad_mix_beta_1 = 0.00946" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "#### Set Peak Asymmetry" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.asym_alpha_0 = 0.0\n", + "expt.peak.asym_alpha_1 = 0.5971" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "expt.background_type = 'line-segment'\n", + "for x in range(0, 35000, 5000):\n", + " expt.background.create(id=str(x), x=x, y=200)" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "expt.linked_phases.create(id='si', scale=10.0)" + ] + }, + { + "cell_type": "markdown", + "id": "25", + "metadata": {}, + "source": [ + "## Define Project\n", + "\n", + "The project object is used to manage the structure, experiment, and\n", + "analysis.\n", + "\n", + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add(structure)" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "#### Add Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add(expt)" + ] + }, + { + "cell_type": "markdown", + "id": "31", + "metadata": {}, + "source": [ + "## Perform Analysis\n", + "\n", + "This section shows the analysis process, including how to set up\n", + "calculation and fitting engines.\n", + "\n", + "#### Set Minimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "33", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', show_residual=True)\n", + "project.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "35", + "metadata": {}, + "source": [ + "### Perform Fit 1/5\n", + "\n", + "Set parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a.free = True\n", + "\n", + "expt.linked_phases['si'].scale.free = True\n", + "expt.instrument.calib_d_to_tof_offset.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "37", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "44", + "metadata": {}, + "source": [ + "### Perform Fit 2/5\n", + "\n", + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], + "source": [ + "for point in expt.background:\n", + " point.y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "46", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "48", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "50", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "53", + "metadata": {}, + "source": [ + "### Perform Fit 3/5\n", + "\n", + "Fix background points." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54", + "metadata": {}, + "outputs": [], + "source": [ + "for point in expt.background:\n", + " point.y.free = False" + ] + }, + { + "cell_type": "markdown", + "id": "55", + "metadata": {}, + "source": [ + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.broad_gauss_sigma_0.free = True\n", + "expt.peak.broad_gauss_sigma_1.free = True\n", + "expt.peak.broad_gauss_sigma_2.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "57", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "58", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "59", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "61", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "64", + "metadata": {}, + "source": [ + "### Perform Fit 4/5\n", + "\n", + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites['Si'].b_iso.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "66", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "68", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "70", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700, show_residual=True)" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-7.py b/docs/docs/tutorials/ed-7.py similarity index 100% rename from tutorials/ed-7.py rename to docs/docs/tutorials/ed-7.py diff --git a/docs/docs/tutorials/ed-8.ipynb b/docs/docs/tutorials/ed-8.ipynb new file mode 100644 index 00000000..4bc42ad1 --- /dev/null +++ b/docs/docs/tutorials/ed-8.ipynb @@ -0,0 +1,747 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: NCAF, WISH\n", + "\n", + "This example demonstrates a Rietveld refinement of Na2Ca3Al2F14\n", + "crystal structure using time-of-flight neutron powder diffraction data\n", + "from WISH at ISIS.\n", + "\n", + "Two datasets from detector banks 5+6 and 4+7 are used for joint\n", + "fitting." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import ExperimentFactory\n", + "from easydiffraction import Project\n", + "from easydiffraction import StructureFactory\n", + "from easydiffraction import download_data" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Define Structure\n", + "\n", + "This section covers how to add structures and modify their\n", + "parameters.\n", + "\n", + "#### Create Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "structure = StructureFactory.from_scratch(name='ncaf')" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "structure.space_group.name_h_m = 'I 21 3'\n", + "structure.space_group.it_coordinate_system_code = '1'" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a = 10.250256" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites.create(\n", + " label='Ca',\n", + " type_symbol='Ca',\n", + " fract_x=0.4663,\n", + " fract_y=0.0,\n", + " fract_z=0.25,\n", + " wyckoff_letter='b',\n", + " b_iso=0.92,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Al',\n", + " type_symbol='Al',\n", + " fract_x=0.2521,\n", + " fract_y=0.2521,\n", + " fract_z=0.2521,\n", + " wyckoff_letter='a',\n", + " b_iso=0.73,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Na',\n", + " type_symbol='Na',\n", + " fract_x=0.0851,\n", + " fract_y=0.0851,\n", + " fract_z=0.0851,\n", + " wyckoff_letter='a',\n", + " b_iso=2.08,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='F1',\n", + " type_symbol='F',\n", + " fract_x=0.1377,\n", + " fract_y=0.3054,\n", + " fract_z=0.1195,\n", + " wyckoff_letter='c',\n", + " b_iso=0.90,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='F2',\n", + " type_symbol='F',\n", + " fract_x=0.3625,\n", + " fract_y=0.3633,\n", + " fract_z=0.1867,\n", + " wyckoff_letter='c',\n", + " b_iso=1.37,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='F3',\n", + " type_symbol='F',\n", + " fract_x=0.4612,\n", + " fract_y=0.4612,\n", + " fract_z=0.4612,\n", + " wyckoff_letter='a',\n", + " b_iso=0.88,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Define Experiment\n", + "\n", + "This section shows how to add experiments, configure their parameters,\n", + "and link the structures defined in the previous step.\n", + "\n", + "#### Download Measured Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path56 = download_data(id=9, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "data_path47 = download_data(id=10, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "14", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "expt56 = ExperimentFactory.from_data_path(\n", + " name='wish_5_6',\n", + " data_path=data_path56,\n", + " beam_mode='time-of-flight',\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "expt47 = ExperimentFactory.from_data_path(\n", + " name='wish_4_7',\n", + " data_path=data_path47,\n", + " beam_mode='time-of-flight',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "expt56.instrument.setup_twotheta_bank = 152.827\n", + "expt56.instrument.calib_d_to_tof_offset = -13.5\n", + "expt56.instrument.calib_d_to_tof_linear = 20773.0\n", + "expt56.instrument.calib_d_to_tof_quad = -1.08308" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19", + "metadata": {}, + "outputs": [], + "source": [ + "expt47.instrument.setup_twotheta_bank = 121.660\n", + "expt47.instrument.calib_d_to_tof_offset = -15.0\n", + "expt47.instrument.calib_d_to_tof_linear = 18660.0\n", + "expt47.instrument.calib_d_to_tof_quad = -0.47488" + ] + }, + { + "cell_type": "markdown", + "id": "20", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "expt56.peak.broad_gauss_sigma_0 = 0.0\n", + "expt56.peak.broad_gauss_sigma_1 = 0.0\n", + "expt56.peak.broad_gauss_sigma_2 = 15.5\n", + "expt56.peak.broad_mix_beta_0 = 0.007\n", + "expt56.peak.broad_mix_beta_1 = 0.01\n", + "expt56.peak.asym_alpha_0 = -0.0094\n", + "expt56.peak.asym_alpha_1 = 0.1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "expt47.peak.broad_gauss_sigma_0 = 0.0\n", + "expt47.peak.broad_gauss_sigma_1 = 29.8\n", + "expt47.peak.broad_gauss_sigma_2 = 18.0\n", + "expt47.peak.broad_mix_beta_0 = 0.006\n", + "expt47.peak.broad_mix_beta_1 = 0.015\n", + "expt47.peak.asym_alpha_0 = -0.0115\n", + "expt47.peak.asym_alpha_1 = 0.1" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "expt56.background_type = 'line-segment'\n", + "for idx, (x, y) in enumerate(\n", + " [\n", + " (9162, 465),\n", + " (11136, 593),\n", + " (13313, 497),\n", + " (14906, 546),\n", + " (16454, 533),\n", + " (17352, 496),\n", + " (18743, 428),\n", + " (20179, 452),\n", + " (21368, 397),\n", + " (22176, 468),\n", + " (22827, 477),\n", + " (24644, 380),\n", + " (26439, 381),\n", + " (28257, 378),\n", + " (31196, 343),\n", + " (34034, 328),\n", + " (37265, 310),\n", + " (41214, 323),\n", + " (44827, 283),\n", + " (49830, 273),\n", + " (52905, 257),\n", + " (58204, 260),\n", + " (62916, 261),\n", + " (70186, 262),\n", + " (74204, 262),\n", + " (82103, 268),\n", + " (91958, 268),\n", + " (102712, 262),\n", + " ],\n", + " start=1,\n", + "):\n", + " expt56.background.create(id=str(idx), x=x, y=y)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "expt47.background_type = 'line-segment'\n", + "for idx, (x, y) in enumerate(\n", + " [\n", + " (9090, 488),\n", + " (10672, 566),\n", + " (12287, 494),\n", + " (14037, 559),\n", + " (15451, 529),\n", + " (16764, 445),\n", + " (18076, 460),\n", + " (19456, 413),\n", + " (20466, 511),\n", + " (21880, 396),\n", + " (23798, 391),\n", + " (25447, 385),\n", + " (28073, 349),\n", + " (30058, 332),\n", + " (32583, 309),\n", + " (34804, 355),\n", + " (37160, 318),\n", + " (40324, 290),\n", + " (46895, 260),\n", + " (50631, 256),\n", + " (54602, 246),\n", + " (58439, 264),\n", + " (66520, 250),\n", + " (75002, 258),\n", + " (83649, 257),\n", + " (92770, 255),\n", + " (101524, 260),\n", + " ],\n", + " start=1,\n", + "):\n", + " expt47.background.create(id=str(idx), x=x, y=y)" + ] + }, + { + "cell_type": "markdown", + "id": "26", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27", + "metadata": {}, + "outputs": [], + "source": [ + "expt56.linked_phases.create(id='ncaf', scale=1.0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], + "source": [ + "expt47.linked_phases.create(id='ncaf', scale=2.0)" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "#### Set Excluded Regions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "expt56.excluded_regions.create(id='1', start=0, end=10010)\n", + "expt56.excluded_regions.create(id='2', start=100010, end=200000)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31", + "metadata": {}, + "outputs": [], + "source": [ + "expt47.excluded_regions.create(id='1', start=0, end=10006)\n", + "expt47.excluded_regions.create(id='2', start=100004, end=200000)" + ] + }, + { + "cell_type": "markdown", + "id": "32", + "metadata": {}, + "source": [ + "## Define Project\n", + "\n", + "The project object is used to manage the structure, experiments,\n", + "and analysis\n", + "\n", + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()" + ] + }, + { + "cell_type": "markdown", + "id": "34", + "metadata": {}, + "source": [ + "#### Set Plotting Engine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35", + "metadata": {}, + "outputs": [], + "source": [ + "# Keep the auto-selected engine. Alternatively, you can uncomment the\n", + "# line below to explicitly set the engine to the required one.\n", + "# project.plotter.engine = 'plotly'" + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add(structure)" + ] + }, + { + "cell_type": "markdown", + "id": "38", + "metadata": {}, + "source": [ + "#### Add Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add(expt56)\n", + "project.experiments.add(expt47)" + ] + }, + { + "cell_type": "markdown", + "id": "40", + "metadata": {}, + "source": [ + "## Perform Analysis\n", + "\n", + "This section shows the analysis process, including how to set up\n", + "calculation and fitting engines.\n", + "\n", + "#### Set Minimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "42", + "metadata": {}, + "source": [ + "#### Set Fit Mode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit_mode.mode = 'joint'" + ] + }, + { + "cell_type": "markdown", + "id": "44", + "metadata": {}, + "source": [ + "#### Set Free Parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites['Ca'].b_iso.free = True\n", + "structure.atom_sites['Al'].b_iso.free = True\n", + "structure.atom_sites['Na'].b_iso.free = True\n", + "structure.atom_sites['F1'].b_iso.free = True\n", + "structure.atom_sites['F2'].b_iso.free = True\n", + "structure.atom_sites['F3'].b_iso.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46", + "metadata": {}, + "outputs": [], + "source": [ + "expt56.linked_phases['ncaf'].scale.free = True\n", + "expt56.instrument.calib_d_to_tof_offset.free = True\n", + "expt56.instrument.calib_d_to_tof_linear.free = True\n", + "expt56.peak.broad_gauss_sigma_2.free = True\n", + "expt56.peak.broad_mix_beta_0.free = True\n", + "expt56.peak.broad_mix_beta_1.free = True\n", + "expt56.peak.asym_alpha_1.free = True\n", + "\n", + "expt47.linked_phases['ncaf'].scale.free = True\n", + "expt47.instrument.calib_d_to_tof_linear.free = True\n", + "expt47.instrument.calib_d_to_tof_offset.free = True\n", + "expt47.peak.broad_gauss_sigma_2.free = True\n", + "expt47.peak.broad_mix_beta_0.free = True\n", + "expt47.peak.broad_mix_beta_1.free = True\n", + "expt47.peak.asym_alpha_1.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "47", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='wish_5_6', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='wish_4_7', show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "50", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "52", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='wish_5_6', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='wish_4_7', show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "55", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "This final section shows how to review the results of the analysis." + ] + }, + { + "cell_type": "markdown", + "id": "56", + "metadata": {}, + "source": [ + "#### Show Project Summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57", + "metadata": {}, + "outputs": [], + "source": [ + "project.summary.show_report()" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-8.py b/docs/docs/tutorials/ed-8.py similarity index 100% rename from tutorials/ed-8.py rename to docs/docs/tutorials/ed-8.py diff --git a/docs/docs/tutorials/ed-9.ipynb b/docs/docs/tutorials/ed-9.ipynb new file mode 100644 index 00000000..735964ae --- /dev/null +++ b/docs/docs/tutorials/ed-9.ipynb @@ -0,0 +1,704 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: LBCO+Si, McStas\n", + "\n", + "This example demonstrates a Rietveld refinement of La0.5Ba0.5CoO3\n", + "crystal structure with a small amount of Si phase using time-of-flight\n", + "neutron powder diffraction data simulated with McStas." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import ExperimentFactory\n", + "from easydiffraction import Project\n", + "from easydiffraction import StructureFactory\n", + "from easydiffraction import download_data" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Define Structures\n", + "\n", + "This section shows how to add structures and modify their\n", + "parameters.\n", + "\n", + "### Create Structure 1: LBCO" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "structure_1 = StructureFactory.from_scratch(name='lbco')" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "structure_1.space_group.name_h_m = 'P m -3 m'\n", + "structure_1.space_group.it_coordinate_system_code = '1'" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure_1.cell.length_a = 3.8909" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure_1.atom_sites.create(\n", + " label='La',\n", + " type_symbol='La',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.2,\n", + " occupancy=0.5,\n", + ")\n", + "structure_1.atom_sites.create(\n", + " label='Ba',\n", + " type_symbol='Ba',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.2,\n", + " occupancy=0.5,\n", + ")\n", + "structure_1.atom_sites.create(\n", + " label='Co',\n", + " type_symbol='Co',\n", + " fract_x=0.5,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='b',\n", + " b_iso=0.2567,\n", + ")\n", + "structure_1.atom_sites.create(\n", + " label='O',\n", + " type_symbol='O',\n", + " fract_x=0,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='c',\n", + " b_iso=1.4041,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "### Create Structure 2: Si" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "structure_2 = StructureFactory.from_scratch(name='si')" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "structure_2.space_group.name_h_m = 'F d -3 m'\n", + "structure_2.space_group.it_coordinate_system_code = '2'" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "structure_2.cell.length_a = 5.43146" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "structure_2.atom_sites.create(\n", + " label='Si',\n", + " type_symbol='Si',\n", + " fract_x=0.0,\n", + " fract_y=0.0,\n", + " fract_z=0.0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.0,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "## Define Experiment\n", + "\n", + "This section shows how to add experiments, configure their parameters,\n", + "and link the structures defined in the previous step.\n", + "\n", + "#### Download Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = download_data(id=8, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "experiment = ExperimentFactory.from_data_path(\n", + " name='mcstas',\n", + " data_path=data_path,\n", + " sample_form='powder',\n", + " beam_mode='time-of-flight',\n", + " radiation_probe='neutron',\n", + " scattering_type='bragg',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.instrument.setup_twotheta_bank = 94.90931761529106\n", + "experiment.instrument.calib_d_to_tof_offset = 0.0\n", + "experiment.instrument.calib_d_to_tof_linear = 58724.76869981215\n", + "experiment.instrument.calib_d_to_tof_quad = -0.00001" + ] + }, + { + "cell_type": "markdown", + "id": "25", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "# experiment.peak_profile_type = 'pseudo-voigt * ikeda-carpenter'\n", + "experiment.peak.broad_gauss_sigma_0 = 45137\n", + "experiment.peak.broad_gauss_sigma_1 = -52394\n", + "experiment.peak.broad_gauss_sigma_2 = 22998\n", + "experiment.peak.broad_mix_beta_0 = 0.0055\n", + "experiment.peak.broad_mix_beta_1 = 0.0041\n", + "experiment.peak.asym_alpha_0 = 0\n", + "experiment.peak.asym_alpha_1 = 0.0097" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "markdown", + "id": "28", + "metadata": {}, + "source": [ + "Select the background type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.background_type = 'line-segment'" + ] + }, + { + "cell_type": "markdown", + "id": "30", + "metadata": {}, + "source": [ + "Add background points." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.background.create(id='1', x=45000, y=0.2)\n", + "experiment.background.create(id='2', x=50000, y=0.2)\n", + "experiment.background.create(id='3', x=55000, y=0.2)\n", + "experiment.background.create(id='4', x=65000, y=0.2)\n", + "experiment.background.create(id='5', x=70000, y=0.2)\n", + "experiment.background.create(id='6', x=75000, y=0.2)\n", + "experiment.background.create(id='7', x=80000, y=0.2)\n", + "experiment.background.create(id='8', x=85000, y=0.2)\n", + "experiment.background.create(id='9', x=90000, y=0.2)\n", + "experiment.background.create(id='10', x=95000, y=0.2)\n", + "experiment.background.create(id='11', x=100000, y=0.2)\n", + "experiment.background.create(id='12', x=105000, y=0.2)\n", + "experiment.background.create(id='13', x=110000, y=0.2)" + ] + }, + { + "cell_type": "markdown", + "id": "32", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.linked_phases.create(id='lbco', scale=4.0)\n", + "experiment.linked_phases.create(id='si', scale=0.2)" + ] + }, + { + "cell_type": "markdown", + "id": "34", + "metadata": {}, + "source": [ + "## Define Project\n", + "\n", + "The project object is used to manage structures, experiments, and\n", + "analysis.\n", + "\n", + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()" + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "#### Add Structures" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add(structure_1)\n", + "project.structures.add(structure_2)" + ] + }, + { + "cell_type": "markdown", + "id": "38", + "metadata": {}, + "source": [ + "#### Show Structures" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.show_names()" + ] + }, + { + "cell_type": "markdown", + "id": "40", + "metadata": {}, + "source": [ + "#### Add Experiments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add(experiment)" + ] + }, + { + "cell_type": "markdown", + "id": "42", + "metadata": {}, + "source": [ + "#### Set Excluded Regions\n", + "\n", + "Show measured data as loaded from the file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas(expt_name='mcstas')" + ] + }, + { + "cell_type": "markdown", + "id": "44", + "metadata": {}, + "source": [ + "Add excluded regions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.excluded_regions.create(id='1', start=0, end=40000)\n", + "experiment.excluded_regions.create(id='2', start=108000, end=200000)" + ] + }, + { + "cell_type": "markdown", + "id": "46", + "metadata": {}, + "source": [ + "Show excluded regions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.excluded_regions.show()" + ] + }, + { + "cell_type": "markdown", + "id": "48", + "metadata": {}, + "source": [ + "Show measured data after adding excluded regions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas(expt_name='mcstas')" + ] + }, + { + "cell_type": "markdown", + "id": "50", + "metadata": {}, + "source": [ + "Show experiment as CIF." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['mcstas'].show_as_cif()" + ] + }, + { + "cell_type": "markdown", + "id": "52", + "metadata": {}, + "source": [ + "## Perform Analysis\n", + "\n", + "This section outlines the analysis process, including how to configure\n", + "calculation and fitting engines.\n", + "\n", + "#### Set Minimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "54", + "metadata": {}, + "source": [ + "#### Set Fitting Parameters\n", + "\n", + "Set structure parameters to be optimized." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55", + "metadata": {}, + "outputs": [], + "source": [ + "structure_1.cell.length_a.free = True\n", + "structure_1.atom_sites['Co'].b_iso.free = True\n", + "structure_1.atom_sites['O'].b_iso.free = True\n", + "\n", + "structure_2.cell.length_a.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "56", + "metadata": {}, + "source": [ + "Set experiment parameters to be optimized." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.linked_phases['lbco'].scale.free = True\n", + "experiment.linked_phases['si'].scale.free = True\n", + "\n", + "experiment.peak.broad_gauss_sigma_0.free = True\n", + "experiment.peak.broad_gauss_sigma_1.free = True\n", + "experiment.peak.broad_gauss_sigma_2.free = True\n", + "\n", + "experiment.peak.asym_alpha_1.free = True\n", + "experiment.peak.broad_mix_beta_0.free = True\n", + "experiment.peak.broad_mix_beta_1.free = True\n", + "\n", + "for point in experiment.background:\n", + " point.y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "58", + "metadata": {}, + "source": [ + "#### Perform Fit" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "60", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='mcstas')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-9.py b/docs/docs/tutorials/ed-9.py similarity index 100% rename from tutorials/ed-9.py rename to docs/docs/tutorials/ed-9.py diff --git a/tutorials/index.json b/docs/docs/tutorials/index.json similarity index 98% rename from tutorials/index.json rename to docs/docs/tutorials/index.json index 138b0e51..d40d1896 100644 --- a/tutorials/index.json +++ b/docs/docs/tutorials/index.json @@ -86,7 +86,7 @@ "13": { "url": "https://easyscience.github.io/diffraction-lib/{version}/tutorials/ed-13/ed-13.ipynb", "original_name": "dmsc-summer-school-2025_analysis-powder-diffraction", - "title": "DMSC Summer School 2025: Powder Diffraction Analysis", + "title": "DMSC Summer School: Powder Diffraction Analysis", "description": "Comprehensive workshop tutorial covering Rietveld refinement of Si and La0.5Ba0.5CoO3 using simulated powder diffraction data", "level": "workshop" }, diff --git a/docs/docs/tutorials/index.md b/docs/docs/tutorials/index.md new file mode 100644 index 00000000..5e406e59 --- /dev/null +++ b/docs/docs/tutorials/index.md @@ -0,0 +1,95 @@ +--- +icon: material/school +--- + +# :material-school: Tutorials + +This section presents a collection of **Jupyter Notebook** tutorials +that demonstrate how to use EasyDiffraction for various tasks. These +tutorials serve as self-contained, step-by-step **guides** to help users +grasp the workflow of diffraction data analysis using EasyDiffraction. + +Instructions on how to run the tutorials are provided in the +[:material-cog-box: Installation & Setup](../installation-and-setup/index.md#how-to-run-tutorials) +section of the documentation. + +The tutorials are organized into the following categories. + +## Getting Started + +- [LBCO `quick` CIF](ed-1.ipynb) – A minimal example intended as a quick + reference for users already familiar with the EasyDiffraction API or + who want to see how Rietveld refinement of the La0.5Ba0.5CoO3 crystal + structure can be performed when both the structure and experiment are + loaded from CIF files. Data collected from constant wavelength neutron + powder diffraction at HRPT at PSI. +- [LBCO `quick` `code`](ed-2.ipynb) – A minimal example intended as a + quick reference for users already familiar with the EasyDiffraction + API or who want to see an example refinement when both the structure + and experiment are defined directly in code. This tutorial covers a + Rietveld refinement of the La0.5Ba0.5CoO3 crystal structure using + constant wavelength neutron powder diffraction data from HRPT at PSI. +- [LBCO `complete`](ed-3.ipynb) – Demonstrates the use of the + EasyDiffraction API in a simplified, user-friendly manner that closely + follows the GUI workflow for a Rietveld refinement of the + La0.5Ba0.5CoO3 crystal structure using constant wavelength neutron + powder diffraction data from HRPT at PSI. This tutorial provides a + full explanation of the workflow with detailed comments and + descriptions of every step, making it suitable for users who are new + to EasyDiffraction or those who prefer a more guided approach. + +## Powder Diffraction + +- [Co2SiO4 `pd-neut-cwl`](ed-5.ipynb) – Demonstrates a Rietveld + refinement of the Co2SiO4 crystal structure using constant wavelength + neutron powder diffraction data from D20 at ILL. +- [HS `pd-neut-cwl`](ed-6.ipynb) – Demonstrates a Rietveld refinement of + the HS crystal structure using constant wavelength neutron powder + diffraction data from HRPT at PSI. +- [Si `pd-neut-tof`](ed-7.ipynb) – Demonstrates a Rietveld refinement of + the Si crystal structure using time-of-flight neutron powder + diffraction data from SEPD at Argonne. +- [NCAF `pd-neut-tof`](ed-8.ipynb) – Demonstrates a Rietveld refinement + of the Na2Ca3Al2F14 crystal structure using two time-of-flight neutron + powder diffraction datasets (from two detector banks) of the WISH + instrument at ISIS. + +## Single Crystal Diffraction + +- [Tb2TiO7 `sg-neut-cwl`](ed-14.ipynb) – Demonstrates structure + refinement of Tb2TiO7 using constant wavelength neutron single crystal + diffraction data from HEiDi at FRM II. +- [Taurine `sg-neut-tof`](ed-15.ipynb) – Demonstrates structure + refinement of Taurine using time-of-flight neutron single crystal + diffraction data from SENJU at J-PARC. + +## Pair Distribution Function (PDF) + +- [Ni `pd-neut-cwl`](ed-10.ipynb) – Demonstrates a PDF analysis of Ni + using data collected from a constant wavelength neutron powder + diffraction experiment. +- [Si `pd-neut-tof`](ed-11.ipynb) – Demonstrates a PDF analysis of Si + using data collected from a time-of-flight neutron powder diffraction + experiment at NOMAD at SNS. +- [NaCl `pd-xray`](ed-12.ipynb) – Demonstrates a PDF analysis of NaCl + using data collected from an X-ray powder diffraction experiment. + +## Multi-Structure & Multi-Experiment Refinement + +- [PbSO4 NPD+XRD](ed-4.ipynb) – Joint fit of PbSO4 using X-ray and + neutron constant wavelength powder diffraction data. +- [LBCO+Si McStas](ed-9.ipynb) – Multi-phase Rietveld refinement of + La0.5Ba0.5CoO3 with Si impurity using time-of-flight neutron data + simulated with McStas. +- [Si Bragg+PDF](ed-16.ipynb) – Joint refinement of Si combining Bragg + diffraction (SEPD) and pair distribution function (NOMAD) analysis. A + single shared structure is refined simultaneously against both + datasets. + +## Workshops & Schools + +- [DMSC Summer School](ed-13.ipynb) – A workshop tutorial that + demonstrates a Rietveld refinement of the La0.5Ba0.5CoO3 crystal + structure using time-of-flight neutron powder diffraction data + simulated with McStas. This tutorial is designed for the ESS DMSC + Summer School. diff --git a/docs/user-guide/analysis-workflow/analysis.md b/docs/docs/user-guide/analysis-workflow/analysis.md similarity index 68% rename from docs/user-guide/analysis-workflow/analysis.md rename to docs/docs/user-guide/analysis-workflow/analysis.md index 73086b3b..f0341519 100644 --- a/docs/user-guide/analysis-workflow/analysis.md +++ b/docs/docs/user-guide/analysis-workflow/analysis.md @@ -5,57 +5,62 @@ icon: material/calculator # :material-calculator: Analysis This section provides an overview of **diffraction data analysis** in -EasyDiffraction, focusing on model-dependent analysis, calculation engines, and -minimization techniques. +EasyDiffraction, focusing on model-dependent analysis, calculation +engines, and minimization techniques. -In EasyDiffraction, we focus on **model-dependent analysis**, where a model is -constructed based on prior knowledge of the studied system, and its parameters -are optimized to achieve the best agreement between experimental and calculated -diffraction data. Model-dependent analysis is widely used in neutron and X-ray -scattering data. +In EasyDiffraction, we focus on **model-dependent analysis**, where a +model is constructed based on prior knowledge of the studied system, and +its parameters are optimized to achieve the best agreement between +experimental and calculated diffraction data. Model-dependent analysis +is widely used in neutron and X-ray scattering data. ## Calculation -EasyDiffraction relies on third-party crystallographic libraries, referred to as -**calculation engines** or just **calculators**, to perform the calculations. +EasyDiffraction relies on third-party crystallographic libraries, +referred to as **calculation engines** or just **calculators**, to +perform the calculations. -The calculation engines are used to calculate the diffraction pattern for the -defined model of the studied structure using the instrumental and other required -experiment-related parameters, such as the wavelength, resolution, etc. +The calculation engines are used to calculate the diffraction pattern +for the defined model of the studied structure using the instrumental +and other required experiment-related parameters, such as the +wavelength, resolution, etc. -You do not necessarily need the measured data to perform the calculations, but -you need a structural model and some details about the type of experiment you -want to simulate. +You do not necessarily need the measured data to perform the +calculations, but you need a structural model and some details about the +type of experiment you want to simulate. -EasyDiffraction is designed as a flexible and extensible tool that supports -different **calculation engines** for diffraction pattern calculations. -Currently, we integrate CrysPy, CrysFML, and PDFfit2 libraries as calculation -engines. +EasyDiffraction is designed as a flexible and extensible tool that +supports different **calculation engines** for diffraction pattern +calculations. Currently, we integrate CrysPy, CrysFML, and PDFfit2 +libraries as calculation engines. ### CrysPy Calculator -[CrysPy](https://www.cryspy.fr) is a Python library originally developed for -analysing polarised neutron diffraction data. It is now evolving into a more -general purpose library and covers powders and single crystals, nuclear and -(commensurate) magnetic structures, unpolarised neutron and X-ray diffraction. +[CrysPy](https://www.cryspy.fr) is a Python library originally developed +for analysing polarised neutron diffraction data. It is now evolving +into a more general purpose library and covers powders and single +crystals, nuclear and (commensurate) magnetic structures, unpolarised +neutron and X-ray diffraction. ### CrysFML Calculator -[CrysFML](https://code.ill.fr/scientific-software/CrysFML2008) library is a -collection of Fortran modules for crystallographic computations. It is used in -the software package [FullProf](https://www.ill.eu/sites/fullprof/), and we are -currently working on its integration into EasyDiffraction. +[CrysFML](https://code.ill.fr/scientific-software/CrysFML2008) library +is a collection of Fortran modules for crystallographic computations. It +is used in the software package +[FullProf](https://www.ill.eu/sites/fullprof/), and we are currently +working on its integration into EasyDiffraction. ### PDFfit2 Calculator -[PDFfit2](https://github.com/diffpy/diffpy.pdffit2/) is a Python library for -calculating the pair distribution function (PDF) from crystallographic models. +[PDFfit2](https://github.com/diffpy/diffpy.pdffit2/) is a Python library +for calculating the pair distribution function (PDF) from +crystallographic models. ### Set Calculator -The calculator is automatically selected based on the experiment type (e.g., -`cryspy` for Bragg diffraction, `pdffit` for total scattering). To show the -supported calculation engines for a specific experiment: +The calculator is automatically selected based on the experiment type +(e.g., `cryspy` for Bragg diffraction, `pdffit` for total scattering). +To show the supported calculation engines for a specific experiment: ```python project.experiments['hrpt'].show_supported_calculator_types() @@ -77,9 +82,9 @@ project.experiments['hrpt'].calculator_type = 'cryspy' ## Minimization / Optimization -The process of refining model parameters involves iterating through multiple -steps until the calculated data sufficiently matches the experimental data. This -process is illustrated in the following diagram: +The process of refining model parameters involves iterating through +multiple steps until the calculated data sufficiently matches the +experimental data. This process is illustrated in the following diagram: ```mermaid flowchart LR @@ -95,31 +100,34 @@ flowchart LR d-- Threshold
reached -->e ``` -Like the calculation engines, EasyDiffraction is designed to utilize various -third-party libraries for model refinement and parameter optimization. These -libraries provide robust curve fitting and uncertainty estimation tools. +Like the calculation engines, EasyDiffraction is designed to utilize +various third-party libraries for model refinement and parameter +optimization. These libraries provide robust curve fitting and +uncertainty estimation tools. ### Lmfit Minimizer Most of the examples in this section will use the -[lmfit](https://lmfit.github.io/lmfit-py/) package, which provides a high-level -interface to non-linear optimisation and curve fitting problems for Python. It -is one of the tools that can be used to fit models to the experimental data. +[lmfit](https://lmfit.github.io/lmfit-py/) package, which provides a +high-level interface to non-linear optimisation and curve fitting +problems for Python. It is one of the tools that can be used to fit +models to the experimental data. ### Bumps Minimizer Another package that can be used for the same purpose is -[bumps](https://bumps.readthedocs.io/en/latest/). In addition to traditional -optimizers which search for the best minimum they can find in the search space, -bumps provides Bayesian uncertainty analysis which explores all viable minima -and finds confidence intervals on the parameters based on uncertainty in the -measured values. +[bumps](https://bumps.readthedocs.io/en/latest/). In addition to +traditional optimizers which search for the best minimum they can find +in the search space, bumps provides Bayesian uncertainty analysis which +explores all viable minima and finds confidence intervals on the +parameters based on uncertainty in the measured values. ### DFO-LS Minimizer -[DFO-LS](https://github.com/numericalalgorithmsgroup/dfols) (Derivative-Free -Optimizer for Least-Squares) is a Python library for solving nonlinear -least-squares minimization, without requiring derivatives of the objective. +[DFO-LS](https://github.com/numericalalgorithmsgroup/dfols) +(Derivative-Free Optimizer for Least-Squares) is a Python library for +solving nonlinear least-squares minimization, without requiring +derivatives of the objective. ### Set Minimizer @@ -148,9 +156,10 @@ project.analysis.current_minimizer = 'lmfit' ### Fit Mode -In EasyDiffraction, you can set the **fit mode** to control how the refinement -process is performed. The fit mode determines whether the refinement is -performed independently for each experiment or jointly across all experiments. +In EasyDiffraction, you can set the **fit mode** to control how the +refinement process is performed. The fit mode determines whether the +refinement is performed independently for each experiment or jointly +across all experiments. The supported fit modes are: @@ -173,14 +182,14 @@ print(project.analysis.fit_mode.mode.value) ### Perform Fit -Refining the structure and experiment parameters against measured data is -usually divided into several steps, where each step involves adding or removing -parameters to be refined, calculating the model data, and comparing it to the -experimental data as shown in the diagram above. +Refining the structure and experiment parameters against measured data +is usually divided into several steps, where each step involves adding +or removing parameters to be refined, calculating the model data, and +comparing it to the experimental data as shown in the diagram above. -To select the parameters to be refined, you can set the attribute `free` of the -parameters to `True`. This indicates that the parameter is free to be optimized -during the refinement process. +To select the parameters to be refined, you can set the attribute `free` +of the parameters to `True`. This indicates that the parameter is free +to be optimized during the refinement process. Here is an example of how to set parameters to be refined: @@ -195,15 +204,16 @@ project.experiments['hrpt'].background['10'].y.free = True project.experiments['hrpt'].background['165'].y.free = True ``` -After setting the parameters to be refined, you can perform the fit using the -`fit` method of the `analysis` object: +After setting the parameters to be refined, you can perform the fit +using the `fit` method of the `analysis` object: ```python project.analysis.fit() ``` -This method will iterate through the defined steps, adjusting the parameters -until the calculated data sufficiently matches the experimental data. +This method will iterate through the defined steps, adjusting the +parameters until the calculated data sufficiently matches the +experimental data. An example of the output after performing the fit is: @@ -233,9 +243,9 @@ Fit results 📈 Fitted parameters: ``` -Now, you can inspect the fitted parameters to see how they have changed during -the refinement process, select more parameters to be refined, and perform -additional fits as needed. +Now, you can inspect the fitted parameters to see how they have changed +during the refinement process, select more parameters to be refined, and +perform additional fits as needed. To plot the measured vs calculated data after the fit, you can use the `plot_meas_vs_calc` method of the `analysis` object: @@ -246,16 +256,16 @@ project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) ## Constraints -In EasyDiffraction, you can define **constraints** on the model parameters to -ensure that they remain within a specific range or follow a certain relationship -during the refinement process. +In EasyDiffraction, you can define **constraints** on the model +parameters to ensure that they remain within a specific range or follow +a certain relationship during the refinement process. ### Setting Aliases -Before setting constraints, you need to set aliases for the parameters you want -to constrain. This can be done using the `add` method of the `aliases` object. -Aliases are used to reference parameters in a more readable way, making it -easier to manage constraints. +Before setting constraints, you need to set aliases for the parameters +you want to constrain. This can be done using the `add` method of the +`aliases` object. Aliases are used to reference parameters in a more +readable way, making it easier to manage constraints. An example of setting aliases for parameters in a structure: @@ -283,11 +293,11 @@ project.analysis.aliases.create( ### Setting Constraints -Now that you have set the aliases, you can define constraints using the `add` -method of the `constraints` object. Constraints are defined by specifying the -**left-hand side (lhs) alias** and the **right-hand side (rhs) expression**. The -rhs expression can be a simple alias or a more complex expression involving -other aliases. +Now that you have set the aliases, you can define constraints using the +`add` method of the `constraints` object. Constraints are defined by +specifying the **left-hand side (lhs) alias** and the **right-hand side +(rhs) expression**. The rhs expression can be a simple alias or a more +complex expression involving other aliases. An example of setting constraints for the aliases defined above: @@ -303,15 +313,16 @@ project.analysis.constraints.create( ) ``` -These constraints ensure that the `biso_Ba` parameter is equal to `biso_La`, and -the `occ_Ba` parameter is equal to `1 - occ_La`. This means that the occupancy -of the Ba atom will always be adjusted based on the occupancy of the La atom, -and the isotropic displacement parameter for Ba will be equal to that of La -during the refinement process. +These constraints ensure that the `biso_Ba` parameter is equal to +`biso_La`, and the `occ_Ba` parameter is equal to `1 - occ_La`. This +means that the occupancy of the Ba atom will always be adjusted based on +the occupancy of the La atom, and the isotropic displacement parameter +for Ba will be equal to that of La during the refinement process. ### Viewing Constraints -To view the defined constraints, you can use the `show_constraints` method: +To view the defined constraints, you can use the `show_constraints` +method: ```python project.analysis.show_constraints() @@ -361,8 +372,9 @@ Example output: ## Saving an Analysis -Saving the project, as described in the [Project](project.md) section, will also -save the analysis settings to the `analysis.cif` inside the project directory. +Saving the project, as described in the [Project](project.md) section, +will also save the analysis settings to the `analysis.cif` inside the +project directory.
diff --git a/docs/user-guide/analysis-workflow/experiment.md b/docs/docs/user-guide/analysis-workflow/experiment.md similarity index 84% rename from docs/user-guide/analysis-workflow/experiment.md rename to docs/docs/user-guide/analysis-workflow/experiment.md index a62d9822..c121d6a9 100644 --- a/docs/user-guide/analysis-workflow/experiment.md +++ b/docs/docs/user-guide/analysis-workflow/experiment.md @@ -4,10 +4,10 @@ icon: material/microscope # :material-microscope: Experiment -An **Experiment** in EasyDiffraction includes the measured diffraction data -along with all relevant parameters that describe the experimental setup and -associated conditions. This can include information about the instrumental -resolution, peak shape, background, etc. +An **Experiment** in EasyDiffraction includes the measured diffraction +data along with all relevant parameters that describe the experimental +setup and associated conditions. This can include information about the +instrumental resolution, peak shape, background, etc. ## Defining an Experiment @@ -15,23 +15,26 @@ EasyDiffraction allows you to: - **Load an existing experiment** from a file (**CIF** format). Both the metadata and measured data are expected to be in CIF format. -- **Manually define** a new experiment by specifying its type, other necessary - experimental parameters, as well as load measured data. This is useful when - you want to create an experiment from scratch or when you have a measured data - file in a non-CIF format (e.g., `.xye`, `.xy`). - -Below, you will find instructions on how to define and manage experiments in -EasyDiffraction. It is assumed that you have already created a `project` object, -as described in the [Project](project.md) section as well as defined its -`structures`, as described in the [Structure](model.md) section. +- **Manually define** a new experiment by specifying its type, other + necessary experimental parameters, as well as load measured data. This + is useful when you want to create an experiment from scratch or when + you have a measured data file in a non-CIF format (e.g., `.xye`, + `.xy`). + +Below, you will find instructions on how to define and manage +experiments in EasyDiffraction. It is assumed that you have already +created a `project` object, as described in the [Project](project.md) +section as well as defined its `structures`, as described in the +[Structure](model.md) section. ### Adding from CIF -This is the most straightforward way to define an experiment in EasyDiffraction. -If you have a crystallographic information file (CIF) for your experiment, that -contains both the necessary information (metadata) about the experiment as well -as the measured data, you can add it to your `project.experiments` collection -using the `add_from_cif_path` method. In this case, the name of the experiment +This is the most straightforward way to define an experiment in +EasyDiffraction. If you have a crystallographic information file (CIF) +for your experiment, that contains both the necessary information +(metadata) about the experiment as well as the measured data, you can +add it to your `project.experiments` collection using the +`add_from_cif_path` method. In this case, the name of the experiment will be taken from CIF. ```python @@ -51,9 +54,9 @@ project.experiments.add_from_cif_str(cif_string) ``` Accessing the experiment after adding it will also be done through the -`experiments` object of the `project` instance. The name of the experiment will -be the same as the data block id in the CIF file. For example, if the CIF file -contains a data block with the id `hrpt`, +`experiments` object of the `project` instance. The name of the +experiment will be the same as the data block id in the CIF file. For +example, if the CIF file contains a data block with the id `hrpt`, @@ -77,20 +80,22 @@ project.experiments['hrpt'] ### Defining Manually -If you do not have a CIF file or prefer to define the experiment manually, you -can use the `add_from_data_path` method of the `experiments` object of the -`project` instance. In this case, you will need to specify the **name** of the -experiment, which will be used to reference it later, as well as **data_path** -to the measured data file (e.g., `.xye`, `.xy`). Supported formats are described -in the [Measured Data Category](#5-measured-data-category) section. +If you do not have a CIF file or prefer to define the experiment +manually, you can use the `add_from_data_path` method of the +`experiments` object of the `project` instance. In this case, you will +need to specify the **name** of the experiment, which will be used to +reference it later, as well as **data_path** to the measured data file +(e.g., `.xye`, `.xy`). Supported formats are described in the +[Measured Data Category](#measured-data-category) section. -Optionally, you can also specify the additional parameters that define the -**type of experiment** you want to create. If you do not specify any of these -parameters, the default values will be used, which are the first in the list of -supported options for each parameter: +Optionally, you can also specify the additional parameters that define +the **type of experiment** you want to create. If you do not specify any +of these parameters, the default values will be used, which are the +first in the list of supported options for each parameter: - **sample_form**: The form of the sample (powder, single crystal). -- **beam_mode**: The mode of the beam (constant wavelength, time-of-flight). +- **beam_mode**: The mode of the beam (constant wavelength, + time-of-flight). - **radiation_probe**: The type of radiation used (neutron, X-ray). - **scattering_type**: The type of scattering (bragg, total). @@ -100,8 +105,8 @@ supported options for each parameter: these parameters. If you need to change them, you must create a new experiment or redefine the existing one. -Here is an example of how to add an experiment with all relevant components -explicitly defined: +Here is an example of how to add an experiment with all relevant +components explicitly defined: ```python # Add an experiment with default parameters, based on the specified type. @@ -125,9 +130,9 @@ project.experiments.add_from_data_path( ) ``` -If you do not have measured data for fitting and only want to view the simulated -pattern, you can define an experiment without measured data using the `create` -method: +If you do not have measured data for fitting and only want to view the +simulated pattern, you can define an experiment without measured data +using the `create` method: ```python # Add an experiment without measured data @@ -159,23 +164,24 @@ project.experiments.add(experiment) ## Modifying Parameters -When an experiment is added, it is created with a set of default parameters that -you can modify to match your specific experimental setup. All parameters are -grouped into categories based on their function, making it easier to manage and -understand the different aspects of the experiment: - -1. **Instrument Category**: Defines the instrument configuration, including - wavelength, two-theta offset, and resolution parameters. -2. **Peak Category**: Specifies the peak profile type and its parameters, such - as broadening and asymmetry. -3. **Background Category**: Defines the background type and allows you to add - background points. -4. **Linked Phases Category**: Links the structure defined in the previous step - to the experiment, allowing you to specify the scale factor for the linked - phase. -5. **Measured Data Category**: Contains the measured data. The expected format - depends on the experiment type, but generally includes columns for 2θ angle - or TOF and intensity. +When an experiment is added, it is created with a set of default +parameters that you can modify to match your specific experimental +setup. All parameters are grouped into categories based on their +function, making it easier to manage and understand the different +aspects of the experiment: + +1. **Instrument Category**: Defines the instrument configuration, + including wavelength, two-theta offset, and resolution parameters. +2. **Peak Category**: Specifies the peak profile type and its + parameters, such as broadening and asymmetry. +3. **Background Category**: Defines the background type and allows you + to add background points. +4. **Linked Phases Category**: Links the structure defined in the + previous step to the experiment, allowing you to specify the scale + factor for the linked phase. +5. **Measured Data Category**: Contains the measured data. The expected + format depends on the experiment type, but generally includes columns + for 2θ angle or TOF and intensity. ### 1. Instrument Category { #instrument-category } @@ -230,10 +236,10 @@ project.experiments['hrpt'].linked_phases.create(id='lbco', scale=10.0) ### 6. Measured Data Category { #measured-data-category } -If you do not have a CIF file for your experiment, you can load measured data -from a file in a supported format. The measured data will be automatically -converted into CIF format and added to the experiment. The expected format -depends on the experiment type. +If you do not have a CIF file for your experiment, you can load measured +data from a file in a supported format. The measured data will be +automatically converted into CIF format and added to the experiment. The +expected format depends on the experiment type. #### Supported data file formats: @@ -245,8 +251,8 @@ depends on the experiment type. - [\_pd_meas.2theta_scan](../parameters/pd_meas.md) - [\_pd_meas.intensity_total](../parameters/pd_meas.md) -If no **standard deviations** are provided, they are automatically calculated as -the **square root** of measured intensities. +If no **standard deviations** are provided, they are automatically +calculated as the **square root** of measured intensities. Optional comments with `#` are possible in data file headers. @@ -602,5 +608,5 @@ loop_ --- -Now that the experiment has been defined, you can proceed to the next step: -[Analysis](analysis.md). +Now that the experiment has been defined, you can proceed to the next +step: [Analysis](analysis.md). diff --git a/docs/docs/user-guide/analysis-workflow/index.md b/docs/docs/user-guide/analysis-workflow/index.md new file mode 100644 index 00000000..84598210 --- /dev/null +++ b/docs/docs/user-guide/analysis-workflow/index.md @@ -0,0 +1,37 @@ +# Analysis Workflow + +To streamline the **data analysis process**, EasyDiffraction follows a +structured workflow divided into **five key steps**: + +```mermaid +flowchart LR + a(Project) + b(Model) + c(Experiment) + d(Analysis) + e(Summary) + a --> b + b --> c + c --> d + d --> e +``` + +- [:material-archive: Project](project.md) – Establish a **project** as + a container for structure and experiment parameters, measured and + calculated data, analysis settings and results. +- [:material-puzzle: Structure](model.md) – Load an existing + **crystallographic model** in CIF format or define a new one from + scratch. +- [:material-microscope: Experiment](experiment.md) – Import + **experimental diffraction data** and configure **instrumental** and + other relevant parameters. +- [:material-calculator: Analysis](analysis.md) – **Calculate the + diffraction pattern** and **optimize the structural model** by + refining its parameters to match experimental measurements. +- [:material-clipboard-text: Summary](summary.md) – Generate a + **report** summarizing the results of the analysis, including refined + parameters. + +Each step is described in detail in its respective section, guiding +users through the **entire diffraction data analysis workflow** in +EasyDiffraction. diff --git a/docs/user-guide/analysis-workflow/model.md b/docs/docs/user-guide/analysis-workflow/model.md similarity index 77% rename from docs/user-guide/analysis-workflow/model.md rename to docs/docs/user-guide/analysis-workflow/model.md index c437ea62..95e309cd 100644 --- a/docs/user-guide/analysis-workflow/model.md +++ b/docs/docs/user-guide/analysis-workflow/model.md @@ -5,35 +5,38 @@ icon: material/puzzle # :material-puzzle: Structure The **Structure** in EasyDiffraction represents the **crystallographic -structure** used to calculate the diffraction pattern, which is then fitted to -the **experimentally measured data** to refine the structural parameters. +structure** used to calculate the diffraction pattern, which is then +fitted to the **experimentally measured data** to refine the structural +parameters. EasyDiffraction allows you to: - **Load an existing model** from a file (**CIF** format). -- **Manually define** a new structure by specifying crystallographic parameters. +- **Manually define** a new structure by specifying crystallographic + parameters. -Below, you will find instructions on how to define and manage crystallographic -models in EasyDiffraction. It is assumed that you have already created a -`project` object, as described in the [Project](project.md) section. +Below, you will find instructions on how to define and manage +crystallographic models in EasyDiffraction. It is assumed that you have +already created a `project` object, as described in the +[Project](project.md) section. ## Adding a Model from CIF -This is the most straightforward way to define a structure in EasyDiffraction. -If you have a crystallographic information file (CIF) for your structure, you -can add it to your project using the `add_from_cif_path` method of the -`project.structures` collection. In this case, the name of the model will be -taken from CIF. +This is the most straightforward way to define a structure in +EasyDiffraction. If you have a crystallographic information file (CIF) +for your structure, you can add it to your project using the +`add_from_cif_path` method of the `project.structures` collection. In +this case, the name of the model will be taken from CIF. ```python # Load a phase from a CIF file project.structures.add_from_cif_path('data/lbco.cif') ``` -Accessing the model after loading it will be done through the `structures` -collection of the `project` instance. The name of the model will be the same as -the data block id in the CIF file. For example, if the CIF file contains a data -block with the id `lbco`, +Accessing the model after loading it will be done through the +`structures` collection of the `project` instance. The name of the model +will be the same as the data block id in the CIF file. For example, if +the CIF file contains a data block with the id `lbco`, @@ -57,10 +60,10 @@ project.structures['lbco'] ## Defining a Model Manually -If you do not have a CIF file or prefer to define the model manually, you can -use the `create` method of the `structures` object of the `project` instance. In -this case, you will need to specify the name of the model, which will be used to -reference it later. +If you do not have a CIF file or prefer to define the model manually, +you can use the `create` method of the `structures` object of the +`project` instance. In this case, you will need to specify the name of +the model, which will be used to reference it later. ```python # Add a structure with default parameters @@ -68,15 +71,17 @@ reference it later. project.structures.create(name='nacl') ``` -The `add` method creates a new structure with default parameters. You can then -modify its parameters to match your specific crystallographic structure. All -parameters are grouped into the following categories, which makes it easier to -manage the model: +The `add` method creates a new structure with default parameters. You +can then modify its parameters to match your specific crystallographic +structure. All parameters are grouped into the following categories, +which makes it easier to manage the model: -1. **Space Group Category**: Defines the symmetry of the crystal structure. -2. **Cell Category**: Specifies the dimensions and angles of the unit cell. -3. **Atom Sites Category**: Describes the positions and properties of atoms - within the unit cell. +1. **Space Group Category**: Defines the symmetry of the crystal + structure. +2. **Cell Category**: Specifies the dimensions and angles of the unit + cell. +3. **Atom Sites Category**: Describes the positions and properties of + atoms within the unit cell. ### 1. Space Group Category { #space-group-category } @@ -177,10 +182,10 @@ Structure 🧩 'lbco' as cif ## Saving a Model -Saving the project, as described in the [Project](project.md) section, will also -save the model. Each model is saved as a separate CIF file in the `structures` -subdirectory of the project directory. The project file contains references to -these files. +Saving the project, as described in the [Project](project.md) section, +will also save the model. Each model is saved as a separate CIF file in +the `structures` subdirectory of the project directory. The project file +contains references to these files. Below is an example of the saved CIF file for the `lbco` model: @@ -223,5 +228,5 @@ O O 0 0.5 0.5 c 1 Biso 1.4041 --- -Now that the crystallographic model has been defined and added to the project, -you can proceed to the next step: [Experiment](experiment.md). +Now that the crystallographic model has been defined and added to the +project, you can proceed to the next step: [Experiment](experiment.md). diff --git a/docs/user-guide/analysis-workflow/project.md b/docs/docs/user-guide/analysis-workflow/project.md similarity index 85% rename from docs/user-guide/analysis-workflow/project.md rename to docs/docs/user-guide/analysis-workflow/project.md index 45e1d9c5..ad41f852 100644 --- a/docs/user-guide/analysis-workflow/project.md +++ b/docs/docs/user-guide/analysis-workflow/project.md @@ -4,25 +4,26 @@ icon: material/archive # :material-archive: Project -The **Project** serves as a container for all data and metadata associated with -a particular data analysis task. It acts as the top-level entity in -EasyDiffraction, ensuring structured organization and easy access to relevant -information. Each project can contain multiple **experimental datasets**, with -each dataset containing contribution from multiple **structures**. +The **Project** serves as a container for all data and metadata +associated with a particular data analysis task. It acts as the +top-level entity in EasyDiffraction, ensuring structured organization +and easy access to relevant information. Each project can contain +multiple **experimental datasets**, with each dataset containing +contribution from multiple **structures**. EasyDiffraction allows you to: - **Manually create** a new project by specifying its metadata. - **Load an existing project** from a file (**CIF** format). -Below are instructions on how to set up a project in EasyDiffraction. It is -assumed that you have already imported the `easydiffraction` package, as -described in the [First Steps](../first-steps.md) section. +Below are instructions on how to set up a project in EasyDiffraction. It +is assumed that you have already imported the `easydiffraction` package, +as described in the [First Steps](../first-steps.md) section. ## Creating a Project Manually -You can manually create a new project and specify its short **name**, **title** -and **description**. All these parameters are optional. +You can manually create a new project and specify its short **name**, +**title** and **description**. All these parameters are optional. ```py # Create a new project @@ -44,10 +45,11 @@ Saving the initial project requires specifying the directory path: project.save_as(dir_path='lbco_hrpt') ``` -If working in the interactive mode in a Jupyter notebook or similar environment, -you can also save the project after every significant change. This is useful for -keeping track of changes and ensuring that your work is not lost. If you already -saved the project with `save_as`, you can just call the `save`: +If working in the interactive mode in a Jupyter notebook or similar +environment, you can also save the project after every significant +change. This is useful for keeping track of changes and ensuring that +your work is not lost. If you already saved the project with `save_as`, +you can just call the `save`: ```python project.save() @@ -55,8 +57,9 @@ project.save() ## Loading a Project from CIF -If you have an existing project, you can load it directly from a CIF file. This -is useful for reusing previously defined projects or sharing them with others. +If you have an existing project, you can load it directly from a CIF +file. This is useful for reusing previously defined projects or sharing +them with others. ```python project.load('data/lbco_hrpt.cif') @@ -89,8 +92,8 @@ The example below illustrates a typical **project structure** for a ## Project Files -Below is a complete project example stored in the `La0.5Ba0.5CoO3` directory, -showing the contents of all files in the project. +Below is a complete project example stored in the `La0.5Ba0.5CoO3` +directory, showing the contents of all files in the project. !!! warning "Important" @@ -101,8 +104,8 @@ showing the contents of all files in the project. ### 1. project.cif -This file provides an overview of the project, including file names of the -**structures** and **experiments** associated with the project. +This file provides an overview of the project, including file names of +the **structures** and **experiments** associated with the project. @@ -127,9 +130,9 @@ hrpt.cif ### 2. structures / lbco.cif -This file contains crystallographic information associated with the structure -model, including **space group**, **unit cell parameters**, and **atomic -positions**. +This file contains crystallographic information associated with the +structure model, including **space group**, **unit cell parameters**, +and **atomic positions**. @@ -168,9 +171,9 @@ O O 0 0.5 0.5 c 1 Biso 1.4041 ### 3. experiments / hrpt.cif -This file contains the **experiment type**, **instrumental parameters**, **peak -parameters**, **associated phases**, **background parameters** and **measured -diffraction data**. +This file contains the **experiment type**, **instrumental parameters**, +**peak parameters**, **associated phases**, **background parameters** +and **measured diffraction data**. @@ -236,8 +239,8 @@ loop_ ### 4. analysis.cif -This file contains settings used for data analysis, including the choice of -**calculation** and **fitting** engines, as well as user defined +This file contains settings used for data analysis, including the choice +of **calculation** and **fitting** engines, as well as user defined **constraints**. diff --git a/docs/user-guide/analysis-workflow/summary.md b/docs/docs/user-guide/analysis-workflow/summary.md similarity index 72% rename from docs/user-guide/analysis-workflow/summary.md rename to docs/docs/user-guide/analysis-workflow/summary.md index 4790f857..34a89b61 100644 --- a/docs/user-guide/analysis-workflow/summary.md +++ b/docs/docs/user-guide/analysis-workflow/summary.md @@ -5,20 +5,20 @@ icon: material/clipboard-text # :material-clipboard-text: Summary The **Summary** section represents the final step in the data processing -workflow. It involves generating a **summary report** that consolidates the -results of the diffraction data analysis, providing a comprehensive overview of -the model refinement process and its outcomes. +workflow. It involves generating a **summary report** that consolidates +the results of the diffraction data analysis, providing a comprehensive +overview of the model refinement process and its outcomes. ## Contents of the Summary Report The summary report includes key details such as: -- Final refined model parameters – Optimized crystallographic and instrumental - parameters. -- Goodness-of-fit indicators – Metrics such as R-factors, chi-square (χ²), and - residuals. -- Graphical representation – Visualization of experimental vs. calculated - diffraction patterns. +- Final refined model parameters – Optimized crystallographic and + instrumental parameters. +- Goodness-of-fit indicators – Metrics such as R-factors, chi-square + (χ²), and residuals. +- Graphical representation – Visualization of experimental vs. + calculated diffraction patterns. ## Viewing the Summary Report @@ -36,8 +36,9 @@ including model parameters, fit statistics, and data visualizations. ## Saving a Summary -Saving the project, as described in the [Project](project.md) section, will also -save the summary report to the `summary.cif` inside the project directory. +Saving the project, as described in the [Project](project.md) section, +will also save the summary report to the `summary.cif` inside the +project directory. ![](../assets/images/user-guide/data-acquisition_instrument.png){ width="450", loading=lazy } @@ -42,10 +43,10 @@ Credits: DOI 10.1126/science.1238932 ## Data Reduction -Data reduction involves processing the raw data to remove background noise, -correct for instrumental effects, and convert the data into a more usable -format. The goal is to produce a clean and reliable dataset suitable for -analysis. +Data reduction involves processing the raw data to remove background +noise, correct for instrumental effects, and convert the data into a +more usable format. The goal is to produce a clean and reliable dataset +suitable for analysis. ![](../assets/images/user-guide/data-reduction_1d-pattern.png){ width="450", loading=lazy } @@ -57,28 +58,32 @@ Credits: DOI 10.1126/science.1238932 ## Data Analysis -Data analysis uses the reduced data to extract meaningful information about the -crystallographic structure. This may include determining the crystal or magnetic -structure, identifying phases, performing quantitative analysis, etc. - -Analysis often involves comparing experimental data with data calculated from a -crystallographic model to validate and interpret the results. For powder -diffraction, techniques such as Rietveld or Le Bail refinement may be used. - -In EasyDiffraction, we focus on this **model-dependent analysis**. A model is -built using prior knowledge of the system, and its parameters are optimized to -achieve the best agreement between experimental and calculated diffraction data. - -By "model", we usually refer to a **crystallographic model** of the sample. This -includes unit cell parameters, space group, atomic positions, thermal -parameters, and more. However, the term "model" also encompasses experimental -aspects such as instrumental resolution, background, peak shape, etc. Therefore, -EasyDiffraction separates the model into two parts: the **structure** and the -**experiment**. - -The aim of data analysis is to refine the structural parameters of the sample by -minimizing the difference (or **residual**) between the experimental and -calculated data — and this is exactly where EasyDiffraction comes into play. +Data analysis uses the reduced data to extract meaningful information +about the crystallographic structure. This may include determining the +crystal or magnetic structure, identifying phases, performing +quantitative analysis, etc. + +Analysis often involves comparing experimental data with data calculated +from a crystallographic model to validate and interpret the results. For +powder diffraction, techniques such as Rietveld or Le Bail refinement +may be used. + +In EasyDiffraction, we focus on this **model-dependent analysis**. A +model is built using prior knowledge of the system, and its parameters +are optimized to achieve the best agreement between experimental and +calculated diffraction data. + +By "model", we usually refer to a **crystallographic model** of the +sample. This includes unit cell parameters, space group, atomic +positions, thermal parameters, and more. However, the term "model" also +encompasses experimental aspects such as instrumental resolution, +background, peak shape, etc. Therefore, EasyDiffraction separates the +model into two parts: the **structure** and the **experiment**. + +The aim of data analysis is to refine the structural parameters of the +sample by minimizing the difference (or **residual**) between the +experimental and calculated data — and this is exactly where +EasyDiffraction comes into play. ![](../assets/images/user-guide/data-analysis_refinement.png){ width="450", loading=lazy } diff --git a/docs/user-guide/data-format.md b/docs/docs/user-guide/data-format.md similarity index 76% rename from docs/user-guide/data-format.md rename to docs/docs/user-guide/data-format.md index 63e2e52e..da7c92b1 100644 --- a/docs/user-guide/data-format.md +++ b/docs/docs/user-guide/data-format.md @@ -1,46 +1,48 @@ # Data Format -Before starting the data analysis workflow, it is important to define the **data -formats** used in EasyDiffraction. +Before starting the data analysis workflow, it is important to define +the **data formats** used in EasyDiffraction. ## Crystallographic Information File -Each software package typically uses its own **data format** and **parameter -names** for storing and sharing data. In EasyDiffraction, we use the -**Crystallographic Information File (CIF)** format, which is widely used in -crystallography and materials science. It provides both a human-readable syntax -and a set of dictionaries that define the meaning of each parameter. +Each software package typically uses its own **data format** and +**parameter names** for storing and sharing data. In EasyDiffraction, we +use the **Crystallographic Information File (CIF)** format, which is +widely used in crystallography and materials science. It provides both a +human-readable syntax and a set of dictionaries that define the meaning +of each parameter. These dictionaries are maintained by the [International Union of Crystallography (IUCr)](https://www.iucr.org). The base dictionary, **coreCIF**, contains the most common parameters in -crystallography. The **pdCIF** dictionary covers parameters specific to powder -diffraction, **magCIF** is used for magnetic structure analysis. +crystallography. The **pdCIF** dictionary covers parameters specific to +powder diffraction, **magCIF** is used for magnetic structure analysis. -As most parameters needed for diffraction data analysis are already covered by -IUCr dictionaries, EasyDiffraction uses the strict **CIF format** and follows -these dictionaries as closely as possible — for both input and output — -throughout the workflow described in the +As most parameters needed for diffraction data analysis are already +covered by IUCr dictionaries, EasyDiffraction uses the strict **CIF +format** and follows these dictionaries as closely as possible — for +both input and output — throughout the workflow described in the [Analysis Workflow](analysis-workflow/index.md) section. The key advantage of CIF is the standardized naming of parameters and -categories, which promotes interoperability and familiarity among researchers. +categories, which promotes interoperability and familiarity among +researchers. If a required parameter is not defined in the standard dictionaries, EasyDiffraction introduces **custom CIF keywords**, documented in the -[Parameters](parameters.md) section under the **CIF name for serialization** -columns. +[Parameters](parameters.md) section under the **CIF name for +serialization** columns. ## Format Comparison -Below, we compare **CIF** with another common data format in programming: -**JSON**. +Below, we compare **CIF** with another common data format in +programming: **JSON**. ### Scientific Journals Let's assume the following structural data for La₀.₅Ba₀.₅CoO₃ (LBCO), as -reported in a scientific publication. These parameters are to be refined during -diffraction data analysis: +reported in a scientific publication. These parameters are to be refined +during diffraction data analysis: Table 1. Crystallographic data. Space group: _Pm3̅m_. @@ -53,8 +55,8 @@ Table 1. Crystallographic data. Space group: _Pm3̅m_. | beta | 90.0 | | gamma | 90.0 | -Table 2. Atomic coordinates (_x_, _y_, _z_), occupancies (occ) and isotropic -displacement parameters (_Biso_) +Table 2. Atomic coordinates (_x_, _y_, _z_), occupancies (occ) and +isotropic displacement parameters (_Biso_) | Label | Type | x | y | z | occ | Biso | | ----- | ---- | --- | --- | --- | --- | ------ | @@ -102,17 +104,17 @@ O O 0 0.5 0.5 c 1 Biso 1.4041 -Here, unit cell parameters are grouped under the `_cell` category, and atomic -positions under the `_atom_site` category. The `loop_` keyword indicates that -multiple rows follow for the listed parameters. Each atom is identified using -`_atom_site.label`. +Here, unit cell parameters are grouped under the `_cell` category, and +atomic positions under the `_atom_site` category. The `loop_` keyword +indicates that multiple rows follow for the listed parameters. Each atom +is identified using `_atom_site.label`. ### JSON -Representing the same data in **JSON** results in a format that is more verbose -and less human-readable, especially for large datasets. JSON is ideal for -structured data in programming environments, whereas CIF is better suited for -human-readable crystallographic data. +Representing the same data in **JSON** results in a format that is more +verbose and less human-readable, especially for large datasets. JSON is +ideal for structured data in programming environments, whereas CIF is +better suited for human-readable crystallographic data. ```json { @@ -173,11 +175,11 @@ human-readable crystallographic data. ## Experiment Definition -The previous example described the **structure** (crystallographic model), but -how is the **experiment** itself represented? +The previous example described the **structure** (crystallographic +model), but how is the **experiment** itself represented? -The experiment is also saved as a CIF file. For example, background intensity in -a powder diffraction experiment might be represented as: +The experiment is also saved as a CIF file. For example, background +intensity in a powder diffraction experiment might be represented as: @@ -197,13 +199,13 @@ loop_ -More details on how to define the experiment in CIF format are provided in the -[Experiment](analysis-workflow/experiment.md) section. +More details on how to define the experiment in CIF format are provided +in the [Experiment](analysis-workflow/experiment.md) section. ## Other Input/Output Blocks -EasyDiffraction uses CIF consistently throughout its workflow, including in the -following blocks: +EasyDiffraction uses CIF consistently throughout its workflow, including +in the following blocks: - **project**: contains the project information - **structure**: defines the structure @@ -217,11 +219,13 @@ Example CIF files for each block are provided in the ## Other Data Formats -While CIF is the primary format in EasyDiffraction, we also support other -formats for importing measured data. These include plain text files with -multiple columns. The meaning of the columns depends on the experiment type. +While CIF is the primary format in EasyDiffraction, we also support +other formats for importing measured data. These include plain text +files with multiple columns. The meaning of the columns depends on the +experiment type. -For example, in a standard constant-wavelength powder diffraction experiment: +For example, in a standard constant-wavelength powder diffraction +experiment: - Column 1: 2θ angle - Column 2: intensity diff --git a/docs/user-guide/first-steps.md b/docs/docs/user-guide/first-steps.md similarity index 56% rename from docs/user-guide/first-steps.md rename to docs/docs/user-guide/first-steps.md index acb50bec..b4366684 100644 --- a/docs/user-guide/first-steps.md +++ b/docs/docs/user-guide/first-steps.md @@ -1,44 +1,44 @@ # First Steps -This section introduces the basic usage of the EasyDiffraction Python API. -You'll learn how to import the package, use core classes and utility functions, -and access built-in helper methods to streamline diffraction data analysis -workflows. +This section introduces the basic usage of the EasyDiffraction Python +API. You'll learn how to import the package, use core classes and +utility functions, and access built-in helper methods to streamline +diffraction data analysis workflows. ## Importing EasyDiffraction ### Importing the entire package -To start using EasyDiffraction, first import the package in your Python script -or Jupyter Notebook. This can be done with the following command: +To start using EasyDiffraction, first import the package in your Python +script or Jupyter Notebook. This can be done with the following command: ```python import easydiffraction ``` -Alternatively, you can import it with an alias to avoid naming conflicts and for -convenience: +Alternatively, you can import it with an alias to avoid naming conflicts +and for convenience: ```python import easydiffraction as ed ``` -The latter syntax allows you to access all the modules and classes within the -package using the `ed` prefix. For example, you can create a project instance -like this: +The latter syntax allows you to access all the modules and classes +within the package using the `ed` prefix. For example, you can create a +project instance like this: ```python project = ed.Project() ``` A complete tutorial using the `import` syntax can be found -[here](../../tutorials/ed-3/). +[here](../tutorials/ed-3.ipynb). ### Importing specific parts -Alternatively, you can import specific classes or methods from the package. For -example, you can import the `Project`, `Structure`, `Experiment` classes and -`download_from_repository` method like this: +Alternatively, you can import specific classes or methods from the +package. For example, you can import the `Project`, `Structure`, +`Experiment` classes and `download_from_repository` method like this: ```python from easydiffraction import Project @@ -47,24 +47,25 @@ from easydiffraction import Experiment from easydiffraction import download_from_repository ``` -This enables you to use these classes and methods directly without the package -prefix. This is especially useful when you're using only a few components and -want to keep your code clean and concise. In this case, you can create a project -instance like this: +This enables you to use these classes and methods directly without the +package prefix. This is especially useful when you're using only a few +components and want to keep your code clean and concise. In this case, +you can create a project instance like this: ```python project = Project() ``` A complete tutorial using the `from` syntax can be found -[here](../../tutorials/ed-4/). +[here](../tutorials/ed-4.ipynb). ## Utility functions -EasyDiffraction also provides several utility functions that can simplify your -workflow. One of them is the `download_from_repository` function, which allows -you to download data files from our remote repository, making it easy to access -and use them while experimenting with EasyDiffraction. +EasyDiffraction also provides several utility functions that can +simplify your workflow. One of them is the `download_from_repository` +function, which allows you to download data files from our remote +repository, making it easy to access and use them while experimenting +with EasyDiffraction. For example, you can download a data file like this: @@ -78,29 +79,31 @@ ed.download_from_repository( ) ``` -This command will download the `hrpt_lbco.xye` file from the `docs` branch of -the EasyDiffraction repository and save it in the `data` directory of your -current working directory. This is particularly useful for quickly accessing -example datasets without having to manually download them. +This command will download the `hrpt_lbco.xye` file from the `docs` +branch of the EasyDiffraction repository and save it in the `data` +directory of your current working directory. This is particularly useful +for quickly accessing example datasets without having to manually +download them. ## Help methods -EasyDiffraction provides several helper methods to display supported engines for -calculation, minimization, and plotting. These methods can be called on the -`Project` instance to display the available options for different categories. +EasyDiffraction provides several helper methods to display supported +engines for calculation, minimization, and plotting. These methods can +be called on the `Project` instance to display the available options for +different categories. ### Supported calculators -The calculator is automatically selected based on the experiment type. You can -use the `show_supported_calculator_types()` method on an experiment to see which -calculation engines are compatible: +The calculator is automatically selected based on the experiment type. +You can use the `show_supported_calculator_types()` method on an +experiment to see which calculation engines are compatible: ```python project.experiments['hrpt'].show_supported_calculator_types() ``` -This will display a list of supported calculators along with their descriptions, -allowing you to choose the one that best fits your needs. +This will display a list of supported calculators along with their +descriptions, allowing you to choose the one that best fits your needs. An example of the output for a Bragg diffraction experiment: @@ -119,24 +122,25 @@ project.show_available_minimizers() ### Available parameters -EasyDiffraction provides several methods for showing the available parameters -grouped in different categories. For example, you can use: +EasyDiffraction provides several methods for showing the available +parameters grouped in different categories. For example, you can use: -- `project.analysis.show_all_params()` – to display all available parameters for - the analysis step. -- `project.analysis.show_fittable_params()` – to display only the parameters - that can be fitted during the analysis. -- `project.analysis.show_free_params()` – to display the parameters that are - currently free to be adjusted during the fitting process. +- `project.analysis.show_all_params()` – to display all available + parameters for the analysis step. +- `project.analysis.show_fittable_params()` – to display only the + parameters that can be fitted during the analysis. +- `project.analysis.show_free_params()` – to display the parameters that + are currently free to be adjusted during the fitting process. -Finally, you can use the `project.analysis.how_to_access_parameters()` method to -get a brief overview of how to access and modify parameters in the analysis -step, along with their unique identifiers in the CIF format. This can be -particularly useful for users who are new to the EasyDiffraction API or those -who want to quickly understand how to work with parameters in their projects. +Finally, you can use the `project.analysis.how_to_access_parameters()` +method to get a brief overview of how to access and modify parameters in +the analysis step, along with their unique identifiers in the CIF +format. This can be particularly useful for users who are new to the +EasyDiffraction API or those who want to quickly understand how to work +with parameters in their projects. -An example of the output for the `project.analysis.how_to_access_parameters()` -method is: +An example of the output for the +`project.analysis.how_to_access_parameters()` method is: | | Code variable | Unique ID for CIF | | --- | --------------------------------------------------- | -------------------------------- | @@ -151,8 +155,9 @@ method is: ### Supported plotters -To see the available plotters, you can use the `show_available_plotters()` -method on the `plotter` attribute of the `Project` instance: +To see the available plotters, you can use the +`show_available_plotters()` method on the `plotter` attribute of the +`Project` instance: ```python project.plotter.show_supported_engines() @@ -167,16 +172,19 @@ An example of the output is: ## Data analysis workflow -Once the EasyDiffraction package is imported, you can proceed with the **data -analysis**. This step can be split into several sub-steps, such as creating a -project, defining structures, adding experimental data, etc. - -EasyDiffraction provides a **Python API** that allows you to perform these steps -programmatically in a certain linear order. This is especially useful for users -who prefer to work in a script or Jupyter Notebook environment. The API is -designed to be intuitive and easy to use, allowing you to focus on the analysis -rather than low-level implementation details. - -Because this workflow is an important part of the EasyDiffraction package, it is -described in detail in the separate -[Analysis Workflow](analysis-workflow/index.md) section of the documentation. +Once the EasyDiffraction package is imported, you can proceed with the +**data analysis**. This step can be split into several sub-steps, such +as creating a project, defining structures, adding experimental data, +etc. + +EasyDiffraction provides a **Python API** that allows you to perform +these steps programmatically in a certain linear order. This is +especially useful for users who prefer to work in a script or Jupyter +Notebook environment. The API is designed to be intuitive and easy to +use, allowing you to focus on the analysis rather than low-level +implementation details. + +Because this workflow is an important part of the EasyDiffraction +package, it is described in detail in the separate +[Analysis Workflow](analysis-workflow/index.md) section of the +documentation. diff --git a/docs/user-guide/glossary.md b/docs/docs/user-guide/glossary.md similarity index 80% rename from docs/user-guide/glossary.md rename to docs/docs/user-guide/glossary.md index 827cfdf5..75f0300c 100644 --- a/docs/user-guide/glossary.md +++ b/docs/docs/user-guide/glossary.md @@ -1,25 +1,29 @@ # Glossary -Before guiding you through the use of EasyDiffraction, we define some common -terms and abbreviations used throughout the documentation and tutorials. +Before guiding you through the use of EasyDiffraction, we define some +common terms and abbreviations used throughout the documentation and +tutorials. ## Dictionary Type Labels -The following labels are used to identify different types of CIF dictionaries: +The following labels are used to identify different types of CIF +dictionaries: - [coreCIF][1]{:.label-cif} – Core CIF dictionary by the [IUCr](https://www.iucr.org). - [pdCIF][2]{:.label-cif} – Powder CIF dictionary by the [IUCr](https://www.iucr.org). -- [easydiffractionCIF][0]{:.label-cif} – Custom CIF dictionary developed for - EasyDiffraction. +- [easydiffractionCIF][0]{:.label-cif} – Custom CIF dictionary developed + for EasyDiffraction. -For more information about CIF, see the [Data Format](data-format.md) section. +For more information about CIF, see the [Data Format](data-format.md) +section. ## Experiment Type Labels -EasyDiffraction supports a variety of experiment types, each with its own set of -parameters. The following labels identify the supported experiment types: +EasyDiffraction supports a variety of experiment types, each with its +own set of parameters. The following labels identify the supported +experiment types: ### Neutron Diffraction @@ -27,8 +31,8 @@ parameters. The following labels identify the supported experiment types: constant wavelength. - [pd-neut-tof][0]{:.label-experiment} – Powder neutron diffraction with time-of-flight. -- [sc-neut-cwl][0]{:.label-experiment} – Single-crystal neutron diffraction with - constant wavelength. +- [sc-neut-cwl][0]{:.label-experiment} – Single-crystal neutron + diffraction with constant wavelength. ### X-ray Diffraction diff --git a/docs/user-guide/index.md b/docs/docs/user-guide/index.md similarity index 57% rename from docs/user-guide/index.md rename to docs/docs/user-guide/index.md index a6671f3d..2ddc28b9 100644 --- a/docs/user-guide/index.md +++ b/docs/docs/user-guide/index.md @@ -4,20 +4,21 @@ icon: material/book-open-variant # :material-book-open-variant: User Guide -This section provides an overview of the **core concepts**, **key parameters** -and **workflow steps** required for using EasyDiffraction effectively. +This section provides an overview of the **core concepts**, **key +parameters** and **workflow steps** required for using EasyDiffraction +effectively. Here is a brief overview of the User Guide sections: -- [Glossary](glossary.md) – Defines common terms and labels used throughout the - documentation. -- [Concept](concept.md) – Introduces the overall idea behind diffraction data - processing and where EasyDiffraction fits. -- [Data Format](data-format.md) – Explains the Crystallographic Information File - (CIF) and how it's used in EasyDiffraction. -- [Parameters](parameters.md) – Describes how parameters are structured, named, - and accessed within the EasyDiffraction library. -- [First Steps](first-steps.md) – Shows how to begin using EasyDiffraction in - Python or Jupyter notebooks. +- [Glossary](glossary.md) – Defines common terms and labels used + throughout the documentation. +- [Concept](concept.md) – Introduces the overall idea behind diffraction + data processing and where EasyDiffraction fits. +- [Data Format](data-format.md) – Explains the Crystallographic + Information File (CIF) and how it's used in EasyDiffraction. +- [Parameters](parameters.md) – Describes how parameters are structured, + named, and accessed within the EasyDiffraction library. +- [First Steps](first-steps.md) – Shows how to begin using + EasyDiffraction in Python or Jupyter notebooks. - [Analysis Workflow](analysis-workflow/index.md) – Breaks down the data analysis pipeline into practical, sequential steps. diff --git a/docs/user-guide/parameters.md b/docs/docs/user-guide/parameters.md similarity index 90% rename from docs/user-guide/parameters.md rename to docs/docs/user-guide/parameters.md index 794f65aa..622bf6f1 100644 --- a/docs/user-guide/parameters.md +++ b/docs/docs/user-guide/parameters.md @@ -1,52 +1,58 @@ # Parameters -The data analysis process, introduced in the [Concept](concept.md) section, -assumes that you mainly work with different parameters. The parameters are used -to describe the structure and the experiment and are required to set up the -analysis. +The data analysis process, introduced in the [Concept](concept.md) +section, assumes that you mainly work with different parameters. The +parameters are used to describe the structure and the experiment and are +required to set up the analysis. -Each parameter in EasyDiffraction has a specific name used for code reference, -and it belongs to a specific category. +Each parameter in EasyDiffraction has a specific name used for code +reference, and it belongs to a specific category. - In many cases, the EasyDiffraction name is the same as the CIF name. -- In some cases, the EasyDiffraction name is a slightly modified version of the - CIF name to comply with Python naming conventions. For example, `name_H-M_alt` - becomes `name_h_m`, replacing hyphens with underscores and using lowercase - letters. -- In rare cases, the EasyDiffraction name is a bit shorter, like `b_iso` instead - of CIF `B_iso_or_equiv`, to make the code a bit more user-friendly. -- When there is no defined CIF name for a parameter, EasyDiffraction introduces - its own name, which is used in the code as well as an equivalent CIF name to - be placed in the custom CIF dictionary `easydiffractionCIF`. - -EasyDiffraction names are used in code, while CIF names are used to store and -retrieve the full state of a data analysis project in CIF format. You can find -more about the project in the [Project](analysis-workflow/project.md) section. +- In some cases, the EasyDiffraction name is a slightly modified version + of the CIF name to comply with Python naming conventions. For example, + `name_H-M_alt` becomes `name_h_m`, replacing hyphens with underscores + and using lowercase letters. +- In rare cases, the EasyDiffraction name is a bit shorter, like `b_iso` + instead of CIF `B_iso_or_equiv`, to make the code a bit more + user-friendly. +- When there is no defined CIF name for a parameter, EasyDiffraction + introduces its own name, which is used in the code as well as an + equivalent CIF name to be placed in the custom CIF dictionary + `easydiffractionCIF`. + +EasyDiffraction names are used in code, while CIF names are used to +store and retrieve the full state of a data analysis project in CIF +format. You can find more about the project in the +[Project](analysis-workflow/project.md) section. ## Parameter Attributes -Parameters in EasyDiffraction are more than just variables. They are objects -that, in addition to the name and value, also include attributes such as the -description, unit, uncertainty, minimum and maximum values, etc. All these -attributes are described in the [API Reference](../api-reference/index.md) -section. Examples of how to use these parameters in code are provided in the +Parameters in EasyDiffraction are more than just variables. They are +objects that, in addition to the name and value, also include attributes +such as the description, unit, uncertainty, minimum and maximum values, +etc. All these attributes are described in the +[API Reference](../api-reference/index.md) section. Examples of how to +use these parameters in code are provided in the [Analysis Workflow](analysis-workflow/index.md) and [Tutorials](../tutorials/index.md) sections. -The most important attribute, besides `name` and `value`, is `free`, which is -used to define whether the parameter is free or fixed for optimization during -the fitting process. The `free` attribute is set to `False` by default, which -means the parameter is fixed. To optimize a parameter, set `free` to `True`. +The most important attribute, besides `name` and `value`, is `free`, +which is used to define whether the parameter is free or fixed for +optimization during the fitting process. The `free` attribute is set to +`False` by default, which means the parameter is fixed. To optimize a +parameter, set `free` to `True`. -Although parameters are central, EasyDiffraction hides their creation and -attribute handling from the user. The user only accesses the required parameters -through the top-level objects, such as `project`, `structures`, `experiments`, -etc. The parameters are created and initialized automatically when a new project -is created or an existing one is loaded. +Although parameters are central, EasyDiffraction hides their creation +and attribute handling from the user. The user only accesses the +required parameters through the top-level objects, such as `project`, +`structures`, `experiments`, etc. The parameters are created and +initialized automatically when a new project is created or an existing +one is loaded. In the following sections, you can see a list of the parameters used in -EasyDiffraction. Use the tabs to switch between how to access a parameter in -code and its CIF name for serialization. +EasyDiffraction. Use the tabs to switch between how to access a +parameter in code and its CIF name for serialization. !!! warning "Important" @@ -59,23 +65,27 @@ code and its CIF name for serialization. project.structures['nacl'].space_group.name_h_m ``` -In the example above, `space_group` is a structure category, and `name_h_m` is -the parameter. For simplicity, only the last part (`category.parameter`) of the -full access name will be shown in the tables below. +In the example above, `space_group` is a structure category, and +`name_h_m` is the parameter. For simplicity, only the last part +(`category.parameter`) of the full access name will be shown in the +tables below. -In addition, the CIF names are also provided for each parameter, which are used -to serialize the parameters in the CIF format. +In addition, the CIF names are also provided for each parameter, which +are used to serialize the parameters in the CIF format. -Tags defining the corresponding experiment type are also given before the table. +Tags defining the corresponding experiment type are also given before +the table. ## Structure parameters -Below is a list of parameters used to describe the structure in EasyDiffraction. +Below is a list of parameters used to describe the structure in +EasyDiffraction. ### Crystall structure parameters -[pd-neut-cwl][3]{:.label-experiment} [pd-neut-tof][3]{:.label-experiment} -[pd-xray][3]{:.label-experiment} [sc-neut-cwl][3]{:.label-experiment} +[pd-neut-cwl][3]{:.label-experiment} +[pd-neut-tof][3]{:.label-experiment} [pd-xray][3]{:.label-experiment} +[sc-neut-cwl][3]{:.label-experiment} === "How to access in the code" @@ -130,8 +140,9 @@ EasyDiffraction. ### Common parameters -[pd-neut-cwl][3]{:.label-experiment} [pd-neut-tof][3]{:.label-experiment} -[pd-xray][3]{:.label-experiment} [sc-neut-cwl][3]{:.label-experiment} +[pd-neut-cwl][3]{:.label-experiment} +[pd-neut-tof][3]{:.label-experiment} [pd-xray][3]{:.label-experiment} +[sc-neut-cwl][3]{:.label-experiment} === "How to access in the code" @@ -153,8 +164,8 @@ EasyDiffraction. ### Standard powder diffraction -[pd-neut-cwl][3]{:.label-experiment} [pd-neut-tof][3]{:.label-experiment} -[pd-xray][3]{:.label-experiment} +[pd-neut-cwl][3]{:.label-experiment} +[pd-neut-tof][3]{:.label-experiment} [pd-xray][3]{:.label-experiment} === "How to access in the code" @@ -240,7 +251,8 @@ EasyDiffraction. ### Total scattering -[pd-neut-total][3]{:.label-experiment} [pd-xray-total][3]{:.label-experiment} +[pd-neut-total][3]{:.label-experiment} +[pd-xray-total][3]{:.label-experiment} === "How to access in the code" diff --git a/docs/user-guide/parameters/_diffrn_radiation.md b/docs/docs/user-guide/parameters/_diffrn_radiation.md similarity index 87% rename from docs/user-guide/parameters/_diffrn_radiation.md rename to docs/docs/user-guide/parameters/_diffrn_radiation.md index e3c0a7e9..6e2f0a06 100644 --- a/docs/user-guide/parameters/_diffrn_radiation.md +++ b/docs/docs/user-guide/parameters/_diffrn_radiation.md @@ -4,13 +4,13 @@ Data items in this category describe the radiation used in measuring the diffraction intensities. Please see the -[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) for -further details. +[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) +for further details. ## [\_diffrn_radiation.probe](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) -The nature of the radiation used (i.e. the name of the subatomic particle or the -region of the electromagnetic spectrum). +The nature of the radiation used (i.e. the name of the subatomic +particle or the region of the electromagnetic spectrum). Supported values: `neutron` and `x-ray` diff --git a/docs/user-guide/parameters/_diffrn_radiation_wavelength.md b/docs/docs/user-guide/parameters/_diffrn_radiation_wavelength.md similarity index 95% rename from docs/user-guide/parameters/_diffrn_radiation_wavelength.md rename to docs/docs/user-guide/parameters/_diffrn_radiation_wavelength.md index 4d205788..3b750b22 100644 --- a/docs/user-guide/parameters/_diffrn_radiation_wavelength.md +++ b/docs/docs/user-guide/parameters/_diffrn_radiation_wavelength.md @@ -4,8 +4,8 @@ Data items in this category describe the wavelength of radiation used in diffraction measurements. Please see the -[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) for -further details. +[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) +for further details. ## [\_diffrn_radiation_wavelength.wavelength](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) diff --git a/docs/user-guide/parameters/_exptl_crystal.md b/docs/docs/user-guide/parameters/_exptl_crystal.md similarity index 100% rename from docs/user-guide/parameters/_exptl_crystal.md rename to docs/docs/user-guide/parameters/_exptl_crystal.md diff --git a/docs/user-guide/parameters/_extinction.md b/docs/docs/user-guide/parameters/_extinction.md similarity index 100% rename from docs/user-guide/parameters/_extinction.md rename to docs/docs/user-guide/parameters/_extinction.md diff --git a/docs/user-guide/parameters/_pd_calib.md b/docs/docs/user-guide/parameters/_pd_calib.md similarity index 94% rename from docs/user-guide/parameters/_pd_calib.md rename to docs/docs/user-guide/parameters/_pd_calib.md index 2b96a4c8..407c5f73 100644 --- a/docs/user-guide/parameters/_pd_calib.md +++ b/docs/docs/user-guide/parameters/_pd_calib.md @@ -2,8 +2,8 @@ # \_pd_calib -This section defines the parameters used for the calibration of the instrument, -similar to this +This section defines the parameters used for the calibration of the +instrument, similar to this [IUCr section](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd). ## [\_pd_calib.2theta_offset](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) diff --git a/docs/user-guide/parameters/atom_site.md b/docs/docs/user-guide/parameters/atom_site.md similarity index 82% rename from docs/user-guide/parameters/atom_site.md rename to docs/docs/user-guide/parameters/atom_site.md index 44325b8a..7acdb650 100644 --- a/docs/user-guide/parameters/atom_site.md +++ b/docs/docs/user-guide/parameters/atom_site.md @@ -2,16 +2,16 @@ # \_atom_site -Data items in this category record details about the atom sites in a crystal -structure, such as the positional coordinates and atomic displacement -parameters. Please see the +Data items in this category record details about the atom sites in a +crystal structure, such as the positional coordinates and atomic +displacement parameters. Please see the [IUCr page](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/CATOM_SITE.html) for further details. ## [\_atom_site.label](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Iatom_site.label.html) -This is a unique identifier for a particular site in the asymmetric unit of the -crystal unit cell. +This is a unique identifier for a particular site in the asymmetric unit +of the crystal unit cell. ## [\_atom_site.type_symbol](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Iatom_site.type_symbol.html) @@ -19,7 +19,8 @@ A code to identify the atom specie(s) occupying this site. ## \_atom_site.fract -Atom-site coordinates as fractions of the [\_cell_length](cell.md) values. +Atom-site coordinates as fractions of the [\_cell_length](cell.md) +values. - [\_atom_site.fract_x](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Iatom_site.fract_x.html) - [\_atom_site.fract_y](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Iatom_site.fract_y.html) @@ -31,8 +32,8 @@ The fraction of the atom type present at this site. ## [\_atom_site.ADP_type](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Iatom_site.adp_type.html) -Code for type of atomic displacement parameters used for the site. Currently -only `Biso` (isotropic B) is supported. +Code for type of atomic displacement parameters used for the site. +Currently only `Biso` (isotropic B) is supported. ## [\_atom_site.B_iso_or_equiv](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Iatom_site.B_iso_or_equiv.html) @@ -43,17 +44,17 @@ displacement parameter, in angstroms squared. `optional parameter` -The number of different sites that are generated by the application of the -space-group symmetry to the coordinates given for this site. It is equal to the -multiplicity given for this Wyckoff site in International Tables for -Crystallography Vol. A (2002). +The number of different sites that are generated by the application of +the space-group symmetry to the coordinates given for this site. It is +equal to the multiplicity given for this Wyckoff site in International +Tables for Crystallography Vol. A (2002). ## [\_atom_site.Wyckoff_symbol](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Iatom_site.Wyckoff_symbol.html) `optional parameter` -The Wyckoff symbol (letter) as listed in the space-group tables of International -Tables for Crystallography Vol. A. +The Wyckoff symbol (letter) as listed in the space-group tables of +International Tables for Crystallography Vol. A. [0]: # diff --git a/docs/user-guide/parameters/background.md b/docs/docs/user-guide/parameters/background.md similarity index 75% rename from docs/user-guide/parameters/background.md rename to docs/docs/user-guide/parameters/background.md index 89985470..e307c981 100644 --- a/docs/user-guide/parameters/background.md +++ b/docs/docs/user-guide/parameters/background.md @@ -2,26 +2,27 @@ # \_pd_background -This category defines various background functions that could be used when -calculating diffractograms. Please see the -[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) for -further details. +This category defines various background functions that could be used +when calculating diffractograms. Please see the +[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) +for further details. ## [\_pd_background.line_segment_X](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) -List of X-coordinates used to create many straight-line segments representing -the background in a calculated diffractogram. +List of X-coordinates used to create many straight-line segments +representing the background in a calculated diffractogram. Supported values: `2theta` and `time-of-flight` ## [\_pd_background.line_segment_intensity](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) -List of intensities used to create many straight-line segments representing the -background in a calculated diffractogram. +List of intensities used to create many straight-line segments +representing the background in a calculated diffractogram. ## [\_pd_background.X_coordinate](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) -The type of X-coordinate against which the pd_background values were calculated. +The type of X-coordinate against which the pd_background values were +calculated. [0]: # diff --git a/docs/user-guide/parameters/cell.md b/docs/docs/user-guide/parameters/cell.md similarity index 95% rename from docs/user-guide/parameters/cell.md rename to docs/docs/user-guide/parameters/cell.md index 5aeb12ae..806df516 100644 --- a/docs/user-guide/parameters/cell.md +++ b/docs/docs/user-guide/parameters/cell.md @@ -2,8 +2,8 @@ # \_cell -Data items in this category record details about the crystallographic cell -parameters and their measurement. Please see the +Data items in this category record details about the crystallographic +cell parameters and their measurement. Please see the [IUCr page](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/CCELL.html) for further details. diff --git a/docs/user-guide/parameters/expt_type.md b/docs/docs/user-guide/parameters/expt_type.md similarity index 95% rename from docs/user-guide/parameters/expt_type.md rename to docs/docs/user-guide/parameters/expt_type.md index 5aeb12ae..806df516 100644 --- a/docs/user-guide/parameters/expt_type.md +++ b/docs/docs/user-guide/parameters/expt_type.md @@ -2,8 +2,8 @@ # \_cell -Data items in this category record details about the crystallographic cell -parameters and their measurement. Please see the +Data items in this category record details about the crystallographic +cell parameters and their measurement. Please see the [IUCr page](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/CCELL.html) for further details. diff --git a/docs/user-guide/parameters/instrument.md b/docs/docs/user-guide/parameters/instrument.md similarity index 73% rename from docs/user-guide/parameters/instrument.md rename to docs/docs/user-guide/parameters/instrument.md index 3975161c..fbeca6c9 100644 --- a/docs/user-guide/parameters/instrument.md +++ b/docs/docs/user-guide/parameters/instrument.md @@ -2,21 +2,22 @@ # \_pd_instr -This section contains information relevant to the instrument used for the -diffraction measurement, similar to this +This section contains information relevant to the instrument used for +the diffraction measurement, similar to this [IUCr section](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd). ## [\_pd_instr.resolution](#) -In general, the profile of a Bragg reflection centred at the peak position can -be approximated by mathematical convolution of contributions from the -instrument, called the instrumental resolution function, and from the -microstructure of the sample. Because many contributions to powder diffraction -peaks have a nearly Gaussian or Lorentzian shape, the pseudo-Voigt function, is -widely used to describe peak profiles in powder diffraction. +In general, the profile of a Bragg reflection centred at the peak +position can be approximated by mathematical convolution of +contributions from the instrument, called the instrumental resolution +function, and from the microstructure of the sample. Because many +contributions to powder diffraction peaks have a nearly Gaussian or +Lorentzian shape, the pseudo-Voigt function, is widely used to describe +peak profiles in powder diffraction. -Half-width parameters (normally characterising the instrumental resolution -function) as implemented in [CrysPy](https://cryspy.fr): +Half-width parameters (normally characterising the instrumental +resolution function) as implemented in [CrysPy](https://cryspy.fr): - \_pd_instr.resolution_u - \_pd_instr.resolution_v @@ -34,7 +35,8 @@ Lorentzian isotropic particle size parameteras implemented in ## [\_pd_instr.reflex_asymmetry](#) -Peak profile asymmetry parameters as implemented in [CrysPy](https://cryspy.fr). +Peak profile asymmetry parameters as implemented in +[CrysPy](https://cryspy.fr). - \_pd_instr.reflex_asymmetry_p1 - \_pd_instr.reflex_asymmetry_p2 diff --git a/docs/user-guide/parameters/linked_phases.md b/docs/docs/user-guide/parameters/linked_phases.md similarity index 86% rename from docs/user-guide/parameters/linked_phases.md rename to docs/docs/user-guide/parameters/linked_phases.md index df3f6d29..1d00f1d2 100644 --- a/docs/user-guide/parameters/linked_phases.md +++ b/docs/docs/user-guide/parameters/linked_phases.md @@ -2,10 +2,10 @@ # \_pd_phase_block -A table of phases relevant to the current data block. Each phase is identified -by its data block identifier. Please see the -[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) for -further details. +A table of phases relevant to the current data block. Each phase is +identified by its data block identifier. Please see the +[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) +for further details. ## [\_pd_phase_block.id](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) diff --git a/docs/user-guide/parameters/pd_meas.md b/docs/docs/user-guide/parameters/pd_meas.md similarity index 97% rename from docs/user-guide/parameters/pd_meas.md rename to docs/docs/user-guide/parameters/pd_meas.md index b294a96a..fcb4db17 100644 --- a/docs/user-guide/parameters/pd_meas.md +++ b/docs/docs/user-guide/parameters/pd_meas.md @@ -7,8 +7,8 @@ This section contains the measured diffractogram, similar to this ## [\_pd_meas.2theta_scan](https://raw.githubusercontent.com/COMCIFS/Powder_Dictionary/master/cif_pow.dic) -2θ diffraction angle (in degrees) for intensity points measured in a scanning -method. +2θ diffraction angle (in degrees) for intensity points measured in a +scanning method. ## [\_pd_meas.time-of-flight](https://raw.githubusercontent.com/COMCIFS/Powder_Dictionary/master/cif_pow.dic) diff --git a/docs/user-guide/parameters/peak.md b/docs/docs/user-guide/parameters/peak.md similarity index 95% rename from docs/user-guide/parameters/peak.md rename to docs/docs/user-guide/parameters/peak.md index 5aeb12ae..806df516 100644 --- a/docs/user-guide/parameters/peak.md +++ b/docs/docs/user-guide/parameters/peak.md @@ -2,8 +2,8 @@ # \_cell -Data items in this category record details about the crystallographic cell -parameters and their measurement. Please see the +Data items in this category record details about the crystallographic +cell parameters and their measurement. Please see the [IUCr page](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/CCELL.html) for further details. diff --git a/docs/user-guide/parameters/space_group.md b/docs/docs/user-guide/parameters/space_group.md similarity index 87% rename from docs/user-guide/parameters/space_group.md rename to docs/docs/user-guide/parameters/space_group.md index 883be103..ae5bce6b 100644 --- a/docs/user-guide/parameters/space_group.md +++ b/docs/docs/user-guide/parameters/space_group.md @@ -2,16 +2,16 @@ # \_space_group -Contains all the data items that refer to the space group as a whole. Please see -the +Contains all the data items that refer to the space group as a whole. +Please see the [IUCr page](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/CSPACE_GROUP.html) for further details. ## [\_space_group.name_H-M_alt](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Ispace_group.name_H-M_alt.html) -The international Hermann-Mauguin space-group symbol as defined in International -Tables for Crystallography Volume A. It allows any Hermann-Mauguin symbol to be -given. +The international Hermann-Mauguin space-group symbol as defined in +International Tables for Crystallography Volume A. It allows any +Hermann-Mauguin symbol to be given. ## [\_space_group.IT_coordinate_system_code](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Ispace_group.IT_coordinate_system_code.html) diff --git a/docs/includes/abbreviations.md b/docs/includes/abbreviations.md new file mode 100644 index 00000000..682f16ff --- /dev/null +++ b/docs/includes/abbreviations.md @@ -0,0 +1,15 @@ + + +*[CIF]: Crystallographic Information File. +*[curl]: Command-line tool for transferring data with URLs. +*[GitHub]: A web-based platform for version control and collaboration. +*[Google Colab]: Cloud service that allows you to run Jupyter Notebooks in the cloud. +*[IUCr]: International Union of Crystallography. +*[Jupyter Notebook]: An open-source web application that allows you to create and share documents that contain live code, equations, visualizations, and narrative text. +*[JupyterLab]: Web-based interactive development environment for notebooks, code, and data. +*[pip]: Package installer for Python. +*[PyPI]: The Python Package Index is a repository of software for the Python programming language. +*[Conda]: Conda is a cross-platform, language-agnostic binary package manager. +*[Pixi]: A modern package manager for Windows, macOS, and Linux. + + diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 467bfec8..55ca5f22 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -1,45 +1,167 @@ -########################### -# Override default settings -########################### - # Project information -site_name: '' #EasyDiffraction Library -site_url: https://easyscience.github.io/diffraction-lib/ #https://docs.easydiffraction.org/lib/ +site_name: EasyDiffraction Library +site_url: https://easyscience.github.io/diffraction-lib # Repository -repo_url: https://github.com/easyscience/diffraction-lib/ +repo_url: https://github.com/easyscience/diffraction-lib edit_uri: edit/develop/docs/ # Copyright -copyright: © 2026 EasyDiffraction +copyright: © 2021-2026 EasyDiffraction + +# Sets the theme and theme-specific configuration +theme: + name: material + custom_dir: overrides + features: + #- content.action.edit # Temporary disable edit button (until decided on which branch to use and where to host the notebooks) + #- content.action.view + - content.code.annotate + - content.code.copy # Auto generated button to copy a code block's content + - content.tooltips + - navigation.footer + - navigation.indexes + #- navigation.instant # Instant loading, but it causes issues with rendering equations + #- navigation.sections + - navigation.top # Back-to-top button + - navigation.tracking # Anchor tracking + - search.highlight + - search.share + - search.suggest + - toc.follow + palette: + # Palette toggle for light mode + - media: '(prefers-color-scheme: light)' + scheme: default + primary: custom + toggle: + icon: fontawesome/solid/sun + name: Switch to dark mode + # Palette toggle for dark mode + - media: '(prefers-color-scheme: dark)' + scheme: slate + primary: custom + toggle: + icon: fontawesome/solid/moon + name: Switch to light mode + font: + text: Mulish + code: Roboto Mono + icon: + edit: material/file-edit-outline + favicon: assets/images/favicon.png + logo_dark_mode: assets/images/logo_dark.svg + logo_light_mode: assets/images/logo_light.svg -# Extra icons in the bottom right corner +# A set of key-value pairs, where the values can be any valid YAML +# construct, that will be passed to the template extra: - social: + generator: false # Disable `Made with Material for MkDocs` (bottom left) + social: # Extra icons in the bottom right corner + - icon: easyscience # File: overrides/.icons/easyscience.svg + link: https://easyscience.org + name: EasyScience Framework Webpage - icon: easydiffraction # File: overrides/.icons/easydiffraction.svg - link: https://easydiffraction.org + link: https://easyscience.github.io/diffraction name: EasyDiffraction Main Webpage - icon: app # File: overrides/.icons/app.svg - link: https://docs.easydiffraction.org/app/ + link: https://easyscience.github.io/diffraction-app name: EasyDiffraction Application Docs - icon: fontawesome/brands/github # Name as in Font Awesome link: https://github.com/easyscience/diffraction-lib name: EasyDiffraction Library Source Code on GitHub + # Set custom variables to be used in Markdown and HTML files + vars: + ci_branch: !ENV CI_BRANCH + github_repository: !ENV GITHUB_REPOSITORY + release_version: !ENV RELEASE_VERSION + docs_version: !ENV DOCS_VERSION + notebooks_dir: !ENV NOTEBOOKS_DIR + # Renders a version selector in the header + version: + provider: mike + +# Customization to be included by the theme +extra_css: + - assets/stylesheets/extra.css + +extra_javascript: + - assets/javascripts/extra.js + # MathJax for rendering mathematical expressions + - assets/javascripts/mathjax.js # Custom MathJax config to ensure compatibility with mkdocs-jupyter + - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js # Official MathJax CDN + +# A list of extensions beyond the ones that MkDocs uses by default (meta, toc, tables, and fenced_code) +markdown_extensions: + - abbr + - admonition + - attr_list + - def_list + - footnotes + - pymdownx.arithmatex: # rendering of equations and integrates with MathJax or KaTeX + generic: true + - pymdownx.blocks.caption + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + options: + custom_icons: + - docs/overrides/.icons + - pymdownx.highlight: # whether highlighting should be carried out during build time by Pygments + use_pygments: true + pygments_lang_class: true + - pymdownx.snippets: + auto_append: + - docs/includes/abbreviations.md + - pymdownx.superfences: # whether highlighting should be carried out during build time by Pygments + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tabbed: # enables content tabs + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + - toc: + toc_depth: 3 -# Jupyter notebooks +# A list of plugins (with optional configuration settings) to use when building the site plugins: + - autorefs + - inline-svg + - markdownextradata # Plugin that injects the mkdocs.yml extra variables into the Markdown template + - mike # Plugin that makes it easy to deploy multiple versions of the docs - mkdocs-jupyter: - execute_ignore: - - '*.ipynb' # Do not execute any notebooks -# - 'quick*.ipynb' -# - 'basic*.ipynb' -# - 'advanced*.ipynb' -# - 'cryst*.ipynb' -# - 'pdf*.ipynb' + include: ['*.ipynb'] # Default: ['*.py', '*.ipynb'] + execute: false # Do not execute notebooks during build. They are expected to be pre-executed. + allow_errors: false + include_source: true + include_requirejs: true # Required for Plotly + #custom_mathjax_url: 'https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js' # See 'extra_javascript' above + ignore_h1_titles: true # Use titles defined in the nav section below + remove_tag_config: + remove_input_tags: + - hide-in-docs + - mkdocstrings: + handlers: + python: + paths: ['src'] # Change 'src' to your actual sources directory + options: + docstring_style: numpy + group_by_category: false + heading_level: 1 + show_root_heading: true + show_root_full_path: false + show_submodules: true + show_source: true + - search -################## -# Add new settings -################## +# Determines additional directories to watch when running mkdocs serve +watch: + - includes + - overrides + - ../src # Exclude files and folders from the global navigation not_in_nav: | @@ -100,7 +222,6 @@ nav: - experiment: api-reference/datablocks/experiment.md - structure: api-reference/datablocks/structure.md - display: api-reference/display.md - - experiments: api-reference/experiment.md - io: api-reference/io.md - project: api-reference/project.md - summary: api-reference/summary.md diff --git a/docs/overrides/.icons/app.svg b/docs/overrides/.icons/app.svg new file mode 100644 index 00000000..b4fdd4f3 --- /dev/null +++ b/docs/overrides/.icons/app.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/overrides/.icons/easydiffraction.svg b/docs/overrides/.icons/easydiffraction.svg new file mode 100644 index 00000000..5813e570 --- /dev/null +++ b/docs/overrides/.icons/easydiffraction.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/overrides/.icons/easyscience.svg b/docs/overrides/.icons/easyscience.svg new file mode 100644 index 00000000..fb514912 --- /dev/null +++ b/docs/overrides/.icons/easyscience.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/overrides/.icons/google-colab.svg b/docs/overrides/.icons/google-colab.svg new file mode 100644 index 00000000..9cd9d1b0 --- /dev/null +++ b/docs/overrides/.icons/google-colab.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 00000000..2e146827 --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} + +{% block content %} + +{% if page.nb_url %} + {# Parse notebook path/URL #} + {% set parts = page.nb_url.split('/') %} + {% set tutorial_name = parts[-2] %} + {% set filename = parts[-1] %} + + {# Colab url #} + {% set base_colab_url = "https://colab.research.google.com/github/" %} + {% set colab_url = + base_colab_url ~ config.extra.vars.github_repository ~ + "/blob/gh-pages/" ~ config.extra.vars.docs_version ~ + "/tutorials/" ~ tutorial_name ~ "/" ~ filename + %} + + {# Download link: relative to the current page #} + {% set file_url = filename %} + + {# Open in Colab (absolute GitHub URL; works anywhere) #} + + {% include ".icons/google-colab.svg" %} + + + {# Download: use a RELATIVE link to the file next to this page #} + + {% include ".icons/material/download.svg" %} + +{% endif %} + +{{ super() }} +{% endblock content %} diff --git a/docs/overrides/partials/logo.html b/docs/overrides/partials/logo.html new file mode 100644 index 00000000..78fa69ca --- /dev/null +++ b/docs/overrides/partials/logo.html @@ -0,0 +1,15 @@ +{% if ( config.theme.logo_light_mode and config.theme.logo_dark_mode ) %} +logo +logo +{% elif config.theme.logo %} +logo +{% else %} {% set icon = config.theme.icon.logo or "material/library" %} {% +include ".icons/" ~ icon ~ ".svg" %} {% endif %} diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md deleted file mode 100644 index a7c2cc37..00000000 --- a/docs/tutorials/index.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -icon: material/school ---- - -# :material-school: Tutorials - -This section presents a collection of **Jupyter Notebook** tutorials that -demonstrate how to use EasyDiffraction for various tasks. These tutorials serve -as self-contained, step-by-step **guides** to help users grasp the workflow of -diffraction data analysis using EasyDiffraction. - -Instructions on how to run the tutorials are provided in the -[:material-cog-box: Installation & Setup](../installation-and-setup/index.md#how-to-run-tutorials) -section of the documentation. - -The tutorials are organized into the following categories. - -## Getting Started - -- [LBCO `quick` CIF](ed-1.ipynb) – A minimal example intended as a quick - reference for users already familiar with the EasyDiffraction API or who want - to see how Rietveld refinement of the La0.5Ba0.5CoO3 crystal structure can be - performed when both the structure and experiment are loaded from CIF files. - Data collected from constant wavelength neutron powder diffraction at HRPT at - PSI. -- [LBCO `quick` `code`](ed-2.ipynb) – A minimal example intended as a quick - reference for users already familiar with the EasyDiffraction API or who want - to see an example refinement when both the structure and experiment are - defined directly in code. This tutorial covers a Rietveld refinement of the - La0.5Ba0.5CoO3 crystal structure using constant wavelength neutron powder - diffraction data from HRPT at PSI. -- [LBCO `complete`](ed-3.ipynb) – Demonstrates the use of the EasyDiffraction - API in a simplified, user-friendly manner that closely follows the GUI - workflow for a Rietveld refinement of the La0.5Ba0.5CoO3 crystal structure - using constant wavelength neutron powder diffraction data from HRPT at PSI. - This tutorial provides a full explanation of the workflow with detailed - comments and descriptions of every step, making it suitable for users who are - new to EasyDiffraction or those who prefer a more guided approach. - -## Powder Diffraction - -- [Co2SiO4 `pd-neut-cwl`](ed-5.ipynb) – Demonstrates a Rietveld refinement of - the Co2SiO4 crystal structure using constant wavelength neutron powder - diffraction data from D20 at ILL. -- [HS `pd-neut-cwl`](ed-6.ipynb) – Demonstrates a Rietveld refinement of the HS - crystal structure using constant wavelength neutron powder diffraction data - from HRPT at PSI. -- [Si `pd-neut-tof`](ed-7.ipynb) – Demonstrates a Rietveld refinement of the Si - crystal structure using time-of-flight neutron powder diffraction data from - SEPD at Argonne. -- [NCAF `pd-neut-tof`](ed-8.ipynb) – Demonstrates a Rietveld refinement of the - Na2Ca3Al2F14 crystal structure using two time-of-flight neutron powder - diffraction datasets (from two detector banks) of the WISH instrument at ISIS. - -## Single Crystal Diffraction - -- [Tb2TiO7 `sg-neut-cwl`](ed-14.ipynb) – Demonstrates structure refinement of - Tb2TiO7 using constant wavelength neutron single crystal diffraction data from - HEiDi at FRM II. -- [Taurine `sg-neut-tof`](ed-15.ipynb) – Demonstrates structure refinement of - Taurine using time-of-flight neutron single crystal diffraction data from - SENJU at J-PARC. - -## Pair Distribution Function (PDF) - -- [Ni `pd-neut-cwl`](ed-10.ipynb) – Demonstrates a PDF analysis of Ni using data - collected from a constant wavelength neutron powder diffraction experiment. -- [Si `pd-neut-tof`](ed-11.ipynb) – Demonstrates a PDF analysis of Si using data - collected from a time-of-flight neutron powder diffraction experiment at NOMAD - at SNS. -- [NaCl `pd-xray`](ed-12.ipynb) – Demonstrates a PDF analysis of NaCl using data - collected from an X-ray powder diffraction experiment. - -## Multi-Structure & Multi-Experiment Refinement - -- [PbSO4 NPD+XRD](ed-4.ipynb) – Joint fit of PbSO4 using X-ray and neutron - constant wavelength powder diffraction data. -- [LBCO+Si McStas](ed-9.ipynb) – Multi-phase Rietveld refinement of - La0.5Ba0.5CoO3 with Si impurity using time-of-flight neutron data simulated - with McStas. -- [Si Bragg+PDF](ed-16.ipynb) – Joint refinement of Si combining Bragg - diffraction (SEPD) and pair distribution function (NOMAD) analysis. A single - shared structure is refined simultaneously against both datasets. - -## Workshops & Schools - -- [DMSC Summer School](ed-13.ipynb) – A workshop tutorial that demonstrates a - Rietveld refinement of the La0.5Ba0.5CoO3 crystal structure using - time-of-flight neutron powder diffraction data simulated with McStas. This - tutorial is designed for the ESS DMSC Summer School. diff --git a/docs/user-guide/analysis-workflow/index.md b/docs/user-guide/analysis-workflow/index.md deleted file mode 100644 index 51c1f623..00000000 --- a/docs/user-guide/analysis-workflow/index.md +++ /dev/null @@ -1,34 +0,0 @@ -# Analysis Workflow - -To streamline the **data analysis process**, EasyDiffraction follows a -structured workflow divided into **five key steps**: - -```mermaid -flowchart LR - a(Project) - b(Model) - c(Experiment) - d(Analysis) - e(Summary) - a --> b - b --> c - c --> d - d --> e -``` - -- [:material-archive: Project](project.md) – Establish a **project** as a - container for structure and experiment parameters, measured and calculated - data, analysis settings and results. -- [:material-puzzle: Structure](model.md) – Load an existing **crystallographic - model** in CIF format or define a new one from scratch. -- [:material-microscope: Experiment](experiment.md) – Import **experimental - diffraction data** and configure **instrumental** and other relevant - parameters. -- [:material-calculator: Analysis](analysis.md) – **Calculate the diffraction - pattern** and **optimize the structural model** by refining its parameters to - match experimental measurements. -- [:material-clipboard-text: Summary](summary.md) – Generate a **report** - summarizing the results of the analysis, including refined parameters. - -Each step is described in detail in its respective section, guiding users -through the **entire diffraction data analysis workflow** in EasyDiffraction. diff --git a/pixi.lock b/pixi.lock index 1685316c..1b3db850 100644 --- a/pixi.lock +++ b/pixi.lock @@ -14,14 +14,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gsl-2.8-hbf7d49c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda @@ -32,15 +32,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.7.0-he4ff34a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.8.2-he4ff34a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.12-hc97d973_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -53,16 +53,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -70,77 +70,86 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d1/3b/6103194ea934f1c3a4ea080905c8849f71e83de455c16cb625d25f49b779/chardet-7.4.0.post1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/af/3b86dbd18d8dab5646f5b7c7db5bd9c43108e093864032aabd35d41b127d/diffpy_pdffit2-1.5.2.tar.gz - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - 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/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-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/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/bc/60d93477b653eeb1ddf5f9ec34be689b79234d82dbdded269ac0252715b8/fonttools-4.62.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/8c/db8e79c4c744ebae1dcf25f7dbcc5d7df912cdbcdf7221e761479e8bd04b/gemmi-0.7.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -164,10 +173,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -178,7 +187,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/28/83/36557b04cfdc317ed8a525c4993b23e43a8fbcddaddd78619112ca07138c/msgspec-0.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -202,12 +211,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -226,15 +235,16 @@ 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/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-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 - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -242,18 +252,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/94/98/aa2d4b9d28969cc7f62409f9f9fc5b5a853af651255eba03e9bee8546dd9/scipp-25.12.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -262,7 +273,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/53/01/1c0485ae02e645bc517bf5d5a6ca674f62c97e247890b954cbfe85c64dae/spglib-2.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -270,11 +283,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -284,10 +297,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/34/b104c413079874493eed7bf11838b47b697cf1f0ed7e9de374ea37b4e4e0/uv-0.10.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -309,14 +320,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.6-hb5e19a0_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/gsl-2.8-hc707ee6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.2-h14c5de8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.3-h25d91c4_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20260107.1-cxx17_h7ed6875_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-6_he492b99_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-6_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.2-h19cb2f5_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.4-h991f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda @@ -325,14 +336,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.2-h11316ed_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-hf3981d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.68.1-h70048d4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.32-openmp_h9e49c7b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.52.0-h77d7759_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.1-h0d3cbff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.7.0-hf6efa0e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.8.2-hf3170e9_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.12-h894a449_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -345,17 +356,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -363,77 +374,86 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/e9/32/83a15c6077e7f240834ffd9ed78ef12f20f6e1924d7d7986d33f3d2af905/chardet-7.4.0.post1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/eb/bb3ff420acdaf9bcaf94c510f42df11974bc3fc475ef50d619366f33fda3/diffpy_pdffit2-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - 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/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-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/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c1/dc/c409c8ceec0d3119e9ab0b7b1a2e3c76d1f4d66e4a9db5c59e6b7652e7df/fonttools-4.62.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/80/fd758344a72ca7b5e1c5bbdc1d263f3b215d3897941b5f450380445ca0a9/gemmi-0.7.5-cp313-cp313-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -457,10 +477,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -471,7 +491,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8a/d1/b902d38b6e5ba3bdddbec469bba388d647f960aeed7b5b3623a8debe8a76/msgspec-0.20.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -495,12 +515,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -519,15 +539,16 @@ 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/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-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 - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -535,18 +556,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-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/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/22/75e119e0a200914f88f121cd956e1eb7f72c8ace4b63171f52ba0334d142/scipp-25.12.0-cp313-cp313-macosx_11_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e2/69/1dcb8e967f62759578938db5b29792b82ea8939a2d712e79491fa3e1cf0a/scipp-26.3.1-cp313-cp313-macosx_14_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl @@ -555,7 +577,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1b/a5/174d33068d4383df4be9ee1ea9251f17820a622f3be744ca2ab7334818ee/spglib-2.6.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -563,11 +587,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -577,10 +601,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/34/2e5cd576d312eb1131b615f49ee95ff6efb740965324843617adae729cf2/uv-0.10.9-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -602,14 +624,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gsl-2.8-h8d0574d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.2-h55c6f16_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda @@ -618,14 +640,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.1-hc7d1edf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.7.0-hbfc8e16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.8.2-h7039424_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.12-h20e6be0_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -638,17 +660,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -656,76 +678,85 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/83/d3/80554c1cc15631446c9b90aec6fe63b7310aa0b82d3004f7ba38bd8a8270/chardet-7.4.0.post1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/28/d050c2716c74c6fce9ace360e727e6f86b68212fb6b0ea57c005ebe574ea/diffpy_pdffit2-1.5.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - 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/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-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/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/c7/985c1670aa6d82ef270f04cde11394c168f2002700353bd2bde405e59b8f/fonttools-4.62.0-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/9c/1236dd7d22ed48527286b613c84e3376ea731b65e6734b6e6a0b4d03744c/gemmi-0.7.5-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -749,10 +780,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -763,7 +794,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/57/b6/eff0305961a1d9447ec2b02f8c73c8946f22564d302a504185b730c9a761/msgspec-0.20.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -787,12 +818,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -811,15 +842,16 @@ 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/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-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 - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -827,18 +859,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-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/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/18/5c/bc0ca94bff65fe0d206a369b54625f8ec7852dfd1d835174692026f34df9/scipp-25.12.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl @@ -847,7 +880,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/47/634fe8323c6c2bfa86e10eb41ebfe410db5e6231aa1727a31ce4f002480f/spglib-2.6.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -855,11 +890,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -869,10 +904,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/35/684f641de4de2b20db7d2163c735b2bb211e3b3c84c241706d6448e5e868/uv-0.10.9-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -892,8 +925,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/gsl-2.8-h5b8d9c4_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda @@ -904,10 +937,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.7.0-h80d1838_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.1-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_11.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.12-h09917c8_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -923,16 +956,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -940,77 +973,86 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/91/d7/47988d40231b41376f5a66346ef3b322c81091dfd4c0f84df5a1e3bb06b5/chardet-7.4.0.post1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1f/0c/6826cb2151628c59cca66ca6089ff910ab3ccd62b0524c2b398dc145ee52/diffpy_pdffit2-1.5.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - 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/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-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/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/7a/e25245a30457595740041dba9d0ea8ec1b2517f2f1a6a741f15eba1a4edc/fonttools-4.62.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/ab/7d7463cda94f8b68b969ea97aaad679655a0e436efd6a643e528a8de114e/gemmi-0.7.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -1034,10 +1076,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -1048,7 +1090,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/25/5e8080fe0117f799b1b68008dc29a65862077296b92550632de015128579/msgspec-0.20.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -1071,12 +1113,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -1094,35 +1136,38 @@ 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/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-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 - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/eb/d1/b3cd2733a96a36c54c36889b2cfdd0331c1e5b57fa1757485a22d0ec3142/scipp-25.12.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl @@ -1131,7 +1176,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/56/a31e8d3c9e8d21100b83bbe1c1f3f7c94db317393a229e193461e5e6d2a4/spglib-2.6.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -1139,11 +1186,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -1153,10 +1200,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/e9/adf7a12136573937d12ac189569e2e90e7fad18b458192083df6986f3013/uv-0.10.9-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -1172,7 +1217,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl - pypi: ./ - py311-dev: + py-311-env: channels: - url: https://conda.anaconda.org/conda-forge/ indexes: @@ -1186,14 +1231,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gsl-2.8-hbf7d49c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda @@ -1203,17 +1248,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.7.0-he4ff34a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.8.2-he4ff34a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda @@ -1225,16 +1270,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl @@ -1242,78 +1287,87 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/f4/44d3b830a20e89ff82a3134912d9a1cf6084d64f3b95dcad40f74449a654/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e3/a2/dab58511fbeef06dd88866568ea1a11b2f15654223cafc2681e2da84b1f2/chardet-7.4.0.post1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/af/3b86dbd18d8dab5646f5b7c7db5bd9c43108e093864032aabd35d41b127d/diffpy_pdffit2-1.5.2.tar.gz - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - 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/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-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/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/d7/8e4845993ee233c2023d11babe9b3dae7d30333da1d792eeccebcb77baab/fonttools-4.62.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/48/eb/46e443fc70b4aabe6e775521ff476aefb051db9acabb16a5cb51f04e3e2b/gemmi-0.7.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -1337,10 +1391,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -1351,7 +1405,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/6b/96/5c095b940de3aa6b43a71ec76275ac3537b21bd45c7499b5a17a429110fa/msgspec-0.20.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -1376,12 +1430,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -1400,15 +1454,16 @@ 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 - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -1416,18 +1471,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4e/b6/ffe0bb67cec66cd450acff599bb07507bbf5ffda1a3a15dd2d8dbe7a6da7/scipp-25.12.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d4/06/19ff1efd58b85906149ce83dfddce23252cea5bec7e0fa5f834336cfe836/scipp-26.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -1436,7 +1492,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/55/41/591cd1e94254c20f00bb1f32c0b1a6de68c03d54e6daf78dd7b146d0b3fc/spglib-2.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/21/dd/3b7c53f1dbbf736fd27041aee68f8ac52226b610f914085b1652c2323442/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -1444,11 +1502,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -1458,10 +1516,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/34/b104c413079874493eed7bf11838b47b697cf1f0ed7e9de374ea37b4e4e0/uv-0.10.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -1484,14 +1540,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.6-hb5e19a0_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/gsl-2.8-hc707ee6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.2-h14c5de8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.3-h25d91c4_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20260107.1-cxx17_h7ed6875_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-6_he492b99_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-6_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.2-h19cb2f5_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.4-h991f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda @@ -1499,14 +1555,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.2-h11316ed_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.68.1-h70048d4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.32-openmp_h9e49c7b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.52.0-h77d7759_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.1-h0d3cbff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.7.0-hf6efa0e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.8.2-hf3170e9_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.15-ha9537fe_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda @@ -1518,17 +1574,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl @@ -1536,78 +1592,87 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3e/38/fe380893cbba72febb24d5dc0c2f9ac99f437153c36a409a8e254ed77bb6/chardet-7.4.0.post1-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/9e/0e27056c6165ab3e2536d5efbc358cf495e6cd57fbaf51e68b1113fa7771/diffpy_pdffit2-1.5.2-cp311-cp311-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - 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/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-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/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c0/7a/9aeec114bc9fc00d757a41f092f7107863d372e684a5b5724c043654477c/fonttools-4.62.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7f/79/b13830a65bf9fc85474a984604f094cc18817dc93a784f4c567a2dc05169/gemmi-0.7.5-cp311-cp311-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/95/a825894f3e45cbac7554c4e97314ce886b233a20033787eda755ca8fecc7/h5py-3.16.0-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -1631,10 +1696,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -1645,7 +1710,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/03/59/fdcb3af72f750a8de2bcf39d62ada70b5eb17b06d7f63860e0a679cb656b/msgspec-0.20.0-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -1670,12 +1735,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -1694,15 +1759,16 @@ 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 - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -1710,18 +1776,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - 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/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.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/81/21/4962b1daddf0422e56c5ed4c41bea1ccb6d2a9ab72b795196835a20969c7/scipp-26.3.1-cp311-cp311-macosx_14_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl @@ -1730,7 +1797,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/44/30888e2a5b2fa2e6df18606b442cb8b126b0bea5a2f1ec4a2a82538ffecf/spglib-2.6.0-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -1738,11 +1807,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -1752,10 +1821,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/34/2e5cd576d312eb1131b615f49ee95ff6efb740965324843617adae729cf2/uv-0.10.9-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -1778,14 +1845,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gsl-2.8-h8d0574d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.2-h55c6f16_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda @@ -1793,14 +1860,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.1-hc7d1edf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.7.0-hbfc8e16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.8.2-h7039424_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.15-h8561d8f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda @@ -1812,17 +1879,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl @@ -1830,77 +1897,86 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/5e/24/3c1522d777b66e2e3615ee33d1d4291c47b0ec258a9471b559339b01fac5/chardet-7.4.0.post1-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ce/c9/7b61255980383781774d9857aa9e97fe7e9b8b08f97c0974afeef3083dd9/diffpy_pdffit2-1.5.2-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - 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/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-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/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/33/63d79ca41020dd460b51f1e0f58ad1ff0a36b7bcbdf8f3971d52836581e9/fonttools-4.62.0-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/15/26cac702cdf6281ddeb185d5912ce14e555e277c6e4caeb1d36966e43822/gemmi-0.7.5-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -1924,10 +2000,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -1938,7 +2014,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/5a/15/3c225610da9f02505d37d69a77f4a2e7daae2a125f99d638df211ba84e59/msgspec-0.20.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -1963,12 +2039,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -1987,15 +2063,16 @@ 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 - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -2003,18 +2080,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - 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/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.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/60/54/5011adb56853caabfd90686c2e543d1e3c76a8ef2755809b7e12e3f3583b/scipp-26.3.1-cp311-cp311-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl @@ -2023,7 +2101,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/ca/270d463f6c34f539bb55acdab14099c092d3be28c8af64d61399aa07610c/spglib-2.6.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/d7/6d/b8b78b5b80f3c3ab3f7fa90faa195ec3401f6d884b60221260fd4d51864c/sqlalchemy-2.0.48-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -2031,11 +2111,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -2045,10 +2125,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/35/684f641de4de2b20db7d2163c735b2bb211e3b3c84c241706d6448e5e868/uv-0.10.9-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -2069,8 +2147,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/gsl-2.8-h5b8d9c4_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda @@ -2080,10 +2158,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.7.0-h80d1838_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.1-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_11.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-h3155e25_2.conda @@ -2098,16 +2176,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl @@ -2115,78 +2193,87 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fe/1f/a853b73d386521fd44b7f67ded6b17b7b2367067d9106a5c4b44f9a34274/charset_normalizer-3.4.5-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/91/d7/47988d40231b41376f5a66346ef3b322c81091dfd4c0f84df5a1e3bb06b5/chardet-7.4.0.post1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/33/fae9a52a6cb97efd21176303dfef44e487b56e3473c1329e019d5682d158/diffpy_pdffit2-1.5.2-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - 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/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-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/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/77/ce/f5a4c42c117f8113ce04048053c128d17426751a508f26398110c993a074/fonttools-4.62.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b9/5e/62402bf021183bc6122cb01b8f1be17cac67545713fb30f888f59357a782/gemmi-0.7.5-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/3a/efb2cf697fbccdf75b24e2c18025e7dfa54c4f31fab75c51d0fe79942cef/greenlet-3.3.2-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -2210,10 +2297,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -2224,7 +2311,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/89/5e/406b7d578926b68790e390d83a1165a9bfc2d95612a1a9c1c4d5c72ea815/msgspec-0.20.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -2248,12 +2335,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -2271,35 +2358,38 @@ 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 - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/79/c3/3e75075c7f71735f22b66fab0481f2c98e3a4d58cba55cb50ba29114bcf6/pywinpty-3.0.3-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/75/6a3786de6645ac2ccd94fbf83c59cc6b929bfa3a89cb62c8cb3be4de0606/scipp-25.12.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e6/0d/8882a4c7a5ebe59a46b709e82411d9c730d67250d41a2e11bc4bcd4d431d/scipp-26.3.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl @@ -2308,7 +2398,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/a8/d89e1bde525baba10eb8d0be79a5bbaf56c59a47b32bb954866d96a228e3/spglib-2.6.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/58/d5/dd767277f6feef12d05651538f280277e661698f617fa4d086cce6055416/sqlalchemy-2.0.48-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -2316,11 +2408,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -2330,10 +2422,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/e9/adf7a12136573937d12ac189569e2e90e7fad18b458192083df6986f3013/uv-0.10.9-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -2350,7 +2440,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/93/22/b85eca6fa2ad9491af48c973e4c8cf6b103a73dbb271fe3346949449fca0/yarl-1.23.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl - pypi: ./ - py313-dev: + py-313-env: channels: - url: https://conda.anaconda.org/conda-forge/ indexes: @@ -2364,14 +2454,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gsl-2.8-hbf7d49c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda @@ -2382,15 +2472,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.7.0-he4ff34a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.8.2-he4ff34a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.12-hc97d973_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -2403,16 +2493,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -2420,77 +2510,86 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d1/3b/6103194ea934f1c3a4ea080905c8849f71e83de455c16cb625d25f49b779/chardet-7.4.0.post1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/af/3b86dbd18d8dab5646f5b7c7db5bd9c43108e093864032aabd35d41b127d/diffpy_pdffit2-1.5.2.tar.gz - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - 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/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-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/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/bc/60d93477b653eeb1ddf5f9ec34be689b79234d82dbdded269ac0252715b8/fonttools-4.62.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/8c/db8e79c4c744ebae1dcf25f7dbcc5d7df912cdbcdf7221e761479e8bd04b/gemmi-0.7.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -2514,10 +2613,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -2528,7 +2627,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/28/83/36557b04cfdc317ed8a525c4993b23e43a8fbcddaddd78619112ca07138c/msgspec-0.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -2552,12 +2651,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -2576,15 +2675,16 @@ 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/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-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 - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -2592,18 +2692,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/94/98/aa2d4b9d28969cc7f62409f9f9fc5b5a853af651255eba03e9bee8546dd9/scipp-25.12.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -2612,7 +2713,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/53/01/1c0485ae02e645bc517bf5d5a6ca674f62c97e247890b954cbfe85c64dae/spglib-2.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -2620,11 +2723,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -2634,10 +2737,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/34/b104c413079874493eed7bf11838b47b697cf1f0ed7e9de374ea37b4e4e0/uv-0.10.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -2659,14 +2760,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.6-hb5e19a0_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/gsl-2.8-hc707ee6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.2-h14c5de8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.3-h25d91c4_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20260107.1-cxx17_h7ed6875_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-6_he492b99_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-6_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.2-h19cb2f5_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.4-h991f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda @@ -2675,14 +2776,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.2-h11316ed_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-hf3981d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.68.1-h70048d4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.32-openmp_h9e49c7b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.52.0-h77d7759_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.1-h0d3cbff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.7.0-hf6efa0e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.8.2-hf3170e9_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.12-h894a449_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -2695,17 +2796,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -2713,77 +2814,86 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/e9/32/83a15c6077e7f240834ffd9ed78ef12f20f6e1924d7d7986d33f3d2af905/chardet-7.4.0.post1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/eb/bb3ff420acdaf9bcaf94c510f42df11974bc3fc475ef50d619366f33fda3/diffpy_pdffit2-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - 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/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-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/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c1/dc/c409c8ceec0d3119e9ab0b7b1a2e3c76d1f4d66e4a9db5c59e6b7652e7df/fonttools-4.62.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/80/fd758344a72ca7b5e1c5bbdc1d263f3b215d3897941b5f450380445ca0a9/gemmi-0.7.5-cp313-cp313-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -2807,10 +2917,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -2821,7 +2931,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8a/d1/b902d38b6e5ba3bdddbec469bba388d647f960aeed7b5b3623a8debe8a76/msgspec-0.20.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -2845,12 +2955,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -2869,15 +2979,16 @@ 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/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-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 - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -2885,18 +2996,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-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/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/22/75e119e0a200914f88f121cd956e1eb7f72c8ace4b63171f52ba0334d142/scipp-25.12.0-cp313-cp313-macosx_11_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e2/69/1dcb8e967f62759578938db5b29792b82ea8939a2d712e79491fa3e1cf0a/scipp-26.3.1-cp313-cp313-macosx_14_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl @@ -2905,7 +3017,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1b/a5/174d33068d4383df4be9ee1ea9251f17820a622f3be744ca2ab7334818ee/spglib-2.6.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -2913,11 +3027,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -2927,10 +3041,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/34/2e5cd576d312eb1131b615f49ee95ff6efb740965324843617adae729cf2/uv-0.10.9-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -2952,14 +3064,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gsl-2.8-h8d0574d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.2-h55c6f16_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda @@ -2968,14 +3080,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.1-hc7d1edf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.7.0-hbfc8e16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.8.2-h7039424_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.12-h20e6be0_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -2988,17 +3100,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -3006,76 +3118,85 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/83/d3/80554c1cc15631446c9b90aec6fe63b7310aa0b82d3004f7ba38bd8a8270/chardet-7.4.0.post1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/28/d050c2716c74c6fce9ace360e727e6f86b68212fb6b0ea57c005ebe574ea/diffpy_pdffit2-1.5.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - 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/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-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/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/c7/985c1670aa6d82ef270f04cde11394c168f2002700353bd2bde405e59b8f/fonttools-4.62.0-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/9c/1236dd7d22ed48527286b613c84e3376ea731b65e6734b6e6a0b4d03744c/gemmi-0.7.5-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -3099,10 +3220,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -3113,7 +3234,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/57/b6/eff0305961a1d9447ec2b02f8c73c8946f22564d302a504185b730c9a761/msgspec-0.20.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -3137,12 +3258,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -3161,15 +3282,16 @@ 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/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-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 - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -3177,18 +3299,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-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/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/18/5c/bc0ca94bff65fe0d206a369b54625f8ec7852dfd1d835174692026f34df9/scipp-25.12.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl @@ -3197,7 +3320,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/47/634fe8323c6c2bfa86e10eb41ebfe410db5e6231aa1727a31ce4f002480f/spglib-2.6.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -3205,11 +3330,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -3219,10 +3344,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/35/684f641de4de2b20db7d2163c735b2bb211e3b3c84c241706d6448e5e868/uv-0.10.9-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -3242,8 +3365,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/gsl-2.8-h5b8d9c4_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda @@ -3254,10 +3377,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.7.0-h80d1838_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.1-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_11.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.12-h09917c8_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -3273,16 +3396,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -3290,77 +3413,86 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/91/d7/47988d40231b41376f5a66346ef3b322c81091dfd4c0f84df5a1e3bb06b5/chardet-7.4.0.post1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1f/0c/6826cb2151628c59cca66ca6089ff910ab3ccd62b0524c2b398dc145ee52/diffpy_pdffit2-1.5.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - 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/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-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/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/7a/e25245a30457595740041dba9d0ea8ec1b2517f2f1a6a741f15eba1a4edc/fonttools-4.62.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/ab/7d7463cda94f8b68b969ea97aaad679655a0e436efd6a643e528a8de114e/gemmi-0.7.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -3384,10 +3516,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -3398,7 +3530,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/25/5e8080fe0117f799b1b68008dc29a65862077296b92550632de015128579/msgspec-0.20.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -3421,12 +3553,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -3444,35 +3576,38 @@ 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/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-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 - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/eb/d1/b3cd2733a96a36c54c36889b2cfdd0331c1e5b57fa1757485a22d0ec3142/scipp-25.12.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl @@ -3481,7 +3616,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/56/a31e8d3c9e8d21100b83bbe1c1f3f7c94db317393a229e193461e5e6d2a4/spglib-2.6.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -3489,11 +3626,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -3503,10 +3640,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/e9/adf7a12136573937d12ac189569e2e90e7fad18b458192083df6986f3013/uv-0.10.9-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -3728,17 +3863,16 @@ packages: requires_dist: - typing-extensions>=4.0.0 ; python_full_version < '3.9' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl name: anyio - version: 4.12.1 - sha256: d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c + version: 4.13.0 + sha256: 08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708 requires_dist: - exceptiongroup>=1.0.2 ; python_full_version < '3.11' - idna>=2.8 - typing-extensions>=4.5 ; python_full_version < '3.13' - - trio>=0.32.0 ; python_full_version >= '3.10' and extra == 'trio' - - trio>=0.31.0 ; python_full_version < '3.10' and extra == 'trio' - requires_python: '>=3.9' + - trio>=0.32.0 ; extra == 'trio' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl name: appnope version: 0.1.4 @@ -3811,16 +3945,17 @@ packages: requires_dist: - setuptools - flake8 ; extra == 'qa' -- pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl name: ase - version: 3.27.0 - sha256: 058c48ea504fe7fbbe7c932f778415243ef2df45b1ab869866f24efcc17f0538 + version: 3.28.0 + sha256: 0e24056302d7307b7247f90de281de15e3031c14cf400bedb1116c3b0d0e50b8 requires_dist: - numpy>=1.21.6 - scipy>=1.8.1 - matplotlib>=3.5.2 - sphinx ; extra == 'docs' - sphinx-book-theme ; extra == 'docs' + - sphinxcontrib-video ; extra == 'docs' - sphinx-gallery ; extra == 'docs' - pillow ; extra == 'docs' - pytest>=7.4.0 ; extra == 'test' @@ -3855,17 +3990,17 @@ packages: - pytest-cov ; extra == 'test' - pytest-xdist ; extra == 'test' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl name: async-lru - version: 2.2.0 - sha256: e2c1cf731eba202b59c5feedaef14ffd9d02ad0037fcda64938699f2c380eafe + version: 2.3.0 + sha256: eea27b01841909316f2cc739807acea1c623df2be8c5cfad7583286397bb8315 requires_dist: - typing-extensions>=4.0.0 ; python_full_version < '3.11' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl name: attrs - version: 25.4.0 - sha256: adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373 + version: 26.1.0 + sha256: c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl name: autopep8 @@ -3935,10 +4070,10 @@ packages: version: 1.9.0 sha256: ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl name: build - version: 1.4.0 - sha256: 6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596 + version: 1.4.2 + sha256: 7a4d8651ea877cb2a89458b1b198f2e69f536c95e89129dbf5d448045d60db88 requires_dist: - packaging>=24.0 - pyproject-hooks @@ -4136,35 +4271,70 @@ packages: version: 3.5.0 sha256: a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/03/f4/44d3b830a20e89ff82a3134912d9a1cf6084d64f3b95dcad40f74449a654/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/3e/38/fe380893cbba72febb24d5dc0c2f9ac99f437153c36a409a8e254ed77bb6/chardet-7.4.0.post1-cp311-cp311-macosx_10_9_x86_64.whl + name: chardet + version: 7.4.0.post1 + sha256: 2769be12361a6c7873392e435c708eca88c9f0fb6a647af75fa1386db64032d6 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/5e/24/3c1522d777b66e2e3615ee33d1d4291c47b0ec258a9471b559339b01fac5/chardet-7.4.0.post1-cp311-cp311-macosx_11_0_arm64.whl + name: chardet + version: 7.4.0.post1 + sha256: 8e1eaa942ae81d43d535092ff3ba660c967344178cc3876b54834a56c1207f3a + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/83/d3/80554c1cc15631446c9b90aec6fe63b7310aa0b82d3004f7ba38bd8a8270/chardet-7.4.0.post1-cp313-cp313-macosx_11_0_arm64.whl + name: chardet + version: 7.4.0.post1 + sha256: e6285d35f79d0cdc8838d3cb01876f979c8419a74662e8de39444e40639e0b2b + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/91/d7/47988d40231b41376f5a66346ef3b322c81091dfd4c0f84df5a1e3bb06b5/chardet-7.4.0.post1-py3-none-any.whl + name: chardet + version: 7.4.0.post1 + sha256: 57a62ef50f69bc2fb3a3ea1ffffec6d10f3d2112d3b05d6e3cb15c2c9b55f6cc + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d1/3b/6103194ea934f1c3a4ea080905c8849f71e83de455c16cb625d25f49b779/chardet-7.4.0.post1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: chardet + version: 7.4.0.post1 + sha256: 329aa8766c4917d3acc1b1d0462f0b2e820e24e9f341d0f858aee85396ae3002 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e3/a2/dab58511fbeef06dd88866568ea1a11b2f15654223cafc2681e2da84b1f2/chardet-7.4.0.post1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: chardet + version: 7.4.0.post1 + sha256: ad98a6c2e61624b1120919353d222121b8f5848b9d33c885d949fe0235575682 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e9/32/83a15c6077e7f240834ffd9ed78ef12f20f6e1924d7d7986d33f3d2af905/chardet-7.4.0.post1-cp313-cp313-macosx_10_13_x86_64.whl + name: chardet + version: 7.4.0.post1 + sha256: efdb3785c8700b3d0b354553827a166480a439f9754f7366f795bbe8b42d6daf + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl name: charset-normalizer - version: 3.4.5 - sha256: 5bcb3227c3d9aaf73eaaab1db7ccd80a8995c509ee9941e2aae060ca6e4e5d81 + version: 3.4.6 + sha256: 11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl +- pypi: https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: charset-normalizer - version: 3.4.5 - sha256: 610f72c0ee565dfb8ae1241b666119582fdbfe7c0975c175be719f940e110694 + version: 3.4.6 + sha256: 530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: charset-normalizer - version: 3.4.5 - sha256: 0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819 + version: 3.4.6 + sha256: 9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl name: charset-normalizer - version: 3.4.5 - sha256: e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7 + version: 3.4.6 + sha256: 82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl +- pypi: https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl name: charset-normalizer - version: 3.4.5 - sha256: ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23 + version: 3.4.6 + sha256: 572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/fe/1f/a853b73d386521fd44b7f67ded6b17b7b2367067d9106a5c4b44f9a34274/charset_normalizer-3.4.5-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl name: charset-normalizer - version: 3.4.5 - sha256: f8102ae93c0bc863b1d41ea0f4499c20a83229f52ed870850892df555187154a + version: 3.4.6 + sha256: a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4 requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl name: click @@ -4390,59 +4560,79 @@ packages: - pytest-xdist ; extra == 'test-no-images' - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + name: copier + version: 9.14.0 + sha256: e12a18cfef22e67254e5229f0b4bdab85e1e3e82926e448226be0b70d0f4de53 + requires_dist: + - colorama>=0.4.6 + - dunamai>=1.7.0 + - funcy>=1.17 + - jinja2-ansible-filters>=1.3.1 + - jinja2>=3.1.5 + - packaging>=23.0 + - pathspec>=0.9.0 + - platformdirs>=4.3.6 + - plumbum>=1.6.9 + - pydantic>=2.4.2 + - pygments>=2.7.1 + - pyyaml>=5.3.1 + - questionary>=1.8.1 + - typing-extensions>=4.0.0,<5.0.0 ; python_full_version < '3.11' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl name: coverage - version: 7.13.4 - sha256: 2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f + version: 7.13.5 + sha256: 941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl name: coverage - version: 7.13.4 - sha256: 8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7 + version: 7.13.5 + sha256: 145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl name: coverage - version: 7.13.4 - sha256: 3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac + version: 7.13.5 + sha256: 66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl name: coverage - version: 7.13.4 - sha256: d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053 + version: 7.13.5 + sha256: 631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl name: coverage - version: 7.13.4 - sha256: b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9 + version: 7.13.5 + sha256: 5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: coverage - version: 7.13.4 - sha256: e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd + version: 7.13.5 + sha256: ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: coverage - version: 7.13.4 - sha256: b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b + version: 7.13.5 + sha256: 78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl name: coverage - version: 7.13.4 - sha256: 19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11 + version: 7.13.5 + sha256: 258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' @@ -4482,10 +4672,10 @@ packages: requires_dist: - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'macos-listener' requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl name: dask - version: 2026.1.2 - sha256: 46a0cf3b8d87f78a3d2e6b145aea4418a6d6d606fe6a16c79bd8ca2bb862bc91 + version: 2026.3.0 + sha256: be614b9242b0b38288060fb2d7696125946469c98a1c30e174883fd199e0428d requires_dist: - click>=8.1 - cloudpickle>=3.0.0 @@ -4499,7 +4689,7 @@ packages: - dask[array] ; extra == 'dataframe' - pandas>=2.0 ; extra == 'dataframe' - pyarrow>=16.0 ; extra == 'dataframe' - - distributed>=2026.1.2,<2026.1.3 ; extra == 'distributed' + - distributed>=2026.3.0,<2026.3.1 ; extra == 'distributed' - bokeh>=3.1.0 ; extra == 'diagnostics' - jinja2>=2.10.3 ; extra == 'diagnostics' - dask[array,dataframe,diagnostics,distributed] ; extra == 'complete' @@ -4539,10 +4729,10 @@ packages: version: 0.7.1 sha256: a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' -- pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl name: dfo-ls - version: 1.6.2 - sha256: 961d15e7194f3868e9e48da45010cb6d24d85491007fbee4e38a9f3ab8050cef + version: 1.6.5 + sha256: d147d42e471e240f9abf8bc38351a88f555ea6a8fcfd83119bbbf93c36f75ab2 requires_dist: - setuptools - numpy @@ -4610,15 +4800,15 @@ packages: - numpy - pycifrw requires_python: '>=3.11,<3.14' -- pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl name: diffpy-utils - version: 3.7.1 - sha256: 8bf33eb3e228bf6a18242e4cc01cf4b7bff307fbef90f745bf7d680c7ed4d3b7 + version: 3.7.2 + sha256: 6100600736791a8e4638e3dd476704f4dabe3cab75bcb5c60c83c16a2032519a requires_dist: - numpy - xraydb - scipy - requires_python: '>=3.11,<3.15' + requires_python: '>=3.10,<3.15' - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl name: dill version: 0.4.1 @@ -4658,33 +4848,49 @@ packages: - trio>=0.30 ; extra == 'trio' - wmi>=1.5.1 ; sys_platform == 'win32' and extra == 'wmi' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - name: docformatter - version: 1.7.7 - sha256: 7af49f8a46346a77858f6651f431b882c503c2f4442c8b4524b920c863277834 - requires_dist: - - charset-normalizer>=3.0.0,<4.0.0 - - 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 + sha256: f584edf0fda0d308cce0961f807bc90a8fe3d9ff4d62f94e72eca7b43f0ed5f6 + requires_dist: + - importlib-metadata>=1.6.0 ; python_full_version < '3.8' + - packaging>=20.9 + requires_python: '>=3.5' - pypi: ./ name: easydiffraction - version: 0.10.2+devdirty36 - sha256: c26412f987f3f60607ea00f77d4f12aadc3b38ea31833f634b218e09965dbdbc + version: 0.10.2+dev46 + sha256: 9ada6af993ed7303fd58593648683db9afb40a7c191b5f5f7384f37934362c42 requires_dist: - asciichartpy - asteval - bumps - colorama - cryspy + - darkdetect - dfo-ls - diffpy-pdffit2 - diffpy-utils - essdiffraction - gemmi + - jupyterlab - lmfit - numpy + - pandas + - pixi-kernel + - plotly - pooch + - py3dmol - rich - scipy - sympy @@ -4693,67 +4899,37 @@ packages: - typer - uncertainties - varname - - build ; extra == 'all' - - darkdetect ; extra == 'all' - - docformatter ; extra == 'all' - - interrogate ; extra == 'all' - - jinja2 ; extra == 'all' - - jupyterquiz ; extra == 'all' - - jupytext ; extra == 'all' - - mike ; extra == 'all' - - mkdocs ; extra == 'all' - - mkdocs-autorefs ; extra == 'all' - - mkdocs-jupyter ; extra == 'all' - - mkdocs-markdownextradata-plugin ; extra == 'all' - - mkdocs-material ; extra == 'all' - - mkdocs-plugin-inline-svg ; extra == 'all' - - mkdocstrings-python ; extra == 'all' - - nbmake ; extra == 'all' - - nbqa ; extra == 'all' - - nbstripout ; extra == 'all' - - pandas ; extra == 'all' - - plotly ; extra == 'all' - - pre-commit ; extra == 'all' - - py3dmol ; extra == 'all' - - pytest ; extra == 'all' - - pytest-cov ; extra == 'all' - - pytest-xdist ; extra == 'all' - - pyyaml ; extra == 'all' - - radon ; extra == 'all' - - ruff ; extra == 'all' - - validate-pyproject[all] ; extra == 'all' - - versioningit ; extra == 'all' - build ; extra == 'dev' - - docformatter ; extra == 'dev' + - copier ; extra == 'dev' + - format-docstring ; extra == 'dev' + - gitpython ; extra == 'dev' - interrogate ; extra == 'dev' - jinja2 ; extra == 'dev' - jupyterquiz ; extra == 'dev' - jupytext ; extra == 'dev' + - mike ; extra == 'dev' + - mkdocs ; extra == 'dev' + - mkdocs-autorefs ; extra == 'dev' + - mkdocs-jupyter ; extra == 'dev' + - mkdocs-markdownextradata-plugin ; extra == 'dev' + - mkdocs-material ; extra == 'dev' + - mkdocs-plugin-inline-svg ; extra == 'dev' + - mkdocstrings-python ; extra == 'dev' - nbmake ; extra == 'dev' - nbqa ; extra == 'dev' - nbstripout ; extra == 'dev' - pre-commit ; extra == 'dev' + - pydoclint ; extra == 'dev' - pytest ; extra == 'dev' - pytest-cov ; extra == 'dev' - pytest-xdist ; extra == 'dev' + - pyyaml ; extra == 'dev' - radon ; extra == 'dev' - ruff ; extra == 'dev' + - spdx-headers ; extra == 'dev' - validate-pyproject[all] ; extra == 'dev' - versioningit ; extra == 'dev' - - mike ; extra == 'docs' - - mkdocs ; extra == 'docs' - - mkdocs-autorefs ; extra == 'docs' - - mkdocs-jupyter ; extra == 'docs' - - mkdocs-markdownextradata-plugin ; extra == 'docs' - - mkdocs-material ; extra == 'docs' - - mkdocs-plugin-inline-svg ; extra == 'docs' - - mkdocstrings-python ; extra == 'docs' - - pyyaml ; extra == 'docs' - - darkdetect ; extra == 'visualization' - - pandas ; extra == 'visualization' - - plotly ; extra == 'visualization' - - py3dmol ; extra == 'visualization' - requires_python: '>=3.11,<3.14' + requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl name: email-validator version: 2.3.0 @@ -4762,20 +4938,20 @@ packages: - dnspython>=2.0.0 - idna>=2.0.0 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl name: essdiffraction - version: 26.3.0 - sha256: 3e3063d8af817ece183258b35579491d7edf85c4ca95fa3f1cef7877ad24365a + version: 26.4.0 + sha256: a58c1ac09c2196ae3769ab8c2d1f8c0152e4e1a69a02bc286caf419cdaa9ec1d requires_dist: - dask>=2022.1.0 - - essreduce>=26.2.1 + - essreduce>=26.4.0 - graphviz - numpy>=2 - plopp>=26.2.0 - pythreejs>=2.4.1 - sciline>=25.4.1 - scipp>=25.11.0 - - scippneutron>=25.2.0 + - scippneutron>=26.3.0 - scippnexus>=23.12.0 - tof>=25.12.0 - ncrystal[cif]>=4.1.0 @@ -4785,13 +4961,13 @@ packages: - pytest>=7.0 ; extra == 'test' - ipywidgets>=8.1.7 ; extra == 'test' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl name: essreduce - version: 26.3.1 - sha256: 0f629a52ad1793904cc41f6298229321f249f87e972b35f0b0fcfad8f280a78b + version: 26.4.0 + sha256: 06a9ebf58cba3cc29ac70f7b89a3e596be92d6a61130361b8c19fa8afec2b1b5 requires_dist: - sciline>=25.11.0 - - scipp>=25.4.0 + - scipp>=26.3.0 - scippneutron>=25.11.1 - scippnexus>=25.6.0 - graphviz>=0.20 ; extra == 'test' @@ -4854,15 +5030,15 @@ packages: - pytest-benchmark ; extra == 'devel' - pytest-cache ; extra == 'devel' - validictory ; extra == 'devel' -- pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl name: filelock - version: 3.25.1 - sha256: 18972df45473c4aa2c7921b609ee9ca4925910cc3a0fb226c96b92fc224ef7bf + version: 3.25.2 + sha256: ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/77/ce/f5a4c42c117f8113ce04048053c128d17426751a508f26398110c993a074/fonttools-4.62.0-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl name: fonttools - version: 4.62.0 - sha256: 4da779e8f342a32856075ddb193b2a024ad900bc04ecb744014c32409ae871ed + version: 4.62.1 + sha256: 68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4893,10 +5069,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/82/c7/985c1670aa6d82ef270f04cde11394c168f2002700353bd2bde405e59b8f/fonttools-4.62.0-cp313-cp313-macosx_10_13_universal2.whl +- pypi: https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl name: fonttools - version: 4.62.0 - sha256: 274c8b8a87e439faf565d3bcd3f9f9e31bca7740755776a4a90a4bfeaa722efa + version: 4.62.1 + sha256: 9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4927,10 +5103,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/8a/d7/8e4845993ee233c2023d11babe9b3dae7d30333da1d792eeccebcb77baab/fonttools-4.62.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl name: fonttools - version: 4.62.0 - sha256: 591220d5333264b1df0d3285adbdfe2af4f6a45bbf9ca2b485f97c9f577c49ff + version: 4.62.1 + sha256: 7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4961,10 +5137,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/c0/7a/9aeec114bc9fc00d757a41f092f7107863d372e684a5b5724c043654477c/fonttools-4.62.0-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl name: fonttools - version: 4.62.0 - sha256: 153afc3012ff8761b1733e8fbe5d98623409774c44ffd88fbcb780e240c11d13 + version: 4.62.1 + sha256: c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4995,10 +5171,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/c1/dc/c409c8ceec0d3119e9ab0b7b1a2e3c76d1f4d66e4a9db5c59e6b7652e7df/fonttools-4.62.0-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl name: fonttools - version: 4.62.0 - sha256: 93e27131a5a0ae82aaadcffe309b1bae195f6711689722af026862bede05c07c + version: 4.62.1 + sha256: 40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -5029,10 +5205,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e4/33/63d79ca41020dd460b51f1e0f58ad1ff0a36b7bcbdf8f3971d52836581e9/fonttools-4.62.0-cp311-cp311-macosx_10_9_universal2.whl +- pypi: https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: fonttools - version: 4.62.0 - sha256: 196cafef9aeec5258425bd31a4e9a414b2ee0d1557bca184d7923d3d3bcd90f9 + version: 4.62.1 + sha256: 1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -5063,10 +5239,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/f5/7a/e25245a30457595740041dba9d0ea8ec1b2517f2f1a6a741f15eba1a4edc/fonttools-4.62.0-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl name: fonttools - version: 4.62.0 - sha256: 6826a5aa53fb6def8a66bf423939745f415546c4e92478a7c531b8b6282b6c3b + version: 4.62.1 + sha256: e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -5097,10 +5273,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/fb/bc/60d93477b653eeb1ddf5f9ec34be689b79234d82dbdded269ac0252715b8/fonttools-4.62.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: fonttools - version: 4.62.0 - sha256: 106aec9226f9498fc5345125ff7200842c01eda273ae038f5049b0916907acee + version: 4.62.1 + sha256: 6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -5131,6 +5307,15 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl + name: format-docstring + version: 0.2.7 + sha256: c9d50eafebe0f260e3270ca662ff3a0ed4050f64d95e352f8c5f88d9aede42d6 + requires_dist: + - click>=8.0 + - jupyter-notebook-parser>=0.1.4 + - tomli>=1.1.0 ; python_full_version < '3.11' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl name: fqdn version: 1.5.1 @@ -5286,6 +5471,10 @@ packages: - zstandard ; python_full_version < '3.14' and extra == 'test-full' - tqdm ; extra == 'tqdm' requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl + name: funcy + version: '2.0' + sha256: 53df23c8bb1651b12f095df764bfb057935d49537a56de211b098f4c79614bb0 - pypi: https://files.pythonhosted.org/packages/42/15/26cac702cdf6281ddeb185d5912ce14e555e277c6e4caeb1d36966e43822/gemmi-0.7.5-cp311-cp311-macosx_11_0_arm64.whl name: gemmi version: 0.7.5 @@ -5336,6 +5525,35 @@ packages: - markdown ; extra == 'dev' - flake8 ; extra == 'dev' - wheel ; extra == 'dev' +- pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + name: gitdb + version: 4.0.12 + sha256: 67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf + requires_dist: + - smmap>=3.0.1,<6 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl + name: gitpython + version: 3.1.46 + sha256: 79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058 + requires_dist: + - gitdb>=4.0.1,<5 + - typing-extensions>=3.10.0.2 ; python_full_version < '3.10' + - coverage[toml] ; extra == 'test' + - ddt>=1.1.1,!=1.4.3 ; extra == 'test' + - mock ; python_full_version < '3.8' and extra == 'test' + - mypy==1.18.2 ; python_full_version >= '3.9' and extra == 'test' + - pre-commit ; extra == 'test' + - pytest>=7.3.1 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-sugar ; extra == 'test' + - typing-extensions ; python_full_version < '3.11' and extra == 'test' + - sphinx>=7.1.2,<7.2 ; extra == 'doc' + - sphinx-rtd-theme ; extra == 'doc' + - sphinx-autodoc-typehints ; extra == 'doc' + requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl name: graphviz version: '0.21' @@ -5422,10 +5640,10 @@ packages: - psutil ; extra == 'test' - setuptools ; extra == 'test' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl name: griffelib - version: 2.0.0 - sha256: 01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f + version: 2.0.1 + sha256: b769eed581c0e857d362fc8fcd8e57ecd2330c124b6104ac8b4c1c86d76970aa requires_dist: - pip>=24.0 ; extra == 'pypi' - platformdirs>=4.2 ; extra == 'pypi' @@ -5573,9 +5791,9 @@ packages: - socksio==1.* ; extra == 'socks' - zstandard>=0.18.0 ; extra == 'zstd' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda - sha256: 142a722072fa96cf16ff98eaaf641f54ab84744af81754c292cb81e0881c0329 - md5: 186a18e3ba246eccfc7cff00cd19a870 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + sha256: fbf86c4a59c2ed05bbffb2ba25c7ed94f6185ec30ecb691615d42342baa1a16a + md5: c80d8a3b84358cb967fa81e7075fbc8a depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -5583,32 +5801,32 @@ packages: license: MIT license_family: MIT purls: [] - size: 12728445 - timestamp: 1767969922681 -- conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.2-h14c5de8_0.conda - sha256: f3066beae7fe3002f09c8a412cdf1819f49a2c9a485f720ec11664330cf9f1fe - md5: 30334add4de016489b731c6662511684 + size: 12723451 + timestamp: 1773822285671 +- conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.3-h25d91c4_0.conda + sha256: 1294117122d55246bb83ad5b589e2a031aacdf2d0b1f99fd338aa4394f881735 + md5: 627eca44e62e2b665eeec57a984a7f00 depends: - - __osx >=10.13 + - __osx >=11.0 license: MIT license_family: MIT purls: [] - size: 12263724 - timestamp: 1767970604977 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda - sha256: 24bc62335106c30fecbcc1dba62c5eba06d18b90ea1061abd111af7b9c89c2d7 - md5: 114e6bfe7c5ad2525eb3597acdbf2300 + size: 12273764 + timestamp: 1773822733780 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda + sha256: 3a7907a17e9937d3a46dfd41cffaf815abad59a569440d1e25177c15fd0684e5 + md5: f1182c91c0de31a7abd40cedf6a5ebef depends: - __osx >=11.0 license: MIT license_family: MIT purls: [] - size: 12389400 - timestamp: 1772209104304 -- pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + size: 12361647 + timestamp: 1773822915649 +- pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl name: identify - version: 2.6.17 - sha256: be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0 + version: 2.6.18 + sha256: 8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737 requires_dist: - ukkonen ; extra == 'license' requires_python: '>=3.10' @@ -5622,18 +5840,16 @@ packages: - pytest>=8.3.2 ; extra == 'all' - flake8>=7.1.1 ; extra == 'all' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl name: importlib-metadata - version: 8.7.1 - sha256: 5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151 + version: 9.0.0 + sha256: 2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7 requires_dist: - zipp>=3.20 - pytest>=6,!=8.1.* ; extra == 'test' - packaging ; extra == 'test' - pyfakefs ; extra == 'test' - - flufl-flake8 ; extra == 'test' - pytest-perf>=0.9.2 ; extra == 'test' - - jaraco-test>=5.4 ; extra == 'test' - sphinx>=3.5 ; extra == 'doc' - jaraco-packaging>=9.3 ; extra == 'doc' - rst-linker>=1.9 ; extra == 'doc' @@ -5641,13 +5857,12 @@ packages: - sphinx-lint ; extra == 'doc' - jaraco-tidelift>=1.4 ; extra == 'doc' - ipython ; extra == 'perf' - - pytest-checkdocs>=2.4 ; extra == 'check' + - pytest-checkdocs>=2.14 ; extra == 'check' - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' - pytest-cov ; extra == 'cover' - pytest-enabler>=3.4 ; extra == 'enabler' - - pytest-mypy>=1.0.1 ; extra == 'type' - - mypy<1.19 ; platform_python_implementation == 'PyPy' and extra == 'type' - requires_python: '>=3.9' + - pytest-mypy>=1.0.1 ; platform_python_implementation != 'PyPy' and extra == 'type' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl name: iniconfig version: 2.3.0 @@ -5696,23 +5911,23 @@ packages: - pytest-cov ; extra == 'test' - nbval>=0.9.2 ; extra == 'test' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl name: ipykernel - version: 6.31.0 - sha256: abe5386f6ced727a70e0eb0cf1da801fa7c5fa6ff82147747d5a0406cd8c94af + version: 7.2.0 + sha256: 3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661 requires_dist: - appnope>=0.1.2 ; sys_platform == 'darwin' - comm>=0.1.1 - debugpy>=1.6.5 - ipython>=7.23.1 - - jupyter-client>=8.0.0 - - jupyter-core>=4.12,!=5.0.* + - jupyter-client>=8.8.0 + - jupyter-core>=5.1,!=6.0.* - matplotlib-inline>=0.1 - nest-asyncio>=1.4 - packaging>=22 - psutil>=5.7 - pyzmq>=25 - - tornado>=6.2 + - tornado>=6.4.1 - traitlets>=5.4.0 - coverage[toml] ; extra == 'cov' - matplotlib ; extra == 'cov' @@ -5721,8 +5936,8 @@ packages: - intersphinx-registry ; extra == 'docs' - myst-parser ; extra == 'docs' - pydata-sphinx-theme ; extra == 'docs' - - sphinx ; extra == 'docs' - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx<8.2.0 ; extra == 'docs' - sphinxcontrib-github-alt ; extra == 'docs' - sphinxcontrib-spelling ; extra == 'docs' - trio ; extra == 'docs' @@ -5734,8 +5949,8 @@ packages: - pytest-asyncio>=0.23.5 ; extra == 'test' - pytest-cov ; extra == 'test' - pytest-timeout ; extra == 'test' - - pytest>=7.0,<9 ; extra == 'test' - requires_python: '>=3.9' + - pytest>=7.0,<10 ; extra == 'test' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl name: ipython version: 9.10.0 @@ -5906,16 +6121,25 @@ packages: - markupsafe>=2.0 - babel>=2.7 ; extra == 'i18n' requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl + name: jinja2-ansible-filters + version: 1.3.2 + sha256: e1082f5564917649c76fed239117820610516ec10f87735d0338688800a55b34 + requires_dist: + - jinja2 + - pyyaml + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl name: json5 version: 0.13.0 sha256: 9a08e1dd65f6a4d4c6fa82d216cf2477349ec2346a38fd70cc11d2557499fbcc requires_python: '>=3.8.0' -- pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl name: jsonpointer - version: 3.0.0 - sha256: 13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942 - requires_python: '>=3.7' + version: 3.1.1 + sha256: 8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl name: jsonschema version: 4.26.0 @@ -6034,6 +6258,11 @@ packages: - jupyter-server>=1.1.2 - importlib-metadata>=4.8.3 ; python_full_version < '3.10' requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + name: jupyter-notebook-parser + version: 0.1.4 + sha256: 27b3b67cf898684e646d569f017cb27046774ad23866cb0bdf51d5f76a46476b + requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl name: jupyter-server version: 2.17.0 @@ -6106,10 +6335,10 @@ packages: - pytest-timeout ; extra == 'test' - pytest>=7.0 ; extra == 'test' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl name: jupyterlab - version: 4.5.5 - sha256: a35694a40a8e7f2e82f387472af24e61b22adcce87b5a8ab97a5d9c486202a6d + version: 4.5.6 + sha256: d6b3dac883aa4d9993348e0f8e95b24624f75099aed64eab6a4351a9cdd1e580 requires_dist: - async-lru>=1.0.0 - httpx>=0.25.0,<1 @@ -6360,9 +6589,9 @@ packages: - changelist==0.5 ; extra == 'dev' - spin==0.15 ; extra == 'dev' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda - sha256: 565941ac1f8b0d2f2e8f02827cbca648f4d18cd461afc31f15604cd291b5c5f3 - md5: 12bd9a3f089ee6c9266a37dab82afabd +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c + md5: 18335a698559cdbcd86150a48bf54ba6 depends: - __glibc >=2.17,<3.0.a0 - zstd >=1.5.7,<1.6.0a0 @@ -6371,8 +6600,8 @@ packages: license: GPL-3.0-only license_family: GPL purls: [] - size: 725507 - timestamp: 1770267139900 + size: 728002 + timestamp: 1774197446916 - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda sha256: a7a4481a4d217a3eadea0ec489826a69070fcc3153f00443aa491ed21527d239 md5: 6f7b4302263347698fd24565fbf11310 @@ -6416,76 +6645,72 @@ packages: purls: [] size: 1229639 timestamp: 1770863511331 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda - build_number: 5 - sha256: 18c72545080b86739352482ba14ba2c4815e19e26a7417ca21a95b76ec8da24c - md5: c160954f7418d7b6e87eaf05a8913fa9 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda + build_number: 6 + sha256: 7bfe936dbb5db04820cf300a9cc1f5ee8d5302fc896c2d66e30f1ee2f20fbfd6 + md5: 6d6d225559bfa6e2f3c90ee9c03d4e2e depends: - - libopenblas >=0.3.30,<0.3.31.0a0 - - libopenblas >=0.3.30,<1.0a0 + - libopenblas >=0.3.32,<0.3.33.0a0 + - libopenblas >=0.3.32,<1.0a0 constrains: + - blas 2.306 openblas + - liblapack 3.11.0 6*_openblas + - liblapacke 3.11.0 6*_openblas + - libcblas 3.11.0 6*_openblas - mkl <2026 - - liblapack 3.11.0 5*_openblas - - libcblas 3.11.0 5*_openblas - - blas 2.305 openblas - - liblapacke 3.11.0 5*_openblas license: BSD-3-Clause - license_family: BSD purls: [] - size: 18213 - timestamp: 1765818813880 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda - build_number: 5 - sha256: 4754de83feafa6c0b41385f8dab1b13f13476232e16f524564a340871a9fc3bc - md5: 36d2e68a156692cbae776b75d6ca6eae + size: 18621 + timestamp: 1774503034895 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-6_he492b99_openblas.conda + build_number: 6 + sha256: 6865098475f3804208038d0c424edf926f4dc9eacaa568d14e29f59df53731fd + md5: 93e7fc07b395c9e1341d3944dcf2aced depends: - - libopenblas >=0.3.30,<0.3.31.0a0 - - libopenblas >=0.3.30,<1.0a0 + - libopenblas >=0.3.32,<0.3.33.0a0 + - libopenblas >=0.3.32,<1.0a0 constrains: - - liblapack 3.11.0 5*_openblas - - blas 2.305 openblas - - libcblas 3.11.0 5*_openblas + - libcblas 3.11.0 6*_openblas + - blas 2.306 openblas - mkl <2026 - - liblapacke 3.11.0 5*_openblas + - liblapacke 3.11.0 6*_openblas + - liblapack 3.11.0 6*_openblas license: BSD-3-Clause - license_family: BSD purls: [] - size: 18476 - timestamp: 1765819054657 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda - build_number: 5 - sha256: 620a6278f194dcabc7962277da6835b1e968e46ad0c8e757736255f5ddbfca8d - md5: bcc025e2bbaf8a92982d20863fe1fb69 + size: 18724 + timestamp: 1774503646078 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda + build_number: 6 + sha256: 979227fc03628925037ab2dfda008eb7b5592644d9c2c21dd285cefe8c42553d + md5: e551103471911260488a02155cef9c94 depends: - - libopenblas >=0.3.30,<0.3.31.0a0 - - libopenblas >=0.3.30,<1.0a0 + - libopenblas >=0.3.32,<0.3.33.0a0 + - libopenblas >=0.3.32,<1.0a0 constrains: - - libcblas 3.11.0 5*_openblas - - liblapack 3.11.0 5*_openblas - - liblapacke 3.11.0 5*_openblas - - blas 2.305 openblas + - liblapacke 3.11.0 6*_openblas + - liblapack 3.11.0 6*_openblas + - blas 2.306 openblas + - libcblas 3.11.0 6*_openblas - mkl <2026 license: BSD-3-Clause - license_family: BSD purls: [] - size: 18546 - timestamp: 1765819094137 -- conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda - build_number: 5 - sha256: f0cb7b2697461a306341f7ff32d5b361bb84f3e94478464c1e27ee01fc8f276b - md5: f9decf88743af85c9c9e05556a4c47c0 + size: 18859 + timestamp: 1774504387211 +- conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda + build_number: 6 + sha256: 10c8054f007adca8c780cd8bb9335fa5d990f0494b825158d3157983a25b1ea2 + md5: 95543eec964b4a4a7ca3c4c9be481aa1 depends: - - mkl >=2025.3.0,<2026.0a0 + - mkl >=2025.3.1,<2026.0a0 constrains: - - liblapack 3.11.0 5*_mkl - - libcblas 3.11.0 5*_mkl - - blas 2.305 mkl - - liblapacke 3.11.0 5*_mkl + - blas 2.306 mkl + - liblapacke 3.11.0 6*_mkl + - liblapack 3.11.0 6*_mkl + - libcblas 3.11.0 6*_mkl license: BSD-3-Clause - license_family: BSD purls: [] - size: 67438 - timestamp: 1765819100043 + size: 68082 + timestamp: 1774503684284 - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda sha256: 318f36bd49ca8ad85e6478bd8506c88d82454cc008c1ac1c6bf00a3c42fa610e md5: 72c8fd1af66bd67bf580645b426513ed @@ -6585,86 +6810,82 @@ packages: purls: [] size: 290754 timestamp: 1764018009077 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda - build_number: 5 - sha256: 0cbdcc67901e02dc17f1d19e1f9170610bd828100dc207de4d5b6b8ad1ae7ad8 - md5: 6636a2b6f1a87572df2970d3ebc87cc0 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda + build_number: 6 + sha256: 57edafa7796f6fa3ebbd5367692dd4c7f552be42109c2dd1a7c89b55089bf374 + md5: 36ae340a916635b97ac8a0655ace2a35 depends: - - libblas 3.11.0 5_h4a7cf45_openblas + - libblas 3.11.0 6_h4a7cf45_openblas constrains: - - liblapacke 3.11.0 5*_openblas - - blas 2.305 openblas - - liblapack 3.11.0 5*_openblas + - blas 2.306 openblas + - liblapack 3.11.0 6*_openblas + - liblapacke 3.11.0 6*_openblas license: BSD-3-Clause - license_family: BSD purls: [] - size: 18194 - timestamp: 1765818837135 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda - build_number: 5 - sha256: 8077c29ea720bd152be6e6859a3765228cde51301fe62a3b3f505b377c2cb48c - md5: b31d771cbccff686e01a687708a7ca41 + size: 18622 + timestamp: 1774503050205 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-6_h9b27e0a_openblas.conda + build_number: 6 + sha256: 8422e1ce083e015bdb44addd25c9a8fe99aa9b0edbd9b7f1239b7ac1e3d04f77 + md5: 2a174868cb9e136c4e92b3ffc2815f04 depends: - - libblas 3.11.0 5_he492b99_openblas + - libblas 3.11.0 6_he492b99_openblas constrains: - - liblapack 3.11.0 5*_openblas - - blas 2.305 openblas - - liblapacke 3.11.0 5*_openblas + - liblapacke 3.11.0 6*_openblas + - blas 2.306 openblas + - liblapack 3.11.0 6*_openblas license: BSD-3-Clause - license_family: BSD purls: [] - size: 18484 - timestamp: 1765819073006 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda - build_number: 5 - sha256: 38809c361bbd165ecf83f7f05fae9b791e1baa11e4447367f38ae1327f402fc0 - md5: efd8bd15ca56e9d01748a3beab8404eb + size: 18713 + timestamp: 1774503667477 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda + build_number: 6 + sha256: 2e6b3e9b1ab672133b70fc6730e42290e952793f132cb5e72eee22835463eba0 + md5: 805c6d31c5621fd75e53dfcf21fb243a depends: - - libblas 3.11.0 5_h51639a9_openblas + - libblas 3.11.0 6_h51639a9_openblas constrains: - - liblapacke 3.11.0 5*_openblas - - liblapack 3.11.0 5*_openblas - - blas 2.305 openblas + - liblapacke 3.11.0 6*_openblas + - blas 2.306 openblas + - liblapack 3.11.0 6*_openblas license: BSD-3-Clause - license_family: BSD purls: [] - size: 18548 - timestamp: 1765819108956 -- conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda - build_number: 5 - sha256: 49dc59d8e58360920314b8d276dd80da7866a1484a9abae4ee2760bc68f3e68d - md5: b3fa8e8b55310ba8ef0060103afb02b5 + size: 18863 + timestamp: 1774504433388 +- conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda + build_number: 6 + sha256: 02b2a2225f4899c6aaa1dc723e06b3f7a4903d2129988f91fc1527409b07b0a5 + md5: 9e4bf521c07f4d423cba9296b7927e3c depends: - - libblas 3.11.0 5_hf2e6a31_mkl + - libblas 3.11.0 6_hf2e6a31_mkl constrains: - - liblapack 3.11.0 5*_mkl - - liblapacke 3.11.0 5*_mkl - - blas 2.305 mkl + - blas 2.306 mkl + - liblapacke 3.11.0 6*_mkl + - liblapack 3.11.0 6*_mkl license: BSD-3-Clause - license_family: BSD purls: [] - size: 68079 - timestamp: 1765819124349 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda - sha256: fa002b43752fe5860e588435525195324fe250287105ebd472ac138e97de45e6 - md5: 836389b6b9ae58f3fbcf7cafebd5c7f2 + size: 68221 + timestamp: 1774503722413 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.2-h19cb2f5_0.conda + sha256: 46561199545890e050a8a90edcfce984e5f881da86b09388926e3a6c6b759dec + md5: ed6f7b7a35f942a0301e581d72616f7d depends: - __osx >=11.0 license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 570141 - timestamp: 1772001147762 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda - sha256: ce1049fa6fda9cf08ff1c50fb39573b5b0ea6958375d8ea7ccd8456ab81a0bcb - md5: e9c56daea841013e7774b5cd46f41564 + size: 564908 + timestamp: 1774439353713 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.2-h55c6f16_0.conda + sha256: d1402087c8792461bfc081629e8aa97e6e577a31ae0b84e6b9cc144a18f48067 + md5: 4280e0a7fd613b271e022e60dea0138c depends: - __osx >=11.0 license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 568910 - timestamp: 1772001095642 + size: 568094 + timestamp: 1774439202359 - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda sha256: 1cd6048169fa0395af74ed5d8f1716e22c19a81a8a36f934c110ca3ad4dd27b4 md5: 172bf1cd1ff8629f2b1179945ed45055 @@ -7034,55 +7255,55 @@ packages: purls: [] size: 89411 timestamp: 1769482314283 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - sha256: a4a7dab8db4dc81c736e9a9b42bdfd97b087816e029e221380511960ac46c690 - md5: b499ce4b026493a13774bcf0f4c33849 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda + sha256: 663444d77a42f2265f54fb8b48c5450bfff4388d9c0f8253dd7855f0d993153f + md5: 2a45e7f8af083626f009645a6481f12d depends: - __glibc >=2.17,<3.0.a0 - - c-ares >=1.34.5,<2.0a0 + - c-ares >=1.34.6,<2.0a0 - libev >=4.33,<4.34.0a0 - libev >=4.33,<5.0a0 - libgcc >=14 - libstdcxx >=14 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.5,<4.0a0 license: MIT license_family: MIT purls: [] - size: 666600 - timestamp: 1756834976695 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - sha256: c48d7e1cc927aef83ff9c48ae34dd1d7495c6ccc1edc4a3a6ba6aff1624be9ac - md5: e7630cef881b1174d40f3e69a883e55f + size: 663344 + timestamp: 1773854035739 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.68.1-h70048d4_0.conda + sha256: 899551e16aac9dfb85bfc2fd98b655f4d1b7fea45720ec04ccb93d95b4d24798 + md5: dba4c95e2fe24adcae4b77ebf33559ae depends: - - __osx >=10.13 - - c-ares >=1.34.5,<2.0a0 + - __osx >=11.0 + - c-ares >=1.34.6,<2.0a0 - libcxx >=19 - libev >=4.33,<4.34.0a0 - libev >=4.33,<5.0a0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.5,<4.0a0 license: MIT license_family: MIT purls: [] - size: 605680 - timestamp: 1756835898134 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - sha256: a07cb53b5ffa2d5a18afc6fd5a526a5a53dd9523fbc022148bd2f9395697c46d - md5: a4b4dd73c67df470d091312ab87bf6ae + size: 606749 + timestamp: 1773854765508 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + sha256: 2bc7bc3978066f2c274ebcbf711850cc9ab92e023e433b9631958a098d11e10a + md5: 6ea18834adbc3b33df9bd9fb45eaf95b depends: - __osx >=11.0 - - c-ares >=1.34.5,<2.0a0 + - c-ares >=1.34.6,<2.0a0 - libcxx >=19 - libev >=4.33,<4.34.0a0 - libev >=4.33,<5.0a0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.5,<4.0a0 license: MIT license_family: MIT purls: [] - size: 575454 - timestamp: 1756835746393 + size: 576526 + timestamp: 1773854624224 - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5 md5: d864d34357c3b65a4b731f78c0801dc4 @@ -7094,51 +7315,48 @@ packages: purls: [] size: 33731 timestamp: 1750274110928 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda - sha256: 199d79c237afb0d4780ccd2fbf829cea80743df60df4705202558675e07dd2c5 - md5: be43915efc66345cccb3c310b6ed0374 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + sha256: 6dc30b28f32737a1c52dada10c8f3a41bc9e021854215efca04a7f00487d09d9 + md5: 89d61bc91d3f39fda0ca10fcd3c68594 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - libgfortran - libgfortran5 >=14.3.0 constrains: - - openblas >=0.3.30,<0.3.31.0a0 + - openblas >=0.3.32,<0.3.33.0a0 license: BSD-3-Clause - license_family: BSD purls: [] - size: 5927939 - timestamp: 1763114673331 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda - sha256: ba642353f7f41ab2d2eb6410fbe522238f0f4483bcd07df30b3222b4454ee7cd - md5: 9241a65e6e9605e4581a2a8005d7f789 + size: 5928890 + timestamp: 1774471724897 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.32-openmp_h9e49c7b_0.conda + sha256: 6764229359cd927c9efc036930ba28f83436b8d6759c5ac4ea9242fc29a7184e + md5: 4058c5f8dbef6d28cb069f96b95ae6df depends: - - __osx >=10.13 + - __osx >=11.0 - libgfortran - libgfortran5 >=14.3.0 - llvm-openmp >=19.1.7 constrains: - - openblas >=0.3.30,<0.3.31.0a0 + - openblas >=0.3.32,<0.3.33.0a0 license: BSD-3-Clause - license_family: BSD purls: [] - size: 6268795 - timestamp: 1763117623665 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda - sha256: ebbbc089b70bcde87c4121a083c724330f02a690fb9d7c6cd18c30f1b12504fa - md5: a6f6d3a31bb29e48d37ce65de54e2df0 + size: 6289730 + timestamp: 1774474444702 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda + sha256: 713e453bde3531c22a660577e59bf91ef578dcdfd5edb1253a399fa23514949a + md5: 3a1111a4b6626abebe8b978bb5a323bf depends: - __osx >=11.0 - libgfortran - libgfortran5 >=14.3.0 - llvm-openmp >=19.1.7 constrains: - - openblas >=0.3.30,<0.3.31.0a0 + - openblas >=0.3.32,<0.3.33.0a0 license: BSD-3-Clause - license_family: BSD purls: [] - size: 4284132 - timestamp: 1768547079205 + size: 4308797 + timestamp: 1774472508546 - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda sha256: d716847b7deca293d2e49ed1c8ab9e4b9e04b9d780aea49a97c26925b28a7993 md5: fd893f6a3002a635b5e50ceb9dd2c0f4 @@ -7295,98 +7513,97 @@ packages: purls: [] size: 520078 timestamp: 1772704728534 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 - md5: edb0dca6bc32e4f4789199455a1dbeb8 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + sha256: 55044c403570f0dc26e6364de4dc5368e5f3fc7ff103e867c487e2b5ab2bcda9 + md5: d87ff7921124eccd67248aa483c23fec depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 constrains: - - zlib 1.3.1 *_2 + - zlib 1.3.2 *_2 license: Zlib license_family: Other purls: [] - size: 60963 - timestamp: 1727963148474 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - sha256: 8412f96504fc5993a63edf1e211d042a1fd5b1d51dedec755d2058948fcced09 - md5: 003a54a4e32b02f7355b50a837e699da + size: 63629 + timestamp: 1774072609062 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_2.conda + sha256: 4c6da089952b2d70150c74234679d6f7ac04f4a98f9432dec724968f912691e7 + md5: 30439ff30578e504ee5e0b390afc8c65 depends: - - __osx >=10.13 + - __osx >=11.0 constrains: - - zlib 1.3.1 *_2 + - zlib 1.3.2 *_2 license: Zlib license_family: Other purls: [] - size: 57133 - timestamp: 1727963183990 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - sha256: ce34669eadaba351cd54910743e6a2261b67009624dbc7daeeafdef93616711b - md5: 369964e85dc26bfe78f41399b366c435 + size: 59000 + timestamp: 1774073052242 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + sha256: 361415a698514b19a852f5d1123c5da746d4642139904156ddfca7c922d23a05 + md5: bc5a5721b6439f2f62a84f2548136082 depends: - __osx >=11.0 constrains: - - zlib 1.3.1 *_2 + - zlib 1.3.2 *_2 license: Zlib license_family: Other purls: [] - size: 46438 - timestamp: 1727963202283 -- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - sha256: ba945c6493449bed0e6e29883c4943817f7c79cbff52b83360f7b341277c6402 - md5: 41fbfac52c601159df6c01f875de31b9 + size: 47759 + timestamp: 1774072956767 +- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + sha256: 88609816e0cc7452bac637aaf65783e5edf4fee8a9f8e22bdc3a75882c536061 + md5: dbabbd6234dea34040e631f87676292f depends: - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 constrains: - - zlib 1.3.1 *_2 + - zlib 1.3.2 *_2 license: Zlib license_family: Other purls: [] - size: 55476 - timestamp: 1727963768015 -- conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda - sha256: b63df4e592b3362e7d13e3d1cf8e55ce932ff4f17611c8514b5d36368ec2094c - md5: 3921780bab286f2439ba483c22b90345 + size: 58347 + timestamp: 1774072851498 +- conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.1-h0d3cbff_0.conda + sha256: 65c30298a921b3f6d49e2f4ec220bc2d2237cbdabd1c19031f4fafbfdad8aa5e + md5: d5e67fb9aeb3f32fc474ca7859a5583b depends: - __osx >=11.0 constrains: - - openmp 22.1.0|22.1.0.* - intel-openmp <0.0a0 + - openmp 22.1.1|22.1.1.* license: Apache-2.0 WITH LLVM-exception license_family: APACHE purls: [] - size: 311938 - timestamp: 1772024731611 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda - sha256: 0daeedb3872ad0fdd6f0d7e7165c63488e8a315d7057907434145fba0c1e7b3d - md5: ff0820b5588b20be3b858552ecf8ffae + size: 311088 + timestamp: 1774349643537 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.1-hc7d1edf_0.conda + sha256: c6f67e928f47603aca7e4b83632d8f3e82bd698051c7c0b34fcce3796eb9b63c + md5: 5a44f53783d87427790fc8692542f1bb depends: - __osx >=11.0 constrains: - - openmp 22.1.0|22.1.0.* - intel-openmp <0.0a0 + - openmp 22.1.1|22.1.1.* license: Apache-2.0 WITH LLVM-exception license_family: APACHE purls: [] - size: 285558 - timestamp: 1772028716784 -- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda - sha256: bb55a3736380759d338f87aac68df4fd7d845ae090b94400525f5d21a55eea31 - md5: e5505e0b7d6ef5c19d5c0c1884a2f494 + size: 285912 + timestamp: 1774349644882 +- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.1-h4fa8253_0.conda + sha256: 64c7fe6490583f3c49c36c2f413e681072102db8abea13a3e1832f44eaf55518 + md5: d9f479404fe316e575f4a4575f3df406 depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: - - openmp 22.1.0|22.1.0.* + - openmp 22.1.1|22.1.1.* - intel-openmp <0.0a0 license: Apache-2.0 WITH LLVM-exception license_family: APACHE purls: [] - size: 347404 - timestamp: 1772025050288 + size: 347138 + timestamp: 1774349485844 - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl name: lmfit version: 1.3.4 @@ -7785,22 +8002,22 @@ packages: - markupsafe>=2.0.1 - mkdocs>=1.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl name: mkdocs-get-deps - version: 0.2.1 - sha256: 07d6076298715dfcb8232e7dec083d09015b4e65482ce7f6743cb07cd1da847e + version: 0.2.2 + sha256: e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650 requires_dist: - importlib-metadata>=4.3 ; python_full_version < '3.10' - mergedeep>=1.3.4 - platformdirs>=2.2.0 - pyyaml>=5.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl name: mkdocs-jupyter - version: 0.25.1 - sha256: 3f679a857609885d322880e72533ef5255561bbfdb13cfee2a1e92ef4d4ad8d8 + version: 0.26.1 + sha256: 527242c2c8f1d30970764bbab752de41243e5703f458d8bc05336ec53828192e requires_dist: - - ipykernel>6.0.0,<7.0.0 + - ipykernel>6.0.0,<8 - jupytext>1.13.8,<2 - mkdocs-material>9.0.0 - mkdocs>=1.4.0,<2 @@ -7815,10 +8032,10 @@ packages: - mkdocs - pyyaml requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl name: mkdocs-material - version: 9.7.4 - sha256: 6549ad95e4d130ed5099759dfa76ea34c593eefdb9c18c97273605518e99cfbf + version: 9.7.6 + sha256: 71b84353921b8ea1ba84fe11c50912cc512da8fe0881038fcc9a0761c0e635ba requires_dist: - babel>=2.10 - backrefs>=5.7.post1 @@ -7826,7 +8043,7 @@ packages: - jinja2>=3.1 - markdown>=3.2 - mkdocs-material-extensions>=1.3 - - mkdocs>=1.6 + - mkdocs>=1.6,<2 - paginate>=0.5 - pygments>=2.16 - pymdown-extensions>=10.2 @@ -7876,11 +8093,11 @@ packages: - griffelib>=2.0 - typing-extensions>=4.0 ; python_full_version < '3.11' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda - sha256: b2b4c84b95210760e4d12319416c60ab66e03674ccdcbd14aeb59f82ebb1318d - md5: fd05d1e894497b012d05a804232254ed +- conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_11.conda + sha256: f2c2b2a3c2e7d08d78c10bef7c135a4262c80d1d48c85fb5902ca30d61d645f4 + md5: 3fd3009cef89c36e9898a6feeb0f5530 depends: - - llvm-openmp >=21.1.8 + - llvm-openmp >=22.1.1 - tbb >=2022.3.0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 @@ -7888,8 +8105,8 @@ packages: license: LicenseRef-IntelSimplifiedSoftwareOct2022 license_family: Proprietary purls: [] - size: 100224829 - timestamp: 1767634557029 + size: 99997309 + timestamp: 1774449747739 - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl name: mpld3 version: 0.5.12 @@ -8087,10 +8304,10 @@ packages: requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl name: narwhals - version: 2.17.0 - sha256: 2ac5307b7c2b275a7d66eeda906b8605e3d7a760951e188dcfff86e8ebe083dd + version: 2.18.1 + sha256: a0a8bb80205323851338888ba3a12b4f65d352362c8a94be591244faf36504ad requires_dist: - cudf-cu12>=24.10.0 ; extra == 'cudf' - dask[dataframe]>=2024.8 ; extra == 'dask' @@ -8394,87 +8611,83 @@ packages: version: 1.10.0 sha256: 5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*' -- conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.7.0-he4ff34a_0.conda - sha256: 4791285a34195615e22a94dc62cbd43181548b34eb34e6cb1dcf8f469476a32e - md5: ba562095149fde99c700365d90e6d632 +- conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.8.2-he4ff34a_0.conda + sha256: d1a673d1418d9e956b6e4e46c23e72a511c5c1d45dc5519c947457427036d5e2 + md5: baffb1570b3918c784d4490babc52fbf depends: - - __glibc >=2.28,<3.0.a0 - - libstdcxx >=14 - libgcc >=14 - - libnghttp2 >=1.67.0,<2.0a0 + - libstdcxx >=14 + - __glibc >=2.28,<3.0.a0 + - libnghttp2 >=1.68.1,<2.0a0 + - libuv >=1.51.0,<2.0a0 - c-ares >=1.34.6,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - libsqlite >=3.52.0,<4.0a0 + - icu >=78.3,<79.0a0 + - libzlib >=1.3.2,<2.0a0 + - libabseil >=20260107.1,<20260108.0a0 + - libabseil * cxx17* - zstd >=1.5.7,<1.6.0a0 - libbrotlicommon >=1.2.0,<1.3.0a0 - libbrotlienc >=1.2.0,<1.3.0a0 - libbrotlidec >=1.2.0,<1.3.0a0 - - libuv >=1.51.0,<2.0a0 - - openssl >=3.5.5,<4.0a0 - - libabseil >=20260107.1,<20260108.0a0 - - libabseil * cxx17* - - icu >=78.2,<79.0a0 - - libzlib >=1.3.1,<2.0a0 - - libsqlite >=3.51.2,<4.0a0 license: MIT - license_family: MIT purls: [] - size: 18875002 - timestamp: 1772300115686 -- conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.7.0-hf6efa0e_0.conda - sha256: a23a3b536f1ccd3c10a2cbdf8c638b601c51f6e9bbf1e6c708610cf5b1df3d0f - md5: 448185eb18358e4b5fe2a157cc7e415f + size: 18829340 + timestamp: 1774514313036 +- conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.8.2-hf3170e9_0.conda + sha256: 6e82ed9c2de2e5a472a9c25f7a4a3a296d33aa38b94151acbbb5a28754962d8d + md5: de36be6257a15d17e85c96b47d290f82 depends: - libcxx >=19 - - __osx >=10.15 - - openssl >=3.5.5,<4.0a0 - - c-ares >=1.34.6,<2.0a0 - - libuv >=1.51.0,<2.0a0 + - __osx >=11.0 + - libzlib >=1.3.2,<2.0a0 + - libnghttp2 >=1.68.1,<2.0a0 + - libabseil >=20260107.1,<20260108.0a0 + - libabseil * cxx17* + - icu >=78.3,<79.0a0 - libbrotlicommon >=1.2.0,<1.3.0a0 - libbrotlienc >=1.2.0,<1.3.0a0 - libbrotlidec >=1.2.0,<1.3.0a0 - - libabseil >=20260107.1,<20260108.0a0 - - libabseil * cxx17* - - libnghttp2 >=1.67.0,<2.0a0 - - icu >=78.2,<79.0a0 - - libsqlite >=3.51.2,<4.0a0 - - libzlib >=1.3.1,<2.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libuv >=1.51.0,<2.0a0 + - c-ares >=1.34.6,<2.0a0 + - openssl >=3.5.5,<4.0a0 - zstd >=1.5.7,<1.6.0a0 license: MIT - license_family: MIT purls: [] - size: 18342335 - timestamp: 1772299132495 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.7.0-hbfc8e16_0.conda - sha256: 6398a45d2bb943585540d98a9b1b3bed1fb48fabd327b56eba2ece00e3dd202f - md5: 22a4a51f30590f274bb3f831d911c09a + size: 18382168 + timestamp: 1774517889949 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.8.2-h7039424_0.conda + sha256: 4782b172b3b8a557b60bf5f591821cf100e2092ba7a5494ce047dfa41626de26 + md5: ca8277c52fdface8bb8ebff7cd9a6f56 depends: - - __osx >=11.0 - libcxx >=19 - - libsqlite >=3.51.2,<4.0a0 - - zstd >=1.5.7,<1.6.0a0 + - __osx >=11.0 + - icu >=78.3,<79.0a0 - libbrotlicommon >=1.2.0,<1.3.0a0 - libbrotlienc >=1.2.0,<1.3.0a0 - libbrotlidec >=1.2.0,<1.3.0a0 + - libnghttp2 >=1.68.1,<2.0a0 - libuv >=1.51.0,<2.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + - c-ares >=1.34.6,<2.0a0 - libabseil >=20260107.1,<20260108.0a0 - libabseil * cxx17* - - icu >=78.2,<79.0a0 - - c-ares >=1.34.6,<2.0a0 - - openssl >=3.5.5,<4.0a0 - - libnghttp2 >=1.67.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 license: MIT - license_family: MIT purls: [] - size: 17351648 - timestamp: 1772299122966 -- conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.7.0-h80d1838_0.conda - sha256: 0ea0ddad32366396d1beda7ce93ddd3d9f705286c1a4f99f05ec0049183c1e97 - md5: 61b62d3be12c9edbe34f202d51891927 + size: 17101803 + timestamp: 1774517834028 +- conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda + sha256: 5e38e51da1aa4bc352db9b4cec1c3e25811de0f4408edaa24e009a64de6dbfdf + md5: e626ee7934e4b7cb21ce6b721cff8677 license: MIT - license_family: MIT purls: [] - size: 31254428 - timestamp: 1772299132945 + size: 31271315 + timestamp: 1774517904472 - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl name: notebook-shim version: 0.2.4 @@ -9614,11 +9827,6 @@ packages: - trove-classifiers>=2024.10.12 ; extra == 'tests' - defusedxml ; extra == 'xmp' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - name: pip - version: 26.0.1 - sha256: bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl name: pixi-kernel version: 0.7.1 @@ -9636,10 +9844,10 @@ packages: version: 4.9.4 sha256: 68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl name: plopp - version: 26.3.0 - sha256: 44e231f202e8e9bfba4556762e7a49ec4832c5c1df0c96a744f87e7d50fddc11 + version: 26.3.1 + sha256: 56531f2f71fa4f7f33c172312d2423d969deb9b9dd29b2524ad3ed7e33d220eb requires_dist: - lazy-loader>=0.4 - matplotlib>=3.8 @@ -9718,6 +9926,14 @@ packages: - pytest-benchmark ; extra == 'testing' - coverage ; extra == 'testing' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl + name: plumbum + version: 1.10.0 + sha256: 9583d737ac901c474d99d030e4d5eec4c4e6d2d7417b1cf49728cf3be34f6dc8 + requires_dist: + - pywin32 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' + - paramiko ; extra == 'ssh' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl name: ply version: '3.11' @@ -10156,6 +10372,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 @@ -10205,10 +10431,10 @@ packages: - setuptools ; extra == 'dev' - xmlschema ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl name: pytest-cov - version: 7.0.0 - sha256: 3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861 + version: 7.1.0 + sha256: a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678 requires_dist: - coverage[toml]>=7.10.6 - pluggy>=1.2 @@ -10427,10 +10653,10 @@ packages: requires_dist: - six>=1.5 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' -- pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl name: python-discovery - version: 1.1.2 - sha256: d18edd61b382d62f8bcd004a71ebaabc87df31dbefb30aeed59f4fc6afa005be + version: 1.2.1 + sha256: b6a957b24c1cd79252484d3566d1b49527581d46e789aaf43181005e56201502 requires_dist: - filelock>=3.15.4 - platformdirs>=4.3.6,<5 @@ -10529,6 +10755,14 @@ packages: - pytest-check-links ; extra == 'test' - numpy>=1.14 ; extra == 'test' requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl + name: pywin32 + version: '311' + sha256: 3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503 +- pypi: https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl + name: pywin32 + version: '311' + sha256: 718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d - pypi: https://files.pythonhosted.org/packages/79/c3/3e75075c7f71735f22b66fab0481f2c98e3a4d58cba55cb50ba29114bcf6/pywinpty-3.0.3-cp311-cp311-win_amd64.whl name: pywinpty version: 3.0.3 @@ -10628,6 +10862,13 @@ packages: requires_dist: - cffi ; implementation_name == 'pypy' requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl + name: questionary + version: 2.1.1 + sha256: a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59 + requires_dist: + - prompt-toolkit>=2.0,<4.0 + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl name: radon version: 6.0.1 @@ -10680,18 +10921,24 @@ packages: - rpds-py>=0.7.0 - typing-extensions>=4.4.0 ; python_full_version < '3.13' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl name: requests - version: 2.32.5 - sha256: 2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 + version: 2.33.0 + sha256: 3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b requires_dist: - charset-normalizer>=2,<4 - idna>=2.5,<4 - - urllib3>=1.21.1,<3 - - certifi>=2017.4.17 + - urllib3>=1.26,<3 + - certifi>=2023.5.7 - pysocks>=1.5.6,!=1.5.7 ; extra == 'socks' - - chardet>=3.0.2,<6 ; extra == 'use-chardet-on-py3' - requires_python: '>=3.9' + - chardet>=3.0.2,<8 ; extra == 'use-chardet-on-py3' + - pytest-httpbin==2.1.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-xdist ; extra == 'test' + - pysocks>=1.5.6,!=1.5.7 ; extra == 'test' + - pytest>=3 ; extra == 'test' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl name: returns version: 0.26.0 @@ -10771,25 +11018,25 @@ packages: version: 0.30.0 sha256: e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8 requires_python: '>=3.10' -- 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/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl name: ruff - version: 0.15.5 - sha256: 6edd3792d408ebcf61adabc01822da687579a1a023f297618ac27a5b51ef0080 + version: 0.15.8 + sha256: 04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl name: ruff - version: 0.15.5 - sha256: 821d41c5fa9e19117616c35eaa3f4b75046ec76c65e7ae20a333e9a8696bc7fe + version: 0.15.8 + sha256: 6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3 requires_python: '>=3.7' -- 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/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl name: ruff - version: 0.15.5 - sha256: 89f463f7c8205a9f8dea9d658d59eff49db05f88f89cc3047fb1a02d9f344010 + version: 0.15.8 + sha256: d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: ruff - version: 0.15.5 - sha256: c1cb7169f53c1ddb06e71a9aebd7e98fc0fea936b39afb36d8e86d36ecc2636a + version: 0.15.8 + sha256: 0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1 requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl name: sciline @@ -10808,10 +11055,10 @@ packages: - rich ; extra == 'test' - rich ; extra == 'progress' requires_python: '>=3.11' -- 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/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: scipp - version: 25.12.0 - sha256: 6391dc46739006e1e4eb7f2fcbcdbdd40f11a3cbae53e93b63b5ba32909bd792 + version: 26.3.1 + sha256: 993706e7c31f0317be2db5f528f9142ba67b2e52d7af174fcad195f702e1d6c7 requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -10834,10 +11081,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/18/5c/bc0ca94bff65fe0d206a369b54625f8ec7852dfd1d835174692026f34df9/scipp-25.12.0-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl name: scipp - version: 25.12.0 - sha256: 0285c91b202dea9aeb18f29d73ff135208a19b2068cd30e17ee81fc435b1943c + version: 26.3.1 + sha256: 03c6dbf8936a2ed62587c5abe8ab5266a5098834a0709321ce799bd1328eb3e6 requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -10860,10 +11107,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/4e/b6/ffe0bb67cec66cd450acff599bb07507bbf5ffda1a3a15dd2d8dbe7a6da7/scipp-25.12.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/60/54/5011adb56853caabfd90686c2e543d1e3c76a8ef2755809b7e12e3f3583b/scipp-26.3.1-cp311-cp311-macosx_14_0_arm64.whl name: scipp - version: 25.12.0 - sha256: 9ec0200eeb660965056b27f5f05505a961d8921b78d0a92c04743d1c22ce7203 + version: 26.3.1 + sha256: 67d275fc83b062053df9aa7ce3af4d2205109c2bc3ab22467bcd73ceb0a83df2 requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -10886,10 +11133,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- 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/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl name: scipp - version: 25.12.0 - sha256: e27082f5bd3655f15479ce87be495235b9bcd9b5db654a7219261be0c6117b31 + version: 26.3.1 + sha256: 8dfe8adedb5cba05acaaea15e3b6fe1820ac2f497e87c1e581ba4be9d82c53bb requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -10912,10 +11159,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/94/98/aa2d4b9d28969cc7f62409f9f9fc5b5a853af651255eba03e9bee8546dd9/scipp-25.12.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/81/21/4962b1daddf0422e56c5ed4c41bea1ccb6d2a9ab72b795196835a20969c7/scipp-26.3.1-cp311-cp311-macosx_14_0_x86_64.whl name: scipp - version: 25.12.0 - sha256: e5694bef45299c4813ee2fe863fab600c3b0f5e13e2735072dbbb5cf1804d0e0 + version: 26.3.1 + sha256: 7c90e78fcba1d272df059fc01350c9e18f017aec26369b03def723a3702d763d requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -10938,10 +11185,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/bd/75/6a3786de6645ac2ccd94fbf83c59cc6b929bfa3a89cb62c8cb3be4de0606/scipp-25.12.0-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/d4/06/19ff1efd58b85906149ce83dfddce23252cea5bec7e0fa5f834336cfe836/scipp-26.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: scipp - version: 25.12.0 - sha256: aee5f585232e2a7a664f57bb9695164715271b74896704e7ee8a669dd7b06008 + version: 26.3.1 + sha256: ab5859a24b3150b588dd2c67e68b0c7f07c9444eae501f3b6326d6b4a34cbf10 requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -10964,10 +11211,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/e5/22/75e119e0a200914f88f121cd956e1eb7f72c8ace4b63171f52ba0334d142/scipp-25.12.0-cp313-cp313-macosx_11_0_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/e2/69/1dcb8e967f62759578938db5b29792b82ea8939a2d712e79491fa3e1cf0a/scipp-26.3.1-cp313-cp313-macosx_14_0_x86_64.whl name: scipp - version: 25.12.0 - sha256: 27ebc37f3b7e20c9dca9cf1a0ac53c4ffaea31c02dfc8ba2b5aa008a252cbcba + version: 26.3.1 + sha256: 4c9c8632ba24ce74bd98430a1376310fa5b3fcd2c3467a7e6a484ebb091e915f requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -10990,10 +11237,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/eb/d1/b3cd2733a96a36c54c36889b2cfdd0331c1e5b57fa1757485a22d0ec3142/scipp-25.12.0-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/e6/0d/8882a4c7a5ebe59a46b709e82411d9c730d67250d41a2e11bc4bcd4d431d/scipp-26.3.1-cp311-cp311-win_amd64.whl name: scipp - version: 25.12.0 - sha256: 015db5035749750cf026db56fe537af10f159dc3cd0f51f0ea9f4ecc3a7a5da8 + version: 26.3.1 + sha256: 37877cf07b4f54f224d5465c265d6a1e591d605d0c23dd350a4b48d95c26ab7b requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -11494,11 +11741,38 @@ packages: version: 1.17.0 sha256: 4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' +- pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + name: smmap + version: 5.0.3 + sha256: c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f + requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl name: soupsieve version: 2.8.3 sha256: ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95 requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + name: spdx-headers + version: 1.5.1 + sha256: 73bcb1ed087824b55ccaa497d03d8f0f0b0eaf30e5f0f7d5bbd29d2c4fe78fcf + requires_dist: + - chardet>=5.2.0 + - requests>=2.32.3 + - black>=23.0.0 ; extra == 'dev' + - build>=0.10.0 ; extra == 'dev' + - hatch>=1.9.0 ; extra == 'dev' + - isort>=5.12.0 ; extra == 'dev' + - mypy>=1.0.0 ; extra == 'dev' + - pre-commit>=4.3.0 ; extra == 'dev' + - pytest-cov>=4.0.0 ; extra == 'dev' + - pytest>=7.0.0 ; extra == 'dev' + - ruff>=0.5.0 ; extra == 'dev' + - twine>=4.0.0 ; extra == 'dev' + - types-requests>=2.31.0.6 ; extra == 'dev' + - pytest-cov>=4.0.0 ; extra == 'test' + - pytest-mock>=3.10.0 ; extra == 'test' + - pytest>=7.0.0 ; extra == 'test' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/16/56/a31e8d3c9e8d21100b83bbe1c1f3f7c94db317393a229e193461e5e6d2a4/spglib-2.6.0-cp313-cp313-win_amd64.whl name: spglib version: 2.6.0 @@ -12091,10 +12365,10 @@ packages: purls: [] size: 3526350 timestamp: 1769460339384 -- pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl name: tof - version: 26.1.0 - sha256: 2603a7b94a7296ee503a0edadb314701431808c8ededb5c442334f89633962a5 + version: 26.3.0 + sha256: e89783a072b05fdb53d9e76fbf919dc8935e75e118fdaf17ca5cc33727ef002b requires_dist: - plopp>=23.10.0 - pooch>=1.5.0 @@ -12108,70 +12382,70 @@ packages: version: 6.2.0 sha256: a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl name: tomli - version: 2.4.0 - sha256: d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa + version: 2.4.1 + sha256: 36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: tomli - version: 2.4.0 - sha256: 9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e + version: 2.4.1 + sha256: f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl name: tomli - version: 2.4.0 - sha256: 84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0 + version: 2.4.1 + sha256: eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl name: tomli - version: 2.4.0 - sha256: 3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87 + version: 2.4.1 + sha256: 5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: tomli - version: 2.4.0 - sha256: b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867 + version: 2.4.1 + sha256: 5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl name: tomli - version: 2.4.0 - sha256: 5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9 + version: 2.4.1 + sha256: 8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl name: tomli - version: 2.4.0 - sha256: 1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e + version: 2.4.1 + sha256: 4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl name: tomli - version: 2.4.0 - sha256: 5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76 + version: 2.4.1 + sha256: f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30 requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl name: toolz version: 1.1.0 sha256: 15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl name: tornado - version: 6.5.4 - sha256: e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f + version: 6.5.5 + sha256: 6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl +- pypi: https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl name: tornado - version: 6.5.4 - sha256: d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9 + version: 6.5.5 + sha256: 487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl name: tornado - version: 6.5.4 - sha256: fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc + version: 6.5.5 + sha256: 65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: tornado - version: 6.5.4 - sha256: 2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843 + version: 6.5.5 + sha256: e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5 requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl name: traitlets @@ -12269,10 +12543,6 @@ packages: - python-docs-theme ; extra == 'doc' - uncertainties[arrays,doc,test] ; extra == 'all' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - name: untokenize - version: 0.1.1 - sha256: 3865dbbbb8efb4bb5eaa72f1be7f3e0be00ea8b7f125c69cbd1f5fda926f37a2 - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl name: uri-template version: 1.3.0 @@ -12310,26 +12580,6 @@ packages: - pysocks>=1.5.6,!=1.5.7,<2.0 ; extra == 'socks' - backports-zstd>=1.0.0 ; python_full_version < '3.14' and extra == 'zstd' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/6f/34/2e5cd576d312eb1131b615f49ee95ff6efb740965324843617adae729cf2/uv-0.10.9-py3-none-macosx_10_12_x86_64.whl - name: uv - version: 0.10.9 - sha256: 880dd4cffe4bd184e8871ddf4c7d3c3b042e1f16d2682310644aa8d61eaea3e6 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/79/34/b104c413079874493eed7bf11838b47b697cf1f0ed7e9de374ea37b4e4e0/uv-0.10.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: uv - version: 0.10.9 - sha256: 7c9d6deb30edbc22123be75479f99fb476613eaf38a8034c0e98bba24a344179 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/89/35/684f641de4de2b20db7d2163c735b2bb211e3b3c84c241706d6448e5e868/uv-0.10.9-py3-none-macosx_11_0_arm64.whl - name: uv - version: 0.10.9 - sha256: a7a784254380552398a6baf4149faf5b31a4003275f685c28421cf8197178a08 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/c9/e9/adf7a12136573937d12ac189569e2e90e7fad18b458192083df6986f3013/uv-0.10.9-py3-none-win_amd64.whl - name: uv - version: 0.10.9 - sha256: af79552276d8bd622048ab2d67ec22120a6af64d83963c46b1482218c27b571f - requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl name: validate-pyproject version: '0.25' diff --git a/pixi.toml b/pixi.toml index c0a8608e..2cbf09c5 100644 --- a/pixi.toml +++ b/pixi.toml @@ -5,7 +5,7 @@ # Platform-independent [activation.env] -PYTHONIOENCODING = "utf-8" +PYTHONIOENCODING = 'utf-8' # Platform-specific @@ -27,6 +27,7 @@ PYTHONPATH = "${PIXI_PROJECT_ROOT}/src;%PYTHONPATH%" ########### [workspace] + # Supported platforms for the lock file (pixi.lock) platforms = ['win-64', 'linux-64', 'osx-64', 'osx-arm64'] @@ -34,7 +35,7 @@ platforms = ['win-64', 'linux-64', 'osx-64', 'osx-arm64'] channels = ['conda-forge'] ##################### -# System requirements +# SYSTEM REQUIREMENTS ##################### [system-requirements] @@ -52,34 +53,20 @@ macos = '14.0' # Default feature configuration [dependencies] -gsl = '*' # GNU Scientific Library; required for pdffit2. - -#[target.win-64.dependencies] -#libcblas = '*' # CBLAS library for linear algebra; required for pdffit2. +nodejs = '*' # Required for Prettier (non-Python formatting) +gsl = '*' # GNU Scientific Library; required for diffpy.pdffit2 [pypi-dependencies] # == [feature.default.pypi-dependencies] -pip = '*' # Native package installer -uv = '*' # Package manager -jupyterlab = '*' # Jupyter notebooks -pixi-kernel = '*' # Pixi Jupyter kernel -#easydiffraction = { version = '*', extras = ['all'] } # Main package -easydiffraction = { path = ".", editable = true, extras = ['all'] } - -# Specific features +#pip = '*' # Native package installer +easydiffraction = { path = '.', editable = true, extras = ['dev'] } -# Each feature sets a specific Python version for the environment. +# Specific features: Set specific Python versions -[feature.py311.dependencies] +[feature.py-min.dependencies] python = '3.11.*' - -[feature.py313.dependencies] +[feature.py-max.dependencies] python = '3.13.*' -# This feature installs Node.js for formatting non-Python files with Prettier. - -[feature.nodejs.dependencies] -nodejs = '*' - ############## # ENVIRONMENTS ############## @@ -88,13 +75,12 @@ nodejs = '*' # The `default` feature is always included in all environments. # Additional features can be specified per environment. +py-311-env = { features = ['default', 'py-min'] } +py-313-env = { features = ['default', 'py-max'] } # The `default` environment is always created and includes the `default` feature. # It does not need to be specified explicitly unless non-default features are included. - -default = { features = ['py313', 'nodejs'] } -py311-dev = { features = ['py311', 'nodejs'] } -py313-dev = { features = ['py313', 'nodejs'] } +default = { features = ['default', 'py-max'] } ####### # TASKS @@ -102,59 +88,59 @@ py313-dev = { features = ['py313', 'nodejs'] } [tasks] -## 🧪 Testing Tasks +################## +# 🧪 Testing Tasks +################## + unit-tests = 'python -m pytest tests/unit/ --color=yes -v' integration-tests = 'python -m pytest tests/integration/ --color=yes -n auto -v' -notebook-tests = 'python -m pytest --nbmake tutorials/ --nbmake-timeout=600 --color=yes -n auto -v' script-tests = 'python -m pytest tools/test_scripts.py --color=yes -n auto -v' -extra = 'python -m pytest tests/unit/extra.py -q --tb=no --disable-warnings --color=yes' +notebook-tests = 'python -m pytest --nbmake docs/docs/tutorials/ --nbmake-timeout=600 --color=yes -n auto -v' test = { depends-on = ['unit-tests'] } -# 🧹 Code Quality +########### +# ✔️ Checks +########### -### ✔️ Checks pyproject-check = 'python -m validate_pyproject pyproject.toml' -py-lint-check-pre = "python -m ruff check" -py-lint-check = 'pixi run py-lint-check-pre .' -py-format-check-pre = "python -m ruff format --check" -py-format-check = "pixi run py-format-check-pre ." -nonpy-format-check-pre = "npx prettier --list-different --config=prettierrc.toml" -nonpy-format-check-modified = "pixi run nonpy-format-check-pre $(git diff --diff-filter=d --name-only HEAD | grep -E '\\.(json|ya?ml|toml|md|css|html)$' || echo .)" -nonpy-format-check = "pixi run nonpy-format-check-pre ." -notebook-format-check = 'nbqa ruff tutorials/' -docs-format-check = 'docformatter src/ tutorials/ --check' -# Run like a real commit: staged files only (almost) -pre-commit-check = 'pre-commit run --hook-stage pre-commit' -# CI check: lint/format everything -pre-commit-check-all = 'pre-commit run --all-files --hook-stage pre-commit' -# Pre-push check: lint/format everything -pre-push-check = 'pre-commit run --all-files --hook-stage pre-push' - -check = { depends-on = [ - 'docs-format-check', - 'py-format-check', - 'py-lint-check', - 'nonpy-format-check-modified', -] } +param-docstring-check = 'python tools/param_consistency.py src/ --check' +docstring-lint-check = 'pydoclint --quiet 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 .' +nonpy-format-check-modified = 'python tools/nonpy_prettier_modified.py' + +check = 'pre-commit run --hook-stage manual --all-files' + +########## +# 🛠️ Fixes +########## -### 🛠️ Fixes -py-lint-fix = 'pixi run py-lint-check --fix' -#py-format-fix = "python -m ruff format $(git diff --cached --name-only -- '*.py')" -py-format-fix = "python -m ruff format" -nonpy-format-fix = 'pixi run nonpy-format-check --write' -nonpy-format-fix-modified = "pixi run nonpy-format-check-modified --write" -notebook-format-fix = 'pixi run notebook-format-check --fix' -docs-format-fix = 'docformatter src/ tutorials/ --in-place' +param-docstring-fix = 'python tools/param_consistency.py src/ --fix' +docstring-format-fix = 'format-docstring src/' +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 = 'echo "✅ All auto-formatting steps completed successfully!"' fix = { depends-on = [ + #'param-docstring-fix', # ED only + 'docstring-format-fix', 'py-format-fix', - 'docs-format-fix', 'py-lint-fix', 'nonpy-format-fix', + 'notebook-lint-fix', + 'success-message', ] } -## 🧮 Code Complexity +#################### +# 🧮 Code Complexity +#################### + complexity-check = 'radon cc -s src/' complexity-check-json = 'radon cc -s -j src/' maintainability-check = 'radon mi src/' @@ -162,62 +148,127 @@ maintainability-check-json = 'radon mi -j src/' raw-metrics = 'radon raw -s src/' raw-metrics-json = 'radon raw -s -j src/' -## 📊 Coverage +############# +# 📊 Coverage +############# + unit-tests-coverage = 'pixi run unit-tests --cov=src/easydiffraction --cov-report=term-missing' integration-tests-coverage = 'pixi run integration-tests --cov=src/easydiffraction --cov-report=term-missing' -docstring-coverage = 'interrogate -c pyproject.toml src/' +docstring-coverage = 'interrogate -c pyproject.toml src/easydiffraction' -cov = { depends-on = ['docstring-coverage', 'integration-tests-coverage'] } +cov = { depends-on = [ + 'docstring-coverage', + 'unit-tests-coverage', + 'integration-tests-coverage', +] } -## 📓 Notebook Management -notebook-convert = 'jupytext tutorials/*.py --from py:percent --to ipynb' -notebook-strip = 'nbstripout tutorials/*.ipynb' +######################## +# 📓 Notebook Management +######################## + +notebook-convert = 'jupytext docs/docs/tutorials/*.py --from py:percent --to ipynb' +notebook-strip = 'nbstripout docs/docs/tutorials/*.ipynb' notebook-tweak = 'python tools/tweak_notebooks.py tutorials/' -notebook-clean = 'rm -f tutorials/*.ipynb' -notebook-exec = 'python -m pytest --nbmake tutorials/ --nbmake-timeout=600 --overwrite --color=yes -n auto -v' +notebook-exec = 'python -m pytest --nbmake docs/docs/tutorials/ --nbmake-timeout=600 --overwrite --color=yes -n auto -v' notebook-prepare = { depends-on = [ - 'notebook-convert', + #'notebook-convert', 'notebook-strip', - 'notebook-tweak', + #'notebook-tweak', +] } + +######################## +# 📚 Documentation Tasks +######################## + +docs-vars = "JUPYTER_PLATFORM_DIRS=1 PYTHONWARNINGS='ignore::RuntimeWarning'" +docs-pre = 'pixi run docs-vars python -m mkdocs' +docs-serve = 'pixi run docs-pre serve -f docs/mkdocs.yml' +docs-serve-dirty = 'pixi run docs-serve --dirty' +docs-build = 'pixi run docs-pre build -f docs/mkdocs.yml' +docs-build-local = 'pixi run docs-build --no-directory-urls' + +docs-deploy-pre = 'mike deploy -F docs/mkdocs.yml --push --branch gh-pages --update-aliases --alias-type redirect' +docs-set-default-pre = 'mike set-default -F docs/mkdocs.yml --push --branch gh-pages' + +docs-update-assets = 'python tools/update_docs_assets.py' + +############################## +# 📦 Template Management Tasks +############################## + +copier-copy = 'copier copy gh:easyscience/templates . --data-file ../diffraction/.copier-answers.yml --data template_type=lib' +copier-recopy = 'copier recopy --data-file ../diffraction/.copier-answers.yml --data template_type=lib' +copier-update = 'copier update --data-file ../diffraction/.copier-answers.yml --data template_type=lib' + +##################### +# 🪝 Pre-commit Hooks +##################### + +pre-commit-clean = 'pre-commit clean' +pre-commit-install = 'pre-commit install --hook-type pre-commit --hook-type pre-push --overwrite' +pre-commit-uninstall = 'pre-commit uninstall --hook-type pre-commit --hook-type pre-push' +pre-commit-setup = { depends-on = [ + 'pre-commit-clean', + 'pre-commit-uninstall', + 'pre-commit-install', ] } -## 📚 Documentation Tasks -docs-assets = 'tools/add_assets_to_docs.sh' -docs-notebooks = 'mv tutorials/*.* docs/tutorials/' # *.py, *.ipynb, index.json -docs-config = 'python tools/create_mkdocs_yml.py' -docs-deploy = 'mike deploy --push --branch gh-pages --update-aliases --alias-type redirect' -docs-set-default = 'mike set-default --push --branch gh-pages' -docs-serve = "JUPYTER_PLATFORM_DIRS=1 PYTHONWARNINGS='ignore::RuntimeWarning' python -m mkdocs serve --dirty" -docs-build = "JUPYTER_PLATFORM_DIRS=1 PYTHONWARNINGS='ignore::RuntimeWarning' python -m mkdocs build" -docs-local = "pixi run docs-build --no-directory-urls" -docs-clean = 'tools/cleanup_docs.sh' -docs-setup = { depends-on = [ - 'docs-config', - 'docs-assets', - 'notebook-prepare', - 'docs-notebooks', +################# +# 🐙️ GitHub Tasks +################# + +repo-wiki = 'gh api -X PATCH repos/easyscience/diffraction-lib -f has_wiki=false' +repo-discussions = 'gh api -X PATCH repos/easyscience/diffraction-lib -f has_discussions=true' +repo-description = "gh api -X PATCH repos/easyscience/diffraction-lib -f description='Diffraction data analysis'" +repo-homepage = "gh api -X PATCH repos/easyscience/diffraction-lib -f homepage='https://easyscience.github.io/diffraction-lib'" +repo-config = { depends-on = [ + 'repo-wiki', + 'repo-discussions', + 'repo-description', + 'repo-homepage', ] } -## 🚀 Development & Build Tasks +master-protection = 'gh api -X POST repos/easyscience/diffraction-lib/rulesets --input .github/configs/rulesets-master.json' +develop-protection = 'gh api -X POST repos/easyscience/diffraction-lib/rulesets --input .github/configs/rulesets-develop.json' +gh-pages-protection = 'gh api -X POST repos/easyscience/diffraction-lib/rulesets --input .github/configs/rulesets-gh-pages.json' +branch-protection = { depends-on = [ + 'master-protection', + 'develop-protection', + 'gh-pages-protection', +] } + +pages-deployment = 'gh api -X POST repos/easyscience/diffraction-lib/pages --input .github/configs/pages-deployment.json' + +github-labels = 'python tools/update_github_labels.py' + +######################### +# ⚖️ SPDX License Headers +######################### + +license-remove = 'python tools/license_headers.py remove src/ tests/ --exclude-from-pyproject-toml tool.ruff.exclude' +license-add = 'python tools/license_headers.py add src/ tests/ --exclude-from-pyproject-toml tool.ruff.exclude' +license-check = 'python tools/license_headers.py check src/ tests/ --exclude-from-pyproject-toml tool.ruff.exclude' + +#################################### +# 🚀 Other Development & Build Tasks +#################################### + +default-build = 'python -m build' dist-build = 'python -m build --wheel --outdir dist' -spdx-update = 'python tools/update_spdx.py' -#dev-install = 'uv pip install --requirements pyproject.toml --extra all' -#dev-install = "python -m uv pip install --force-reinstall --editable '.[all]'" + npm-config = 'npm config set registry https://registry.npmjs.org/' prettier-install = 'npm install --no-save --no-audit --no-fund prettier prettier-plugin-toml' -pre-commit-setup = 'pre-commit clean && pre-commit uninstall && pre-commit install --hook-type pre-commit --hook-type pre-push --overwrite' -pre-commit-update = 'pre-commit autoupdate' + clean-pycache = "find . -type d -name '__pycache__' -prune -exec rm -rf '{}' +" -dev = { depends-on = [ - #'dev-install', +post-install = { depends-on = [ 'npm-config', 'prettier-install', #'pre-commit-setup', ] } -wheel = { depends-on = ['npm-config', 'prettier-install'] } - -## 🔗 Main Package Shortcut +########################## +# 🔗 Main Package Shortcut +########################## easydiffraction = 'python -m easydiffraction' diff --git a/prettierrc.toml b/prettierrc.toml index 6874d28d..b98c86eb 100644 --- a/prettierrc.toml +++ b/prettierrc.toml @@ -1,11 +1,22 @@ +plugins = [ + "prettier-plugin-toml", # use the TOML plugin +] + endOfLine = 'lf' # change line endings to LF -printWidth = 80 # wrap Markdown files at 80 characters proseWrap = 'always' # change wrapping in Markdown files semi = false # remove semicolons singleQuote = true # use single quotes instead of double quotes tabWidth = 2 # change tab width to 2 spaces useTabs = false # use spaces instead of tabs -plugins = [ - 'prettier-plugin-toml', # use the TOML plugin -] +printWidth = 79 # wrap lines at 79 characters + +[[overrides]] +files = ["*.md"] +[overrides.options] +printWidth = 72 # wrap Markdown files at 72 characters + +[[overrides]] +files = ["*.yml", "*.yaml"] +[overrides.options] +printWidth = 88 # wrap YAML files at 88 characters diff --git a/pycrysfml.md b/pycrysfml.md index af681478..de9bc4d0 100644 --- a/pycrysfml.md +++ b/pycrysfml.md @@ -12,7 +12,8 @@ ```bash otool -L .venv/lib/python3.12/site-packages/pycrysfml/crysfml08lib.so ``` -- If the library is linked to the wrong Python version, you can fix it with: +- If the library is linked to the wrong Python version, you can fix it + with: ```bash install_name_tool -change `python3-config --prefix`/Python `python3-config --prefix`/lib/libpython3.12.dylib .venv/lib/python3.12/site-packages/pycrysfml/crysfml08lib.so ``` diff --git a/pyproject.toml b/pyproject.toml index dd645c71..16d18621 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,9 +6,10 @@ name = 'easydiffraction' dynamic = ['version'] # Use versioningit to manage the version description = 'Diffraction data analysis' -authors = [{ name = 'EasyDiffraction contributors' }] +authors = [{ name = 'EasyScience contributors' }] readme = 'README.md' -license = { file = 'LICENSE' } +license = 'BSD-3-Clause' +license-files = ['LICENSE'] classifiers = [ 'Intended Audience :: Science/Research', 'Topic :: Scientific/Engineering', @@ -20,7 +21,7 @@ classifiers = [ 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', ] -requires-python = '>=3.11,<3.14' +requires-python = '>=3.11' dependencies = [ 'essdiffraction', # ESS-specific diffraction library 'numpy', # Numerical computing library @@ -43,29 +44,36 @@ dependencies = [ 'diffpy.utils', # Utilities for PDF calculations 'uncertainties', # Propagation of uncertainties 'typeguard', # Runtime type checking + 'darkdetect', # Detecting dark mode (system-level) + 'pandas', # Displaying tables in Jupyter notebooks + 'plotly', # Interactive plots + 'py3Dmol', # Visualisation of crystal structures + 'jupyterlab', # Jupyter notebooks + 'pixi-kernel', # Pixi Jupyter kernel ] [project.optional-dependencies] dev = [ - 'build', # Building the package - 'pre-commit', # Pre-commit hooks - 'jinja2', # Templating - 'nbmake', # Building notebooks - 'nbstripout', # Strip output from notebooks - 'nbqa', # Linting and formatting notebooks - 'pytest', # Testing - 'pytest-cov', # Test coverage - 'pytest-xdist', # Enable parallel testing - 'ruff', # Linting and formatting code - 'radon', # Code complexity and maintainability - 'validate-pyproject[all]', # Validate pyproject.toml - '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 -] -docs = [ + 'GitPython', # Interact with Git repositories + 'build', # Building the package + 'pre-commit', # Pre-commit hooks + 'jinja2', # Templating + 'nbmake', # Building notebooks + 'nbstripout', # Strip output from notebooks + 'nbqa', # Linting and formatting notebooks + 'pytest', # Testing + 'pytest-cov', # Test coverage + 'pytest-xdist', # Enable parallel testing + 'ruff', # Linting and formatting code + 'radon', # Code complexity and maintainability + 'validate-pyproject[all]', # Validate pyproject.toml + 'versioningit', # Automatic versioning from git tags + 'jupytext', # Jupyter notebook text format support + 'jupyterquiz', # Quizzes in Jupyter notebooks + 'pydoclint', # Docstring linter + 'format-docstring', # Docstring formatter + 'interrogate', # Docstring coverage checker + 'copier', # Template management 'mike', # MkDocs: Versioned documentation support 'mkdocs', # Static site generator 'mkdocs-material', # Documentation framework on top of MkDocs @@ -75,35 +83,20 @@ docs = [ 'mkdocs-markdownextradata-plugin', # MkDocs: Markdown extra data support, such as global variables 'mkdocstrings-python', # MkDocs: Python docstring support 'pyyaml', # YAML parser -] -visualization = [ - 'darkdetect', # Detecting dark mode (system-level) - 'pandas', # Displaying tables in Jupyter notebooks - 'plotly', # Interactive plots - 'py3Dmol', # Visualisation of crystal structures -] -all = [ - 'easydiffraction[dev]', - 'easydiffraction[docs]', - 'easydiffraction[visualization]', + 'spdx-headers', # SPDX license header validation ] [project.urls] -homepage = 'https://easydiffraction.org' -documentation = 'https://docs.easydiffraction.org/lib' -source = 'https://github.com/easyscience/diffraction-lib' -tracker = 'https://github.com/easyscience/diffraction-lib/issues' +Homepage = 'https://easydiffraction.org' +Documentation = 'https://easyscience.github.io/diffraction-lib' +'Release Notes' = 'https://github.com/easyscience/diffraction-lib/releases' +'Source Code' = 'https://github.com/easyscience/diffraction-lib' +'Issue Tracker' = 'https://github.com/easyscience/diffraction-lib/issues' ############################ # 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'] @@ -112,6 +105,9 @@ requires = ['hatchling', 'versioningit'] # Configuration for hatchling ############################# +# 'hatch' -- Build system for Python +# https://hatch.pypa.io/ + [tool.hatch.build.targets.wheel] packages = ['src/easydiffraction'] @@ -125,6 +121,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 @@ -144,43 +143,45 @@ method = 'git' match = ['v*'] default-tag = 'v999.0.0' -################################ -# Configuration for docformatter -################################ - -# 'docformatter' -- Code formatter for docstrings -# https://docformatter.readthedocs.io/en/latest/ - -[tool.docformatter] -recursive = true -wrap-summaries = 72 -wrap-descriptions = 72 -close-quotes-on-newline = true - ################################ # Configuration for interrogate ################################ -# 'interrogate' -- Check for missing docstrings +# 'interrogate' -- Docstring coverage checker # https://interrogate.readthedocs.io/en/latest/ [tool.interrogate] -fail-under = 35 # Temporarily reduce to allow gradual improvement +fail-under = 35 # Minimum docstring coverage percentage to pass verbose = 1 -exclude = ["src/**/__init__.py"] +#exclude = ['src/**/__init__.py'] ####################################### # 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/easydiffraction'] # Limit coverage to the source code directory +branch = true # Measure branch coverage as well +source = ['src'] # Limit coverage to the source code directory [tool.coverage.report] -show_missing = true # Show missing lines -skip_covered = true # Skip files with 100% coverage in the report -fail_under = 60 # Temporarily reduce to allow gradual improvement +show_missing = true # Show missing lines +skip_covered = false # Skip files with 100% coverage in the report +fail_under = 60 # 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)'] +testpaths = ['tests'] ######################## # Configuration for ruff @@ -190,107 +191,197 @@ fail_under = 60 # Temporarily reduce to allow gradual improvement # https://docs.astral.sh/ruff/rules/ [tool.ruff] -# Temporarily exclude some directories until we have improved the code quality there -exclude = ['tmp', 'tests/unit'] +exclude = [ + 'tmp', + # Vendored jupyter_dark_detect: keep as-is from upstream for easy updates + # https://github.com/OpenMined/jupyter-dark-detect/tree/main/jupyter_dark_detect + 'src/easydiffraction/utils/_vendored/jupyter_dark_detect', +] indent-width = 4 -line-length = 99 -# Enable new rules that are not yet stable, like DOC -preview = true +line-length = 99 # See also `max-line-length` in [tool.ruff.lint.pycodestyle] +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 +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) + +# Linting rules to use with Ruff -# https://docs.astral.sh/ruff/rules/ [tool.ruff.lint] select = [ - 'ARG', # Argument-related issues (e.g., unused arguments) - 'B', # Bugbear-specific checks (e.g., likely bugs, bad patterns) - 'C', # Complexity-related issues (e.g., high McCabe complexity) - 'D', # Docstring formatting issues (old rules) - 'DOC', # Docstring formatting issues (new rules) - 'DTZ', # Datetime timezone issues (e.g., inconsistent timezone formats) - 'E', # General PEP 8 style errors - 'F', # Pyflakes-specific checks (e.g., unused variables, imports) - 'FLY', # Flynt-specific checks (e.g., enforcing f-strings) - 'G', # Type annotation issues (e.g., missing or incorrect type hints) - 'I', # Import sorting issues (e.g., unsorted imports) - 'ICN', # Import conventions (e.g., enforce aliasing like import numpy as np) - 'N', # Naming convention issues (e.g., variable names, function names) - 'NPY', # NumPy-specific checks (e.g., array operations, broadcasting) - 'PGH', # Misc text patterns checks - 'PTH', # Encourages using pathlib over os.path - 'S', # Security-related issues (e.g., use of insecure functions or libraries) - 'SIM', # Simplification issues (e.g., redundant code, unnecessary constructs) - 'TCH', # Type checking issues (e.g., incompatible types, missing type annotations) - 'TID252', # Enforces absolute imports over relative imports - 'W', # General PEP 8 warnings (e.g., lines too long, trailing whitespace) - #'ANN', # Missing or incorrect type annotations - #'COM', # Comment formatting issues - #'PERF', # Performance-related issues (e.g., inefficient code patterns) - #'PIE', # Potentially problematic idioms and errors - #'PL', # PyLint-specific checks (e.g., code smells, potential errors) - #'PT', # Pytest-related issues - #'RET', # Return statement issues (e.g., inconsistent returns) - #'RUF', # Ruff-specific checks (e.g., enforcing best practices) - #'SLF', # Self argument-related issues (e.g., missing or misused self) - #'T20', # Flake8-print-specific checks (e.g., print statements left in code) - #'TD', # Type definition issues (e.g., incorrect or missing type definitions) - #'TRY', # Tryceratops Try/Except-related issues (e.g., broad exceptions, empty except blocks) - #'UP', # Pyupgrade-specific checks + # Various rules + #'C90', # https://docs.astral.sh/ruff/rules/#mccabe-c90 + 'D', # https://docs.astral.sh/ruff/rules/#pydocstyle-d + '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 ] -# Temporarily disable some docstring checks until we have improved the docstring coverage + +# Exceptions to the linting rules + +# Ignore specific rules globally ignore = [ - 'C408', # Ignore: Unnecessary `dict()` call - 'C416', # Ignore: Unnecessary list comprehension - 'D100', # Ignore: Missing docstring in public module - 'D101', # Ignore: Missing docstring in class - 'D102', # Ignore: Missing docstring in public method - 'D103', # Ignore: Missing docstring in public function - 'D104', # Ignore: Missing docstring in public package - 'D105', # Ignore: Missing docstring in magic method - 'D107', # Ignore: Missing docstring in __init__ - 'D205', # Ignore: 1 blank line required between summary and description - 'DOC201', # Ignore: `return` is not documented in docstring - 'DOC501', # Ignore: Raised exception `ValueError` missing from docstring - 'DOC502', # Ignore: Raised exception is not explicitly raised: `TypeError` - 'DTZ005', # Ignore: `datetime.datetime.now()` called without a `tz` argument + 'COM812', # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ + # The following is replaced by 'D'/[tool.ruff.lint.pydocstyle] and [tool.pydoclint] + 'DOC', # https://docs.astral.sh/ruff/rules/#pydoclint-doc + # Disable, as [tool.format_docstring] split one-line docstrings into the canonical multi-line layout + 'D200', # https://docs.astral.sh/ruff/rules/unnecessary-multiline-docstring/ + # Temporary: + 'D100', # https://docs.astral.sh/ruff/rules/undocumented-public-module/#undocumented-publi-module-d100 + 'D104', # https://docs.astral.sh/ruff/rules/undocumented-public-package/#undocumented-public-package-d104 + 'DTZ005', # https://docs.astral.sh/ruff/rules/call-datetime-now-without-tzinfo/#call-datetime-now-without-tzinfo-dtz005 ] -# Temporarily increase McCabe complexity limit to 19 to allow -# refactoring in smaller steps. -[tool.ruff.lint.mccabe] -max-complexity = 37 # 19 # default is 10 +# Ignore specific rules in certain files or directories +[tool.ruff.lint.per-file-ignores] +'*/__init__.py' = [ + 'F401', # re-exports are intentional in __init__.py +] +'tests/**' = [ + 'ANN', # https://docs.astral.sh/ruff/rules/#flake8-annotations-ann + 'D', # https://docs.astral.sh/ruff/rules/#pydocstyle-d + '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/ + # Temporary: + 'ARG001', + 'ARG002', + 'ARG004', + 'ARG005', + 'B011', + 'B017', + 'B018', + 'E501', + 'E741', + 'F841', + 'I001', + 'N801', + 'N805', + 'N812', + 'PLC', + 'PLE', + 'PLR', + 'PLW', + 'SIM117', + 'W505', +] +'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.per-file-ignores] -'*test_*.py' = ['S101'] # allow asserts in test files -'conftest.py' = ['S101'] # allow asserts in test files -'*/__init__.py' = ['F401'] # re-exports are intentional in __init__.py -# Vendored jupyter_dark_detect: keep as-is from upstream for easy updates -# https://github.com/OpenMined/jupyter-dark-detect/tree/main/jupyter_dark_detect -'src/easydiffraction/utils/_vendored/jupyter_dark_detect/*' = [ - 'S110', # try-except-pass - 'S112', # try-except-continue - 'S404', # subprocess module - 'S607', # partial executable path - 'TID252', # relative imports - 'W291', # trailing whitespace (in JS strings) - 'W505', # doc line too long - 'E501', # line too long (in JS strings) -] +[tool.ruff.lint.mccabe] +# Cyclomatic complexity threshold (default is 10) +max-complexity = 10 [tool.ruff.lint.pycodestyle] -max-line-length = 100 #99# https://peps.python.org/pep-0008/#maximum-line-length -max-doc-length = 72 # https://peps.python.org/pep-0008/#maximum-line-length +# PEP 8 line length guidance: +# https://peps.python.org/pep-0008/#maximum-line-length +# Use 99 characters as the project-wide maximum for regular code lines. +# Use 72 characters for docstrings. +max-line-length = 99 # See also `line-length` in [tool.ruff] +max-doc-length = 72 [tool.ruff.lint.pydocstyle] -convention = 'google' +convention = 'numpy' -[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 -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) +############################# +# 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] +#exclude = '\.' # Temporarily disable pydoclint until we are ready +exclude = 'src/easydiffraction/utils/_vendored/jupyter_dark_detect' # ED only +style = 'numpy' +check-style-mismatch = true +check-arg-defaults = true +allow-init-docstring = true + +#################################### +# Configuration for format-docstring +#################################### + +# 'format-docstring' -- Code formatter for docstrings +# https://github.com/jsh9/format-docstring + +[tool.format_docstring] +#exclude = '\.' # Temporarily disable format-docstring until we are ready +exclude = 'src/easydiffraction/utils/_vendored/jupyter_dark_detect' # ED only +docstring_style = 'numpy' +line_length = 72 +fix_rst_backticks = true +verbose = 'default' diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 907883e8..00000000 --- a/pytest.ini +++ /dev/null @@ -1,13 +0,0 @@ -[pytest] -addopts = --import-mode=importlib -markers = - fast: mark test as fast (should be run on every push) - integration: mark test as integration (slow; opt-in) -testpaths = - tests -filterwarnings = - ignore::DeprecationWarning:cryspy\. - ignore:.*scipy\.misc is deprecated.*:DeprecationWarning - # Suppress expected UserWarnings emitted during tutorial list fetching in tests - ignore:Falling back to latest release info\...:UserWarning:easydiffraction\.utils\.logging - ignore:'tutorials\.zip' not found in the release\.:UserWarning:easydiffraction\.utils\.logging diff --git a/src/easydiffraction/__init__.py b/src/easydiffraction/__init__.py index d15d6682..e2fb3c4e 100644 --- a/src/easydiffraction/__init__.py +++ b/src/easydiffraction/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory diff --git a/src/easydiffraction/__main__.py b/src/easydiffraction/__main__.py index c850b1fa..1a058dd6 100644 --- a/src/easydiffraction/__main__.py +++ b/src/easydiffraction/__main__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import sys @@ -25,7 +25,7 @@ def main( help='Show easydiffraction version and exit.', is_eager=True, ), -): +) -> None: """EasyDiffraction command-line interface.""" if version: ed.show_version() @@ -38,7 +38,7 @@ def main( @app.command('list-tutorials') -def list_tutorials(): +def list_tutorials() -> None: """List available tutorial notebooks.""" ed.list_tutorials() @@ -58,7 +58,7 @@ def download_tutorial( '-o', help='Overwrite existing file if present.', ), -): +) -> None: """Download a specific tutorial notebook by ID.""" ed.download_tutorial(id=id, destination=destination, overwrite=overwrite) @@ -77,7 +77,7 @@ def download_all_tutorials( '-o', help='Overwrite existing files if present.', ), -): +) -> None: """Download all available tutorial notebooks.""" ed.download_all_tutorials(destination=destination, overwrite=overwrite) diff --git a/src/easydiffraction/analysis/__init__.py b/src/easydiffraction/analysis/__init__.py index 429f2648..78150ea5 100644 --- a/src/easydiffraction/analysis/__init__.py +++ b/src/easydiffraction/analysis/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index bf403247..874017cb 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from typing import List @@ -27,31 +27,27 @@ class Analysis: - """High-level orchestration of analysis tasks for a Project. + """ + High-level orchestration of analysis tasks for a Project. This class wires calculators and minimizers, exposes a compact interface for parameters, constraints and results, and coordinates computations across the project's structures and experiments. Typical usage: - - Display or filter parameters to fit. - Select a calculator/minimizer implementation. - Calculate patterns and run single or joint fits. - - Attributes: - project: The parent Project object. - aliases: A registry of human-friendly aliases for parameters. - constraints: Symbolic constraints between parameters. - calculator: Active calculator used for computations. - fitter: Active fitter/minimizer driver. """ - def __init__(self, project) -> None: - """Create a new Analysis instance bound to a project. + def __init__(self, project: object) -> None: + """ + Create a new Analysis instance bound to a project. - Args: - project: The project that owns models and experiments. + Parameters + ---------- + project : object + The project that owns models and experiments. """ self.project = project self._aliases_type: str = AliasesFactory.default_tag() @@ -136,10 +132,13 @@ def aliases_type(self) -> str: @aliases_type.setter def aliases_type(self, new_type: str) -> None: - """Switch to a different aliases collection type. + """ + Switch to a different aliases collection type. - Args: - new_type: Aliases tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Aliases tag (e.g. ``'default'``). """ supported_tags = AliasesFactory.supported_tags() if new_type not in supported_tags: @@ -174,10 +173,13 @@ def constraints_type(self) -> str: @constraints_type.setter def constraints_type(self, new_type: str) -> None: - """Switch to a different constraints collection type. + """ + Switch to a different constraints collection type. - Args: - new_type: Constraints tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Constraints tag (e.g. ``'default'``). """ supported_tags = ConstraintsFactory.supported_tags() if new_type not in supported_tags: @@ -205,12 +207,17 @@ def _get_params_as_dataframe( self, params: List[Union[NumericDescriptor, Parameter]], ) -> pd.DataFrame: - """Convert a list of parameters to a DataFrame. + """ + Convert a list of parameters to a DataFrame. - Args: - params: List of DescriptorFloat or Parameter objects. + Parameters + ---------- + params : List[Union[NumericDescriptor, Parameter]] + List of DescriptorFloat or Parameter objects. - Returns: + Returns + ------- + pd.DataFrame A pandas DataFrame containing parameter information. """ records = [] @@ -246,9 +253,7 @@ def _get_params_as_dataframe( return df def show_all_params(self) -> None: - """Print a table with all parameters for structures and - experiments. - """ + """Print all parameters for structures and experiments.""" structures_params = self.project.structures.parameters experiments_params = self.project.experiments.parameters @@ -278,9 +283,7 @@ def show_all_params(self) -> None: tabler.render(filtered_df) def show_fittable_params(self) -> None: - """Print a table with parameters that can be included in - fitting. - """ + """Print all fittable parameters.""" structures_params = self.project.structures.fittable_parameters experiments_params = self.project.experiments.fittable_parameters @@ -312,9 +315,7 @@ def show_fittable_params(self) -> None: tabler.render(filtered_df) def show_free_params(self) -> None: - """Print a table with only currently-free (varying) - parameters. - """ + """Print only currently free (varying) parameters.""" structures_params = self.project.structures.free_parameters experiments_params = self.project.experiments.free_parameters free_params = structures_params + experiments_params @@ -345,7 +346,8 @@ def show_free_params(self) -> None: tabler.render(filtered_df) def how_to_access_parameters(self) -> None: - """Show Python access paths for all parameters. + """ + Show Python access paths for all parameters. The output explains how to reference specific parameters in code. @@ -409,7 +411,8 @@ def how_to_access_parameters(self) -> None: ) def show_parameter_cif_uids(self) -> None: - """Show CIF unique IDs for all parameters. + """ + Show CIF unique IDs for all parameters. The output explains which unique identifiers are used when creating CIF-based constraints. @@ -472,9 +475,7 @@ def show_current_minimizer(self) -> None: @staticmethod def show_available_minimizers() -> None: - """Print a table of available minimizer drivers on this - system. - """ + """Print available minimizer drivers on this system.""" MinimizerFactory.show_supported() @property @@ -484,10 +485,13 @@ def current_minimizer(self) -> Optional[str]: @current_minimizer.setter def current_minimizer(self, selection: str) -> None: - """Switch to a different minimizer implementation. + """ + Switch to a different minimizer implementation. - Args: - selection: Minimizer selection string, e.g. 'lmfit'. + Parameters + ---------- + selection : str + Minimizer selection string, e.g. 'lmfit'. """ self.fitter = Fitter(selection) console.paragraph('Current minimizer changed to') @@ -498,7 +502,7 @@ def current_minimizer(self, selection: str) -> None: # ------------------------------------------------------------------ @property - def fit_mode(self): + def fit_mode(self) -> object: """Fit-mode category item holding the active strategy.""" return self._fit_mode @@ -509,10 +513,13 @@ def fit_mode_type(self) -> str: @fit_mode_type.setter def fit_mode_type(self, new_type: str) -> None: - """Switch to a different fit-mode category type. + """ + Switch to a different fit-mode category type. - Args: - new_type: Fit-mode tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Fit-mode tag (e.g. ``'default'``). """ supported_tags = FitModeFactory.supported_tags() if new_type not in supported_tags: @@ -541,7 +548,7 @@ def show_current_fit_mode_type(self) -> None: # ------------------------------------------------------------------ @property - def joint_fit_experiments(self): + def joint_fit_experiments(self) -> object: """Per-experiment weight collection for joint fitting.""" return self._joint_fit_experiments @@ -573,10 +580,8 @@ def show_constraints(self) -> None: columns_data=rows, ) - def apply_constraints(self): - """Apply the currently defined constraints to the active - project. - """ + def apply_constraints(self) -> None: + """Apply currently defined constraints to the project.""" if not self.constraints._items: log.warning('No constraints defined.') return @@ -585,27 +590,27 @@ def apply_constraints(self): self.constraints_handler.set_constraints(self.constraints) self.constraints_handler.apply() - def fit(self): - """Execute fitting using the selected mode, calculator and - minimizer. + def fit(self) -> None: + """ + Execute fitting for all experiments. This method performs the optimization but does not display results automatically. Call :meth:`show_fit_results` after fitting to see a summary of the fit quality and parameter values. - In 'single' mode, fits each experiment independently. In - 'joint' mode, performs a simultaneous fit across experiments - with weights. + In 'single' mode, fits each experiment independently. In 'joint' + mode, performs a simultaneous fit across experiments with + weights. Sets :attr:`fit_results` on success, which can be accessed - programmatically - (e.g., ``analysis.fit_results.reduced_chi_square``). + programmatically (e.g., + ``analysis.fit_results.reduced_chi_square``). Example:: - project.analysis.fit() - project.analysis.show_fit_results() # Display results + project.analysis.fit() project.analysis.show_fit_results() # + Display results """ structures = self.project.structures if not structures: @@ -659,7 +664,8 @@ def fit(self): self.fit_results = self.fitter.results def show_fit_results(self) -> None: - """Display a summary of the fit results. + """ + Display a summary of the fit results. Renders the fit quality metrics (reduced χ², R-factors) and a table of fitted parameters with their starting values, final @@ -670,8 +676,7 @@ def show_fit_results(self) -> None: Example:: - project.analysis.fit() - project.analysis.show_fit_results() + project.analysis.fit() project.analysis.show_fit_results() """ if not hasattr(self, 'fit_results') or self.fit_results is None: log.warning('No fit results available. Run fit() first.') @@ -682,14 +687,17 @@ def show_fit_results(self) -> None: self.fitter._process_fit_results(structures, experiments) - def _update_categories(self, called_by_minimizer=False) -> None: - """Update all categories owned by Analysis. + def _update_categories(self, called_by_minimizer: bool = False) -> None: + """ + Update all categories owned by Analysis. This ensures aliases and constraints are up-to-date before serialization or after parameter changes. - Args: - called_by_minimizer: Whether this is called during fitting. + Parameters + ---------- + called_by_minimizer : bool, default=False + Whether this is called during fitting. """ # Apply constraints to sync dependent parameters if self.constraints._items: @@ -701,10 +709,13 @@ def _update_categories(self, called_by_minimizer=False) -> None: if hasattr(category, '_update'): category._update(called_by_minimizer=called_by_minimizer) - def as_cif(self): - """Serialize the analysis section to a CIF string. + def as_cif(self) -> str: + """ + Serialize the analysis section to a CIF string. - Returns: + Returns + ------- + str The analysis section represented as a CIF document string. """ from easydiffraction.io.cif.serialize import analysis_to_cif @@ -713,9 +724,7 @@ def as_cif(self): return analysis_to_cif(self) def show_as_cif(self) -> None: - """Render the analysis section as CIF in a formatted console - view. - """ + """Render the analysis section as CIF in console.""" cif_text: str = self.as_cif() paragraph_title: str = 'Analysis 🧮 info as cif' console.paragraph(paragraph_title) diff --git a/src/easydiffraction/analysis/calculators/__init__.py b/src/easydiffraction/analysis/calculators/__init__.py index 5874b1a4..38bd1aa0 100644 --- a/src/easydiffraction/analysis/calculators/__init__.py +++ b/src/easydiffraction/analysis/calculators/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.calculators.crysfml import CrysfmlCalculator diff --git a/src/easydiffraction/analysis/calculators/base.py b/src/easydiffraction/analysis/calculators/base.py index 5ebd3086..bd667ac8 100644 --- a/src/easydiffraction/analysis/calculators/base.py +++ b/src/easydiffraction/analysis/calculators/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from abc import ABC @@ -17,11 +17,13 @@ class CalculatorBase(ABC): @property @abstractmethod def name(self) -> str: + """Short identifier of the calculation engine.""" pass @property @abstractmethod def engine_imported(self) -> bool: + """True if the underlying calculation library is available.""" pass @abstractmethod @@ -31,9 +33,7 @@ def calculate_structure_factors( experiment: ExperimentBase, called_by_minimizer: bool, ) -> None: - """Calculate structure factors for a single structure and - experiment. - """ + """Calculate structure factors for one experiment.""" pass @abstractmethod @@ -43,16 +43,22 @@ def calculate_pattern( experiment: ExperimentBase, called_by_minimizer: bool, ) -> np.ndarray: - """Calculate the diffraction pattern for a single structure and - experiment. + """ + Calculate diffraction pattern for one structure-experiment pair. - Args: - structure: The structure object. - experiment: The experiment object. - called_by_minimizer: Whether the calculation is called by a - minimizer. + Parameters + ---------- + structure : Structures + The structure object. + experiment : ExperimentBase + The experiment object. + called_by_minimizer : bool + Whether the calculation is called by a minimizer. Default is + False. - Returns: + Returns + ------- + np.ndarray The calculated diffraction pattern as a NumPy array. """ pass diff --git a/src/easydiffraction/analysis/calculators/crysfml.py b/src/easydiffraction/analysis/calculators/crysfml.py index 5d47300e..34624e2b 100644 --- a/src/easydiffraction/analysis/calculators/crysfml.py +++ b/src/easydiffraction/analysis/calculators/crysfml.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from typing import Any @@ -41,6 +41,7 @@ class CrysfmlCalculator(CalculatorBase): @property def name(self) -> str: + """Short identifier of this calculator engine.""" return 'crysfml' def calculate_structure_factors( @@ -48,13 +49,20 @@ def calculate_structure_factors( structures: Structures, experiments: Experiments, ) -> None: - """Call Crysfml to calculate structure factors. - - Args: - structures: The structures to calculate structure - factors for. - experiments: The experiments associated with the sample - models. + """ + Call Crysfml to calculate structure factors. + + Parameters + ---------- + structures : Structures + The structures to calculate structure factors for. + experiments : Experiments + The experiments associated with the sample models. + + Raises + ------ + NotImplementedError + HKL calculation is not implemented for CrysfmlCalculator. """ raise NotImplementedError('HKL calculation is not implemented for CrysfmlCalculator.') @@ -64,18 +72,23 @@ def calculate_pattern( experiment: ExperimentBase, called_by_minimizer: bool = False, ) -> Union[np.ndarray, List[float]]: - """Calculates the diffraction pattern using Crysfml for the - given structure and experiment. - - Args: - structure: The structure to calculate the pattern for. - experiment: The experiment associated with the structure. - called_by_minimizer: Whether the calculation is called by a - minimizer. - - Returns: + """ + Calculate the diffraction pattern using Crysfml. + + Parameters + ---------- + structure : Structures + The structure to calculate the pattern for. + experiment : ExperimentBase + The experiment associated with the structure. + called_by_minimizer : bool, default=False + Whether the calculation is called by a minimizer. + + Returns + ------- + Union[np.ndarray, List[float]] The calculated diffraction pattern as a NumPy array or a - list of floats. + list of floats. """ # Intentionally unused, required by public API/signature del called_by_minimizer @@ -94,13 +107,19 @@ def _adjust_pattern_length( pattern: List[float], target_length: int, ) -> List[float]: - """Adjusts the length of the pattern to match the target length. - - Args: - pattern: The pattern to adjust. - target_length: The desired length of the pattern. - - Returns: + """ + Adjust the pattern length to match the target length. + + Parameters + ---------- + pattern : List[float] + The pattern to adjust. + target_length : int + The desired length of the pattern. + + Returns + ------- + List[float] The adjusted pattern. """ # TODO: Check the origin of this discrepancy coming from @@ -114,16 +133,20 @@ def _crysfml_dict( structure: Structures, experiment: ExperimentBase, ) -> Dict[str, Union[ExperimentBase, Structure]]: - """Converts the structure and experiment into a dictionary - format for Crysfml. - - Args: - structure: The structure to convert. - experiment: The experiment to convert. - - Returns: - A dictionary representation of the structure and - experiment. + """ + Convert structure and experiment into a Crysfml dictionary. + + Parameters + ---------- + structure : Structures + The structure to convert. + experiment : ExperimentBase + The experiment to convert. + + Returns + ------- + Dict[str, Union[ExperimentBase, Structure]] + A dictionary representation of the structure and experiment. """ structure_dict = self._convert_structure_to_dict(structure) experiment_dict = self._convert_experiment_to_dict(experiment) @@ -136,12 +159,17 @@ def _convert_structure_to_dict( self, structure: Structure, ) -> Dict[str, Any]: - """Converts a structure into a dictionary format. + """ + Convert a structure into a dictionary format. - Args: - structure: The structure to convert. + Parameters + ---------- + structure : Structure + The structure to convert. - Returns: + Returns + ------- + Dict[str, Any] A dictionary representation of the structure. """ structure_dict = { @@ -176,12 +204,17 @@ def _convert_experiment_to_dict( self, experiment: ExperimentBase, ) -> Dict[str, Any]: - """Converts an experiment into a dictionary format. + """ + Convert an experiment into a dictionary format. - Args: - experiment: The experiment to convert. + Parameters + ---------- + experiment : ExperimentBase + The experiment to convert. - Returns: + Returns + ------- + Dict[str, Any] A dictionary representation of the experiment. """ expt_type = getattr(experiment, 'type', None) diff --git a/src/easydiffraction/analysis/calculators/cryspy.py b/src/easydiffraction/analysis/calculators/cryspy.py index ad09dc2a..b6a0e4d6 100644 --- a/src/easydiffraction/analysis/calculators/cryspy.py +++ b/src/easydiffraction/analysis/calculators/cryspy.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import contextlib @@ -35,7 +35,8 @@ @CalculatorFactory.register class CryspyCalculator(CalculatorBase): - """Cryspy-based diffraction calculator. + """ + Cryspy-based diffraction calculator. Converts EasyDiffraction models into Cryspy objects and computes patterns. @@ -49,6 +50,7 @@ class CryspyCalculator(CalculatorBase): @property def name(self) -> str: + """Short identifier of this calculator engine.""" return 'cryspy' def __init__(self) -> None: @@ -60,17 +62,18 @@ def calculate_structure_factors( structure: Structure, experiment: ExperimentBase, called_by_minimizer: bool = False, - ): - """Raises a NotImplementedError as HKL calculation is not - implemented. - - Args: - structure: The structure to calculate structure - factors for. - experiment: The experiment associated with the sample - models. - called_by_minimizer: Whether the calculation is called by a - minimizer. + ) -> None: + """ + Raise NotImplementedError as HKL calculation is not implemented. + + Parameters + ---------- + structure : Structure + The structure to calculate structure factors for. + experiment : ExperimentBase + The experiment associated with the sample models. + called_by_minimizer : bool, default=False + Whether the calculation is called by a minimizer. """ combined_name = f'{structure.name}_{experiment.name}' @@ -119,24 +122,28 @@ def calculate_pattern( experiment: ExperimentBase, called_by_minimizer: bool = False, ) -> Union[np.ndarray, List[float]]: - """Calculates the diffraction pattern using Cryspy for the given - structure and experiment. - - We only recreate the cryspy_obj if this method is - - NOT called by the minimizer, or - - the cryspy_dict is NOT yet created. - In other cases, we are modifying the existing cryspy_dict - This allows significantly speeding up the calculation - - Args: - structure: The structure to calculate the pattern for. - experiment: The experiment associated with the structure. - called_by_minimizer: Whether the calculation is called by a - minimizer. - - Returns: + """ + Calculate the diffraction pattern using Cryspy. + + We only recreate the cryspy_obj if this method is - NOT called + by the minimizer, or - the cryspy_dict is NOT yet created. In + other cases, we are modifying the existing cryspy_dict This + allows significantly speeding up the calculation + + Parameters + ---------- + structure : Structure + The structure to calculate the pattern for. + experiment : ExperimentBase + The experiment associated with the structure. + called_by_minimizer : bool, default=False + Whether the calculation is called by a minimizer. + + Returns + ------- + Union[np.ndarray, List[float]] The calculated diffraction pattern as a NumPy array or a - list of floats. + list of floats. """ combined_name = f'{structure.name}_{experiment.name}' @@ -194,14 +201,19 @@ def _recreate_cryspy_dict( structure: Structure, experiment: ExperimentBase, ) -> Dict[str, Any]: - """Recreates the Cryspy dictionary for the given structure and - experiment. - - Args: - structure: The structure to update. - experiment: The experiment to update. - - Returns: + """ + Recreate the Cryspy dictionary for structure and experiment. + + Parameters + ---------- + structure : Structure + The structure to update. + experiment : ExperimentBase + The experiment to update. + + Returns + ------- + Dict[str, Any] The updated Cryspy dictionary. """ combined_name = f'{structure.name}_{experiment.name}' @@ -307,15 +319,20 @@ def _recreate_cryspy_obj( self, structure: Structure, experiment: ExperimentBase, - ) -> Any: - """Recreates the Cryspy object for the given structure and - experiment. - - Args: - structure: The structure to recreate. - experiment: The experiment to recreate. - - Returns: + ) -> object: + """ + Recreate the Cryspy object for structure and experiment. + + Parameters + ---------- + structure : Structure + The structure to recreate. + experiment : ExperimentBase + The experiment to recreate. + + Returns + ------- + object The recreated Cryspy object. """ cryspy_obj = str_to_globaln('') @@ -339,12 +356,17 @@ def _convert_structure_to_cryspy_cif( self, structure: Structure, ) -> str: - """Converts a structure to a Cryspy CIF string. + """ + Convert a structure to a Cryspy CIF string. - Args: - structure: The structure to convert. + Parameters + ---------- + structure : Structure + The structure to convert. - Returns: + Returns + ------- + str The Cryspy CIF string representation of the structure. """ return structure.as_cif @@ -352,16 +374,21 @@ def _convert_structure_to_cryspy_cif( def _convert_experiment_to_cryspy_cif( self, experiment: ExperimentBase, - linked_structure: Any, + linked_structure: object, ) -> str: - """Converts an experiment to a Cryspy CIF string. - - Args: - experiment: The experiment to convert. - linked_structure: The structure linked to the - experiment. - - Returns: + """ + Convert an experiment to a Cryspy CIF string. + + Parameters + ---------- + experiment : ExperimentBase + The experiment to convert. + linked_structure : object + The structure linked to the experiment. + + Returns + ------- + str The Cryspy CIF string representation of the experiment. """ # Try to get experiment attributes diff --git a/src/easydiffraction/analysis/calculators/factory.py b/src/easydiffraction/analysis/calculators/factory.py index fa3812da..f9860f81 100644 --- a/src/easydiffraction/analysis/calculators/factory.py +++ b/src/easydiffraction/analysis/calculators/factory.py @@ -1,9 +1,10 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Calculator factory — delegates to ``FactoryBase``. +""" +Calculator factory — delegates to ``FactoryBase``. -Overrides ``_supported_map`` to filter out calculators whose engines -are not importable in the current environment. +Overrides ``_supported_map`` to filter out calculators whose engines are +not importable in the current environment. """ from __future__ import annotations @@ -17,7 +18,8 @@ class CalculatorFactory(FactoryBase): - """Factory for creating calculation engine instances. + """ + Factory for creating calculation engine instances. Only calculators whose ``engine_imported`` flag is ``True`` are available for creation. diff --git a/src/easydiffraction/analysis/calculators/pdffit.py b/src/easydiffraction/analysis/calculators/pdffit.py index debd90de..4ce20276 100644 --- a/src/easydiffraction/analysis/calculators/pdffit.py +++ b/src/easydiffraction/analysis/calculators/pdffit.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""PDF calculation backend using diffpy.pdffit2 if available. +""" +PDF calculation backend using diffpy.pdffit2 if available. The class adapts the engine to EasyDiffraction calculator interface and silences stdio on import to avoid noisy output in notebooks and logs. @@ -38,6 +39,9 @@ # print("⚠️ 'pdffit' module not found. This calculation engine will # not be available.") PdfFit = None + redirect_stdout = None + pdffit_cif_parser = None + _pdffit_devnull = None @CalculatorFactory.register @@ -51,10 +55,30 @@ class PdffitCalculator(CalculatorBase): engine_imported: bool = PdfFit is not None @property - def name(self): + def name(self) -> str: + """Short identifier of this calculator engine.""" return 'pdffit' - def calculate_structure_factors(self, structures, experiments): + def calculate_structure_factors( + self, + structures: object, + experiments: object, + ) -> list: + """ + Return an empty list; PDF does not compute structure factors. + + Parameters + ---------- + structures : object + Unused; kept for interface consistency. + experiments : object + Unused; kept for interface consistency. + + Returns + ------- + list + An empty list. + """ # PDF doesn't compute HKL but we keep interface consistent # Intentionally unused, required by public API/signature del structures, experiments @@ -66,7 +90,21 @@ def calculate_pattern( structure: Structure, experiment: ExperimentBase, called_by_minimizer: bool = False, - ): + ) -> None: + """ + Calculate the PDF pattern using PDFfit2. + + Parameters + ---------- + structure : Structure + The structure object supplying atom sites and cell + parameters. + experiment : ExperimentBase + The experiment object supplying instrument and peak + parameters. + called_by_minimizer : bool, default=False + Unused; kept for interface consistency. + """ # Intentionally unused, required by public API/signature del called_by_minimizer diff --git a/src/easydiffraction/analysis/categories/__init__.py b/src/easydiffraction/analysis/categories/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/analysis/categories/__init__.py +++ b/src/easydiffraction/analysis/categories/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/analysis/categories/aliases/__init__.py b/src/easydiffraction/analysis/categories/aliases/__init__.py index aa72de6b..6ca3a859 100644 --- a/src/easydiffraction/analysis/categories/aliases/__init__.py +++ b/src/easydiffraction/analysis/categories/aliases/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.categories.aliases.default import Alias diff --git a/src/easydiffraction/analysis/categories/aliases/default.py b/src/easydiffraction/analysis/categories/aliases/default.py index 49b263f4..63cc5f28 100644 --- a/src/easydiffraction/analysis/categories/aliases/default.py +++ b/src/easydiffraction/analysis/categories/aliases/default.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Alias category for mapping friendly names to parameter UIDs. +""" +Alias category for mapping friendly names to parameter UIDs. Defines a small record type used by analysis configuration to refer to parameters via readable labels instead of raw unique identifiers. @@ -19,15 +20,11 @@ class Alias(CategoryItem): - """Single alias entry. + """ + Single alias entry. Maps a human-readable ``label`` to a concrete ``param_uid`` used by the engine. - - Args: - label: Alias label. Must match ``^[A-Za-z_][A-Za-z0-9_]*$``. - param_uid: Target parameter uid. Same identifier pattern as - ``label``. """ def __init__(self) -> None: @@ -60,19 +57,33 @@ def __init__(self) -> None: # ------------------------------------------------------------------ @property - def label(self): + def label(self) -> StringDescriptor: + """ + ... + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._label @label.setter - def label(self, value): + def label(self, value: str) -> None: self._label.value = value @property - def param_uid(self): + def param_uid(self) -> StringDescriptor: + """ + ... + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._param_uid @param_uid.setter - def param_uid(self, value): + def param_uid(self, value: str) -> None: self._param_uid.value = value @@ -85,6 +96,6 @@ class Aliases(CategoryCollection): description='Parameter alias mappings', ) - def __init__(self): + def __init__(self) -> None: """Create an empty collection of aliases.""" super().__init__(item_type=Alias) diff --git a/src/easydiffraction/analysis/categories/aliases/factory.py b/src/easydiffraction/analysis/categories/aliases/factory.py index 07e1fe38..f2bebe43 100644 --- a/src/easydiffraction/analysis/categories/aliases/factory.py +++ b/src/easydiffraction/analysis/categories/aliases/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Aliases factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/analysis/categories/constraints/__init__.py b/src/easydiffraction/analysis/categories/constraints/__init__.py index ded70ca6..97d8c03c 100644 --- a/src/easydiffraction/analysis/categories/constraints/__init__.py +++ b/src/easydiffraction/analysis/categories/constraints/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.categories.constraints.default import Constraint diff --git a/src/easydiffraction/analysis/categories/constraints/default.py b/src/easydiffraction/analysis/categories/constraints/default.py index 647dd273..71d61d95 100644 --- a/src/easydiffraction/analysis/categories/constraints/default.py +++ b/src/easydiffraction/analysis/categories/constraints/default.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Simple symbolic constraint between parameters. +""" +Simple symbolic constraint between parameters. Represents an equation of the form ``lhs_alias = rhs_expr`` where ``rhs_expr`` is evaluated elsewhere by the analysis engine. @@ -20,12 +21,7 @@ class Constraint(CategoryItem): - """Single constraint item. - - Args: - lhs_alias: Left-hand side alias name being constrained. - rhs_expr: Right-hand side expression as a string. - """ + """Single constraint item.""" def __init__(self) -> None: super().__init__() @@ -57,19 +53,33 @@ def __init__(self) -> None: # ------------------------------------------------------------------ @property - def lhs_alias(self): + def lhs_alias(self) -> StringDescriptor: + """ + Left-hand side of the equation. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._lhs_alias @lhs_alias.setter - def lhs_alias(self, value): + def lhs_alias(self, value: str) -> None: self._lhs_alias.value = value @property - def rhs_expr(self): + def rhs_expr(self) -> StringDescriptor: + """ + Right-hand side expression. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._rhs_expr @rhs_expr.setter - def rhs_expr(self, value): + def rhs_expr(self, value: str) -> None: self._rhs_expr.value = value @@ -84,11 +94,11 @@ class Constraints(CategoryCollection): _update_priority = 90 # After most others, but before data categories - def __init__(self): + def __init__(self) -> None: """Create an empty constraints collection.""" super().__init__(item_type=Constraint) - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: del called_by_minimizer constraints = ConstraintsHandler.get() diff --git a/src/easydiffraction/analysis/categories/constraints/factory.py b/src/easydiffraction/analysis/categories/constraints/factory.py index 829260f4..682c9684 100644 --- a/src/easydiffraction/analysis/categories/constraints/factory.py +++ b/src/easydiffraction/analysis/categories/constraints/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Constraints factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/analysis/categories/fit_mode/__init__.py b/src/easydiffraction/analysis/categories/fit_mode/__init__.py index 45267810..058d054c 100644 --- a/src/easydiffraction/analysis/categories/fit_mode/__init__.py +++ b/src/easydiffraction/analysis/categories/fit_mode/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.categories.fit_mode.enums import FitModeEnum diff --git a/src/easydiffraction/analysis/categories/fit_mode/enums.py b/src/easydiffraction/analysis/categories/fit_mode/enums.py index 156e9c30..5e904eae 100644 --- a/src/easydiffraction/analysis/categories/fit_mode/enums.py +++ b/src/easydiffraction/analysis/categories/fit_mode/enums.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Enumeration for fit-mode values.""" @@ -15,9 +15,11 @@ class FitModeEnum(str, Enum): @classmethod def default(cls) -> FitModeEnum: + """Return the default fit mode (SINGLE).""" return cls.SINGLE def description(self) -> str: + """Return a human-readable description of this fit mode.""" if self is FitModeEnum.SINGLE: return 'Independent fitting of each experiment; no shared parameters' elif self is FitModeEnum.JOINT: diff --git a/src/easydiffraction/analysis/categories/fit_mode/factory.py b/src/easydiffraction/analysis/categories/fit_mode/factory.py index 48edef66..f10485f8 100644 --- a/src/easydiffraction/analysis/categories/fit_mode/factory.py +++ b/src/easydiffraction/analysis/categories/fit_mode/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Fit-mode factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/analysis/categories/fit_mode/fit_mode.py b/src/easydiffraction/analysis/categories/fit_mode/fit_mode.py index 8a5bd24b..b0589a2e 100644 --- a/src/easydiffraction/analysis/categories/fit_mode/fit_mode.py +++ b/src/easydiffraction/analysis/categories/fit_mode/fit_mode.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Fit-mode category item. +""" +Fit-mode category item. Stores the active fitting strategy as a CIF-serializable descriptor validated by ``FitModeEnum``. @@ -20,7 +21,8 @@ @FitModeFactory.register class FitMode(CategoryItem): - """Fitting strategy selector. + """ + Fitting strategy selector. Holds a single ``mode`` descriptor whose value is one of ``FitModeEnum`` members (``'single'`` or ``'joint'``). @@ -47,15 +49,16 @@ def __init__(self) -> None: self._identity.category_code = 'fit_mode' @property - def mode(self): - """Active fitting strategy descriptor.""" + def mode(self) -> StringDescriptor: + """ + Fitting strategy. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._mode @mode.setter def mode(self, value: str) -> None: - """Set the fitting strategy value. - - Args: - value: ``'single'`` or ``'joint'``. - """ self._mode.value = value diff --git a/src/easydiffraction/analysis/categories/joint_fit_experiments/__init__.py b/src/easydiffraction/analysis/categories/joint_fit_experiments/__init__.py index 2857b28d..1aa8f0ae 100644 --- a/src/easydiffraction/analysis/categories/joint_fit_experiments/__init__.py +++ b/src/easydiffraction/analysis/categories/joint_fit_experiments/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.categories.joint_fit_experiments.default import JointFitExperiment diff --git a/src/easydiffraction/analysis/categories/joint_fit_experiments/default.py b/src/easydiffraction/analysis/categories/joint_fit_experiments/default.py index 6acf4f44..b94bd7de 100644 --- a/src/easydiffraction/analysis/categories/joint_fit_experiments/default.py +++ b/src/easydiffraction/analysis/categories/joint_fit_experiments/default.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Joint-fit experiment weighting configuration. +""" +Joint-fit experiment weighting configuration. Stores per-experiment weights to be used when multiple experiments are fitted simultaneously. @@ -23,12 +24,7 @@ class JointFitExperiment(CategoryItem): - """A single joint-fit entry. - - Args: - id: Experiment identifier used in the fit session. - weight: Relative weight factor in the combined objective. - """ + """A single joint-fit entry.""" def __init__(self) -> None: super().__init__() @@ -60,19 +56,33 @@ def __init__(self) -> None: # ------------------------------------------------------------------ @property - def id(self): + def id(self) -> StringDescriptor: + """ + Experiment identifier. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._id @id.setter - def id(self, value): + def id(self, value: str) -> None: self._id.value = value @property - def weight(self): + def weight(self) -> NumericDescriptor: + """ + Weight factor. + + Reading this property returns the underlying + ``NumericDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._weight @weight.setter - def weight(self, value): + def weight(self, value: float) -> None: self._weight.value = value @@ -85,6 +95,6 @@ class JointFitExperiments(CategoryCollection): description='Joint-fit experiment weights', ) - def __init__(self): + def __init__(self) -> None: """Create an empty joint-fit experiments collection.""" super().__init__(item_type=JointFitExperiment) diff --git a/src/easydiffraction/analysis/categories/joint_fit_experiments/factory.py b/src/easydiffraction/analysis/categories/joint_fit_experiments/factory.py index 2919c741..57666098 100644 --- a/src/easydiffraction/analysis/categories/joint_fit_experiments/factory.py +++ b/src/easydiffraction/analysis/categories/joint_fit_experiments/factory.py @@ -1,8 +1,6 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Joint-fit-experiments factory — delegates entirely to -``FactoryBase``. -""" +"""Joint-fit-experiments factory — delegates to ``FactoryBase``.""" from __future__ import annotations diff --git a/src/easydiffraction/analysis/fit_helpers/__init__.py b/src/easydiffraction/analysis/fit_helpers/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/analysis/fit_helpers/__init__.py +++ b/src/easydiffraction/analysis/fit_helpers/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/analysis/fit_helpers/metrics.py b/src/easydiffraction/analysis/fit_helpers/metrics.py index 97c715d8..dee08f86 100644 --- a/src/easydiffraction/analysis/fit_helpers/metrics.py +++ b/src/easydiffraction/analysis/fit_helpers/metrics.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from typing import Optional @@ -14,14 +14,19 @@ def calculate_r_factor( y_obs: np.ndarray, y_calc: np.ndarray, ) -> float: - """Calculate the R-factor (reliability factor) between observed and - calculated data. - - Args: - y_obs: Observed data points. - y_calc: Calculated data points. - - Returns: + """ + Calculate the R-factor between observed and calculated data. + + Parameters + ---------- + y_obs : np.ndarray + Observed data points. + y_calc : np.ndarray + Calculated data points. + + Returns + ------- + float R-factor value. """ y_obs = np.asarray(y_obs) @@ -36,15 +41,21 @@ def calculate_weighted_r_factor( y_calc: np.ndarray, weights: np.ndarray, ) -> float: - """Calculate the weighted R-factor between observed and calculated - data. - - Args: - y_obs: Observed data points. - y_calc: Calculated data points. - weights: Weights for each data point. - - Returns: + """ + Calculate weighted R-factor between observed and calculated data. + + Parameters + ---------- + y_obs : np.ndarray + Observed data points. + y_calc : np.ndarray + Calculated data points. + weights : np.ndarray + Weights for each data point. + + Returns + ------- + float Weighted R-factor value. """ y_obs = np.asarray(y_obs) @@ -59,14 +70,19 @@ def calculate_rb_factor( y_obs: np.ndarray, y_calc: np.ndarray, ) -> float: - """Calculate the Bragg R-factor between observed and calculated - data. - - Args: - y_obs: Observed data points. - y_calc: Calculated data points. - - Returns: + """ + Calculate the Bragg R-factor between observed and calculated data. + + Parameters + ---------- + y_obs : np.ndarray + Observed data points. + y_calc : np.ndarray + Calculated data points. + + Returns + ------- + float Bragg R-factor value. """ y_obs = np.asarray(y_obs) @@ -80,14 +96,19 @@ def calculate_r_factor_squared( y_obs: np.ndarray, y_calc: np.ndarray, ) -> float: - """Calculate the R-factor squared between observed and calculated - data. - - Args: - y_obs: Observed data points. - y_calc: Calculated data points. - - Returns: + """ + Calculate the R-factor squared between observed and calculated data. + + Parameters + ---------- + y_obs : np.ndarray + Observed data points. + y_calc : np.ndarray + Calculated data points. + + Returns + ------- + float R-factor squared value. """ y_obs = np.asarray(y_obs) @@ -101,13 +122,19 @@ def calculate_reduced_chi_square( residuals: np.ndarray, num_parameters: int, ) -> float: - """Calculate the reduced chi-square statistic. - - Args: - residuals: Residuals between observed and calculated data. - num_parameters: Number of free parameters used in the model. - - Returns: + """ + Calculate the reduced chi-square statistic. + + Parameters + ---------- + residuals : np.ndarray + Residuals between observed and calculated data. + num_parameters : int + Number of free parameters used in the model. + + Returns + ------- + float Reduced chi-square value. """ residuals = np.asarray(residuals) @@ -124,16 +151,24 @@ def get_reliability_inputs( structures: Structures, experiments: Experiments, ) -> Tuple[np.ndarray, np.ndarray, Optional[np.ndarray]]: - """Collect observed and calculated data points for reliability - calculations. - - Args: - structures: Collection of structures. - experiments: Collection of experiments. - - Returns: - Tuple containing arrays of (observed values, calculated values, - error values) + """ + Collect observed and calculated data for reliability calculations. + + Parameters + ---------- + structures : Structures + Collection of structures. + experiments : Experiments + Collection of experiments. + + Returns + ------- + np.ndarray + Observed values. + np.ndarray + Calculated values. + Optional[np.ndarray] + Error values, or None if not available. """ y_obs_all = [] y_calc_all = [] diff --git a/src/easydiffraction/analysis/fit_helpers/reporting.py b/src/easydiffraction/analysis/fit_helpers/reporting.py index d9dbc40e..a1468aa5 100644 --- a/src/easydiffraction/analysis/fit_helpers/reporting.py +++ b/src/easydiffraction/analysis/fit_helpers/reporting.py @@ -1,7 +1,6 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -from typing import Any from typing import List from typing import Optional @@ -14,7 +13,8 @@ class FitResults: - """Container for results of a single optimization run. + """ + Container for results of a single optimization run. Holds success flag, chi-square metrics, iteration counts, timing, and parameter objects. Provides a printer to summarize key @@ -24,41 +24,53 @@ class FitResults: def __init__( self, success: bool = False, - parameters: Optional[List[Any]] = None, + parameters: Optional[List[object]] = None, chi_square: Optional[float] = None, reduced_chi_square: Optional[float] = None, message: str = '', iterations: int = 0, - engine_result: Optional[Any] = None, - starting_parameters: Optional[List[Any]] = None, + engine_result: Optional[object] = None, + starting_parameters: Optional[List[object]] = None, fitting_time: Optional[float] = None, - **kwargs: Any, + **kwargs: object, ) -> None: - """Initialize FitResults with the given parameters. - - Args: - success: Indicates if the fit was successful. - parameters: List of parameters used in the fit. - chi_square: Chi-square value of the fit. - reduced_chi_square: Reduced chi-square value of the fit. - message: Message related to the fit. - iterations: Number of iterations performed. - engine_result: Result from the fitting engine. - starting_parameters: Initial parameters for the fit. - fitting_time: Time taken for the fitting process. - **kwargs: Additional engine-specific fields. If ``redchi`` - is provided and ``reduced_chi_square`` is not set, it is - used as the reduced chi-square value. + """ + Initialize FitResults with the given parameters. + + Parameters + ---------- + success : bool, default=False + Indicates if the fit was successful. + parameters : Optional[List[object]], default=None + List of parameters used in the fit. + chi_square : Optional[float], default=None + Chi-square value of the fit. + reduced_chi_square : Optional[float], default=None + Reduced chi-square value of the fit. + message : str, default='' + Message related to the fit. + iterations : int, default=0 + Number of iterations performed. + engine_result : Optional[object], default=None + Result from the fitting engine. + starting_parameters : Optional[List[object]], default=None + Initial parameters for the fit. + fitting_time : Optional[float], default=None + Time taken for the fitting process. + **kwargs : object + Additional engine-specific fields. If ``redchi`` is provided + and ``reduced_chi_square`` is not set, it is used as the + reduced chi-square value. """ self.success: bool = success - self.parameters: List[Any] = parameters if parameters is not None else [] + self.parameters: List[object] = parameters if parameters is not None else [] self.chi_square: Optional[float] = chi_square self.reduced_chi_square: Optional[float] = reduced_chi_square self.message: str = message self.iterations: int = iterations - self.engine_result: Optional[Any] = engine_result - self.result: Optional[Any] = None - self.starting_parameters: List[Any] = ( + self.engine_result: Optional[object] = engine_result + self.result: Optional[object] = None + self.starting_parameters: List[object] = ( starting_parameters if starting_parameters is not None else [] ) self.fitting_time: Optional[float] = fitting_time @@ -77,14 +89,21 @@ def display_results( f_obs: Optional[List[float]] = None, f_calc: Optional[List[float]] = None, ) -> None: - """Render a human-readable summary of the fit. - - Args: - y_obs: Observed intensities for pattern R-factor metrics. - y_calc: Calculated intensities for pattern R-factor metrics. - y_err: Standard deviations of observed intensities for wR. - f_obs: Observed structure-factor magnitudes for Bragg R. - f_calc: Calculated structure-factor magnitudes for Bragg R. + """ + Render a human-readable summary of the fit. + + Parameters + ---------- + y_obs : Optional[List[float]], default=None + Observed intensities for pattern R-factor metrics. + y_calc : Optional[List[float]], default=None + Calculated intensities for pattern R-factor metrics. + y_err : Optional[List[float]], default=None + Standard deviations of observed intensities for wR. + f_obs : Optional[List[float]], default=None + Observed structure-factor magnitudes for Bragg R. + f_calc : Optional[List[float]], default=None + Calculated structure-factor magnitudes for Bragg R. """ status_icon = '✅' if self.success else '❌' rf = rf2 = wr = br = None diff --git a/src/easydiffraction/analysis/fit_helpers/tracking.py b/src/easydiffraction/analysis/fit_helpers/tracking.py index 438fd46d..eb32f3ea 100644 --- a/src/easydiffraction/analysis/fit_helpers/tracking.py +++ b/src/easydiffraction/analysis/fit_helpers/tracking.py @@ -1,9 +1,8 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import time from contextlib import suppress -from typing import Any from typing import List from typing import Optional @@ -36,31 +35,40 @@ class _TerminalLiveHandle: - """Adapter that exposes update()/close() for terminal live updates. + """ + Adapter that exposes update()/close() for terminal live updates. Wraps a rich.live.Live instance but keeps the tracker decoupled from the underlying UI mechanism. """ - def __init__(self, live) -> None: + def __init__(self, live: object) -> None: self._live = live - def update(self, renderable) -> None: + def update(self, renderable: object) -> None: + """ + Refresh the live display with a new renderable. + + Parameters + ---------- + renderable : object + A Rich-compatible renderable to display. + """ self._live.update(renderable, refresh=True) def close(self) -> None: + """Stop the live display, suppressing any errors.""" with suppress(Exception): self._live.stop() -def _make_display_handle() -> Any | None: - """Create and initialize a display/update handle for the - environment. +def _make_display_handle() -> object | None: + """ + Create and initialize a display/update handle for the environment. - In Jupyter, returns an IPython DisplayHandle and creates a - placeholder. - - In terminal, returns a _TerminalLiveHandle backed by rich Live. - - If neither applies, returns None. + placeholder. - In terminal, returns a _TerminalLiveHandle backed by + rich Live. - If neither applies, returns None. """ if in_jupyter() and display is not None and HTML is not None: h = DisplayHandle() @@ -77,7 +85,8 @@ def _make_display_handle() -> Any | None: class FitProgressTracker: - """Track and report reduced chi-square during optimization. + """ + Track and report reduced chi-square during optimization. The tracker keeps iteration counters, remembers the best observed reduced chi-square and when it occurred, and can display progress as @@ -94,8 +103,8 @@ def __init__(self) -> None: self._fitting_time: Optional[float] = None self._df_rows: List[List[str]] = [] - self._display_handle: Optional[Any] = None - self._live: Optional[Any] = None + self._display_handle: Optional[object] = None + self._live: Optional[object] = None def reset(self) -> None: """Reset internal state before a new optimization run.""" @@ -112,13 +121,19 @@ def track( residuals: np.ndarray, parameters: List[float], ) -> np.ndarray: - """Update progress with current residuals and parameters. - - Args: - residuals: Residuals between measured and calculated data. - parameters: Current free parameters being fitted. - - Returns: + """ + Update progress with current residuals and parameters. + + Parameters + ---------- + residuals : np.ndarray + Residuals between measured and calculated data. + parameters : List[float] + Current free parameters being fitted. + + Returns + ------- + np.ndarray Residuals unchanged, for optimizer consumption. """ self._iteration += 1 @@ -200,10 +215,13 @@ def stop_timer(self) -> None: self._fitting_time = self._end_time - self._start_time def start_tracking(self, minimizer_name: str) -> None: - """Initialize display and headers and announce the minimizer. + """ + Initialize display and headers and announce the minimizer. - Args: - minimizer_name: Name of the minimizer used for the run. + Parameters + ---------- + minimizer_name : str + Name of the minimizer used for the run. """ console.print(f"🚀 Starting fit process with '{minimizer_name}'...") console.print('📈 Goodness-of-fit (reduced χ²) change:') @@ -221,10 +239,13 @@ def start_tracking(self, minimizer_name: str) -> None: ) def add_tracking_info(self, row: List[str]) -> None: - """Append a formatted row to the progress display. + """ + Append a formatted row to the progress display. - Args: - row: Columns corresponding to DEFAULT_HEADERS. + Parameters + ---------- + row : List[str] + Columns corresponding to DEFAULT_HEADERS. """ # Append and update via the active handle (Jupyter or # terminal live) diff --git a/src/easydiffraction/analysis/fitting.py b/src/easydiffraction/analysis/fitting.py index 453aaf1a..fa6c0368 100644 --- a/src/easydiffraction/analysis/fitting.py +++ b/src/easydiffraction/analysis/fitting.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from typing import TYPE_CHECKING @@ -33,20 +33,26 @@ def fit( structures: Structures, experiments: Experiments, weights: Optional[np.array] = None, - analysis=None, + analysis: object = None, ) -> None: - """Run the fitting process. + """ + Run the fitting process. This method performs the optimization but does not display - results. Use :meth:`show_fit_results` on the Analysis object - to display the fit results after fitting is complete. - - Args: - structures: Collection of structures. - experiments: Collection of experiments. - weights: Optional weights for joint fitting. - analysis: Optional Analysis object to update its categories - during fitting. + results. Use :meth:`show_fit_results` on the Analysis object to + display the fit results after fitting is complete. + + Parameters + ---------- + structures : Structures + Collection of structures. + experiments : Experiments + Collection of experiments. + weights : Optional[np.array], default=None + Optional weights for joint fitting. + analysis : object, default=None + Optional Analysis object to update its categories during + fitting. """ params = structures.free_parameters + experiments.free_parameters @@ -58,6 +64,19 @@ def fit( param._fit_start_value = param.value def objective_function(engine_params: Dict[str, Any]) -> np.ndarray: + """ + Evaluate the residual for the current minimizer parameters. + + Parameters + ---------- + engine_params : Dict[str, Any] + Parameter values provided by the minimizer engine. + + Returns + ------- + np.ndarray + Residual array passed back to the minimizer. + """ return self._residual_function( engine_params=engine_params, parameters=params, @@ -75,16 +94,20 @@ def _process_fit_results( structures: Structures, experiments: Experiments, ) -> None: - """Collect reliability inputs and display fit results. + """ + Collect reliability inputs and display fit results. This method is typically called by :meth:`Analysis.show_fit_results` rather than directly. It - calculates R-factors and other metrics, then renders them to - the console. - - Args: - structures: Collection of structures. - experiments: Collection of experiments. + calculates R-factors and other metrics, then renders them to the + console. + + Parameters + ---------- + structures : Structures + Collection of structures. + experiments : Experiments + Collection of experiments. """ y_obs, y_calc, y_err = get_reliability_inputs( structures, @@ -108,13 +131,19 @@ def _collect_free_parameters( structures: Structures, experiments: Experiments, ) -> List[Parameter]: - """Collect free parameters from structures and experiments. - - Args: - structures: Collection of structures. - experiments: Collection of experiments. - - Returns: + """ + Collect free parameters from structures and experiments. + + Parameters + ---------- + structures : Structures + Collection of structures. + experiments : Experiments + Collection of experiments. + + Returns + ------- + List[Parameter] List of free parameters. """ free_params: List[Parameter] = structures.free_parameters + experiments.free_parameters @@ -127,22 +156,33 @@ def _residual_function( structures: Structures, experiments: Experiments, weights: Optional[np.array] = None, - analysis=None, + analysis: object = None, ) -> np.ndarray: - """Residual function computes the difference between measured - and calculated patterns. It updates the parameter values - according to the optimizer-provided engine_params. - - Args: - engine_params: Engine-specific parameter dict. - parameters: List of parameters being optimized. - structures: Collection of structures. - experiments: Collection of experiments. - weights: Optional weights for joint fitting. - analysis: Optional Analysis object to update its categories - during fitting. - - Returns: + """ + Compute residuals between measured and calculated patterns. + + It updates the parameter values according to the + optimizer-provided engine_params. + + Parameters + ---------- + engine_params : Dict[str, Any] + Engine-specific parameter dict. + parameters : List[Parameter] + List of parameters being optimized. + structures : Structures + Collection of structures. + experiments : Experiments + Collection of experiments. + weights : Optional[np.array], default=None + Optional weights for joint fitting. + analysis : object, default=None + Optional Analysis object to update its categories during + fitting. + + Returns + ------- + np.ndarray Array of weighted residuals. """ # Sync parameters back to objects diff --git a/src/easydiffraction/analysis/minimizers/__init__.py b/src/easydiffraction/analysis/minimizers/__init__.py index 8a41a6f2..01fbc136 100644 --- a/src/easydiffraction/analysis/minimizers/__init__.py +++ b/src/easydiffraction/analysis/minimizers/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.minimizers.dfols import DfolsMinimizer diff --git a/src/easydiffraction/analysis/minimizers/base.py b/src/easydiffraction/analysis/minimizers/base.py index 069601ed..f8499dad 100644 --- a/src/easydiffraction/analysis/minimizers/base.py +++ b/src/easydiffraction/analysis/minimizers/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from abc import ABC @@ -16,14 +16,13 @@ class MinimizerBase(ABC): - """Abstract base for concrete minimizers. - - Contract: - - Subclasses must implement ``_prepare_solver_args``, - ``_run_solver``, ``_sync_result_to_parameters`` and - ``_check_success``. - - The ``fit`` method orchestrates the full workflow and returns - :class:`FitResults`. + """ + Abstract base for concrete minimizers. + + Contract: - Subclasses must implement ``_prepare_solver_args``, + ``_run_solver``, ``_sync_result_to_parameters`` and + ``_check_success``. - The ``fit`` method orchestrates the full + workflow and returns :class:`FitResults`. """ def __init__( @@ -44,10 +43,13 @@ def __init__( self.tracker: FitProgressTracker = FitProgressTracker() def _start_tracking(self, minimizer_name: str) -> None: - """Initialize progress tracking and timer. + """ + Initialize progress tracking and timer. - Args: - minimizer_name: Human-readable name shown in progress. + Parameters + ---------- + minimizer_name : str + Human-readable name shown in progress. """ self.tracker.reset() self.tracker.start_tracking(minimizer_name) @@ -60,12 +62,17 @@ def _stop_tracking(self) -> None: @abstractmethod def _prepare_solver_args(self, parameters: List[Any]) -> Dict[str, Any]: - """Prepare keyword-arguments for the underlying solver. + """ + Prepare keyword-arguments for the underlying solver. - Args: - parameters: List of free parameters to be fitted. + Parameters + ---------- + parameters : List[Any] + List of free parameters to be fitted. - Returns: + Returns + ------- + Dict[str, Any] Mapping of keyword arguments to pass into ``_run_solver``. """ pass @@ -73,36 +80,40 @@ def _prepare_solver_args(self, parameters: List[Any]) -> Dict[str, Any]: @abstractmethod def _run_solver( self, - objective_function: Callable[..., Any], - engine_parameters: Dict[str, Any], - ) -> Any: + objective_function: Callable[..., object], + engine_parameters: Dict[str, object], + ) -> object: """Execute the concrete solver and return its raw result.""" pass @abstractmethod def _sync_result_to_parameters( self, - raw_result: Any, - parameters: List[Any], + raw_result: object, + parameters: List[object], ) -> None: - """Copy values from ``raw_result`` back to ``parameters`` in- - place. - """ + """Copy raw_result values back to parameters in-place.""" pass def _finalize_fit( self, - parameters: List[Any], - raw_result: Any, + parameters: List[object], + raw_result: object, ) -> FitResults: - """Build :class:`FitResults` and store it on ``self.result``. - - Args: - parameters: Parameters after the solver finished. - raw_result: Backend-specific solver output object. - - Returns: - FitResults: Aggregated outcome of the fit. + """ + Build :class:`FitResults` and store it on ``self.result``. + + Parameters + ---------- + parameters : List[object] + Parameters after the solver finished. + raw_result : object + Backend-specific solver output object. + + Returns + ------- + FitResults + Aggregated outcome of the fit. """ self._sync_result_to_parameters(parameters, raw_result) success = self._check_success(raw_result) @@ -117,23 +128,29 @@ def _finalize_fit( return self.result @abstractmethod - def _check_success(self, raw_result: Any) -> bool: + def _check_success(self, raw_result: object) -> bool: """Determine whether the fit was successful.""" pass def fit( self, - parameters: List[Any], - objective_function: Callable[..., Any], + parameters: List[object], + objective_function: Callable[..., object], ) -> FitResults: - """Run the full minimization workflow. - - Args: - parameters: Free parameters to optimize. - objective_function: Callable returning residuals for a given - set of engine arguments. - - Returns: + """ + Run the full minimization workflow. + + Parameters + ---------- + parameters : List[object] + Free parameters to optimize. + objective_function : Callable[..., object] + Callable returning residuals for a given set of engine + arguments. + + Returns + ------- + FitResults FitResults with success flag, best chi2 and timing. """ minimizer_name = self.name or 'Unnamed Minimizer' @@ -153,11 +170,11 @@ def fit( def _objective_function( self, - engine_params: Dict[str, Any], - parameters: List[Any], - structures: Any, - experiments: Any, - calculator: Any, + engine_params: Dict[str, object], + parameters: List[object], + structures: object, + experiments: object, + calculator: object, ) -> np.ndarray: """Default objective helper computing residuals array.""" return self._compute_residuals( @@ -170,11 +187,11 @@ def _objective_function( def _create_objective_function( self, - parameters: List[Any], - structures: Any, - experiments: Any, - calculator: Any, - ) -> Callable[[Dict[str, Any]], np.ndarray]: + parameters: List[object], + structures: object, + experiments: object, + calculator: object, + ) -> Callable[[Dict[str, object]], np.ndarray]: """Return a closure capturing problem context for the solver.""" return lambda engine_params: self._objective_function( engine_params, diff --git a/src/easydiffraction/analysis/minimizers/dfols.py b/src/easydiffraction/analysis/minimizers/dfols.py index b68a034a..fad9ad06 100644 --- a/src/easydiffraction/analysis/minimizers/dfols.py +++ b/src/easydiffraction/analysis/minimizers/dfols.py @@ -1,7 +1,6 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -from typing import Any from typing import Dict from typing import List @@ -17,9 +16,7 @@ @MinimizerFactory.register class DfolsMinimizer(MinimizerBase): - """Minimizer using the DFO-LS package (Derivative-Free Optimization - for Least-Squares). - """ + """Minimizer using DFO-LS (derivative-free least-squares).""" type_info = TypeInfo( tag='dfols', @@ -30,13 +27,13 @@ def __init__( self, name: str = 'dfols', max_iterations: int = DEFAULT_MAX_ITERATIONS, - **kwargs: Any, + **kwargs: object, ) -> None: super().__init__(name=name, method=None, max_iterations=max_iterations) # Intentionally unused, accepted for API compatibility del kwargs - def _prepare_solver_args(self, parameters: List[Any]) -> Dict[str, Any]: + def _prepare_solver_args(self, parameters: List[object]) -> Dict[str, object]: x0 = [] bounds_lower = [] bounds_upper = [] @@ -47,21 +44,25 @@ def _prepare_solver_args(self, parameters: List[Any]) -> Dict[str, Any]: bounds = (np.array(bounds_lower), np.array(bounds_upper)) return {'x0': np.array(x0), 'bounds': bounds} - def _run_solver(self, objective_function: Any, **kwargs: Any) -> Any: + def _run_solver(self, objective_function: object, **kwargs: object) -> object: x0 = kwargs.get('x0') bounds = kwargs.get('bounds') return solve(objective_function, x0=x0, bounds=bounds, maxfun=self.max_iterations) def _sync_result_to_parameters( self, - parameters: List[Any], - raw_result: Any, + parameters: List[object], + raw_result: object, ) -> None: - """Synchronizes the result from the solver to the parameters. - - Args: - parameters: List of parameters being optimized. - raw_result: The result object returned by the solver. + """ + Synchronize the solver result back to the parameters. + + Parameters + ---------- + parameters : List[object] + List of parameters being optimized. + raw_result : object + The result object returned by the solver. """ # Ensure compatibility with raw_result coming from dfols.solve() result_values = raw_result.x if hasattr(raw_result, 'x') else raw_result @@ -74,13 +75,18 @@ def _sync_result_to_parameters( # calculate later if needed param.uncertainty = None - def _check_success(self, raw_result: Any) -> bool: - """Determines success from DFO-LS result dictionary. + def _check_success(self, raw_result: object) -> bool: + """ + Determine success from DFO-LS result dictionary. - Args: - raw_result: The result object returned by the solver. + Parameters + ---------- + raw_result : object + The result object returned by the solver. - Returns: + Returns + ------- + bool True if the optimization was successful, False otherwise. """ return raw_result.flag == raw_result.EXIT_SUCCESS diff --git a/src/easydiffraction/analysis/minimizers/factory.py b/src/easydiffraction/analysis/minimizers/factory.py index e12a9533..18f67cc6 100644 --- a/src/easydiffraction/analysis/minimizers/factory.py +++ b/src/easydiffraction/analysis/minimizers/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Minimizer factory — delegates to ``FactoryBase``.""" diff --git a/src/easydiffraction/analysis/minimizers/lmfit.py b/src/easydiffraction/analysis/minimizers/lmfit.py index cb0a59fd..771bacad 100644 --- a/src/easydiffraction/analysis/minimizers/lmfit.py +++ b/src/easydiffraction/analysis/minimizers/lmfit.py @@ -1,7 +1,6 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -from typing import Any from typing import Dict from typing import List @@ -38,16 +37,21 @@ def __init__( def _prepare_solver_args( self, - parameters: List[Any], - ) -> Dict[str, Any]: - """Prepares the solver arguments for the lmfit minimizer. + parameters: List[object], + ) -> Dict[str, object]: + """ + Prepare the solver arguments for the lmfit minimizer. - Args: - parameters: List of parameters to be optimized. + Parameters + ---------- + parameters : List[object] + List of parameters to be optimized. - Returns: + Returns + ------- + Dict[str, object] A dictionary containing the prepared lmfit. Parameters - object. + object. """ engine_parameters = lmfit.Parameters() for param in parameters: @@ -60,14 +64,20 @@ def _prepare_solver_args( ) return {'engine_parameters': engine_parameters} - def _run_solver(self, objective_function: Any, **kwargs: Any) -> Any: - """Runs the lmfit solver. - - Args: - objective_function: The objective function to minimize. - **kwargs: Additional arguments for the solver. - - Returns: + def _run_solver(self, objective_function: object, **kwargs: object) -> object: + """ + Run the lmfit solver. + + Parameters + ---------- + objective_function : object + The objective function to minimize. + **kwargs : object + Additional arguments for the solver. + + Returns + ------- + object The result of the lmfit minimization. """ engine_parameters = kwargs.get('engine_parameters') @@ -82,14 +92,18 @@ def _run_solver(self, objective_function: Any, **kwargs: Any) -> Any: def _sync_result_to_parameters( self, - parameters: List[Any], - raw_result: Any, + parameters: List[object], + raw_result: object, ) -> None: - """Synchronizes the result from the solver to the parameters. - - Args: - parameters: List of parameters being optimized. - raw_result: The result object returned by the solver. + """ + Synchronize the result from the solver to the parameters. + + Parameters + ---------- + parameters : List[object] + List of parameters being optimized. + raw_result : object + The result object returned by the solver. """ param_values = raw_result.params if hasattr(raw_result, 'params') else raw_result @@ -101,13 +115,18 @@ def _sync_result_to_parameters( param._set_value_from_minimizer(param_result.value) param.uncertainty = getattr(param_result, 'stderr', None) - def _check_success(self, raw_result: Any) -> bool: - """Determines success from lmfit MinimizerResult. + def _check_success(self, raw_result: object) -> bool: + """ + Determine success from lmfit MinimizerResult. - Args: - raw_result: The result object returned by the solver. + Parameters + ---------- + raw_result : object + The result object returned by the solver. - Returns: + Returns + ------- + bool True if the optimization was successful, False otherwise. """ return getattr(raw_result, 'success', False) @@ -116,18 +135,25 @@ def _iteration_callback( self, params: lmfit.Parameters, iter: int, - resid: Any, - *args: Any, - **kwargs: Any, + resid: object, + *args: object, + **kwargs: object, ) -> None: - """Callback function for each iteration of the minimizer. - - Args: - params: The current parameters. - iter: The current iteration number. - resid: The residuals. - *args: Additional positional arguments. - **kwargs: Additional keyword arguments. + """ + Handle each iteration callback of the minimizer. + + Parameters + ---------- + params : lmfit.Parameters + The current parameters. + iter : int + The current iteration number. + resid : object + The residuals. + *args : object + Additional positional arguments. + **kwargs : object + Additional keyword arguments. """ # Intentionally unused, required by callback signature del params, resid, args, kwargs diff --git a/src/easydiffraction/core/__init__.py b/src/easydiffraction/core/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/core/__init__.py +++ b/src/easydiffraction/core/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/core/category.py b/src/easydiffraction/core/category.py index d4b57dc3..25e81894 100644 --- a/src/easydiffraction/core/category.py +++ b/src/easydiffraction/core/category.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -30,12 +30,13 @@ def __str__(self) -> str: return f'<{name} ({params})>' # TODO: Common for all categories - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: del called_by_minimizer pass @property - def unique_name(self): + def unique_name(self) -> str: + """Fully qualified name: datablock, category, entry.""" parts = [ self._identity.datablock_entry_name, self._identity.category_code, @@ -46,7 +47,8 @@ def unique_name(self): return '.'.join(str_parts) @property - def parameters(self): + def parameters(self) -> list: + """All GenericDescriptorBase instances on this item.""" return [v for v in vars(self).values() if isinstance(v, GenericDescriptorBase)] @property @@ -54,7 +56,7 @@ def as_cif(self) -> str: """Return CIF representation of this object.""" return category_item_to_cif(self) - def from_cif(self, block, idx=0): + def from_cif(self, block: object, idx: int = 0) -> None: """Populate this item from a CIF block.""" category_item_from_cif(self, block, idx) @@ -160,7 +162,8 @@ def help(self) -> None: class CategoryCollection(CollectionBase): - """Handles loop-style category containers (e.g. AtomSites). + """ + Handles loop-style category containers (e.g. AtomSites). Each item is a CategoryItem (component). """ @@ -168,16 +171,17 @@ class CategoryCollection(CollectionBase): # TODO: Common for all categories _update_priority = 10 # Default. Lower values run first. - def _key_for(self, item): + def _key_for(self, item: object) -> str | None: """Return the category-level identity key for *item*.""" return item._identity.category_entry_name def _mark_parent_dirty(self) -> None: - """Set ``_need_categories_update`` on the parent datablock. + """ + Set ``_need_categories_update`` on the parent datablock. Called whenever the collection content changes (items added or - removed) so that subsequent ``_update_categories()`` calls - re-run all category updates. + removed) so that subsequent ``_update_categories()`` calls re- + run all category updates. """ parent = getattr(self, '_parent', None) if parent is not None and hasattr(parent, '_need_categories_update'): @@ -190,16 +194,17 @@ def __str__(self) -> str: return f'<{name} collection ({size} items)>' # TODO: Common for all categories - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: del called_by_minimizer pass @property - def unique_name(self): + def unique_name(self) -> str | None: + """Return None; collections have no unique name.""" return None @property - def parameters(self): + def parameters(self) -> list: """All parameters from all items in this collection.""" params = [] for item in self._items: @@ -211,27 +216,33 @@ def as_cif(self) -> str: """Return CIF representation of this object.""" return category_collection_to_cif(self) - def from_cif(self, block): + def from_cif(self, block: object) -> None: """Populate this collection from a CIF block.""" category_collection_from_cif(self, block) - def add(self, item) -> None: - """Insert or replace a pre-built item into the collection. + def add(self, item: object) -> None: + """ + Insert or replace a pre-built item into the collection. - Args: - item: A ``CategoryItem`` instance to add. + Parameters + ---------- + item : object + A ``CategoryItem`` instance to add. """ self[item._identity.category_entry_name] = item self._mark_parent_dirty() - def create(self, **kwargs) -> None: - """Create a new item with the given attributes and add it. + def create(self, **kwargs: object) -> None: + """ + Create a new item with the given attributes and add it. A default instance of the collection's item type is created, then each keyword argument is applied via ``setattr``. - Args: - **kwargs: Attribute names and values for the new item. + Parameters + ---------- + **kwargs : object + Attribute names and values for the new item. """ child_obj = self._item_type() diff --git a/src/easydiffraction/core/collection.py b/src/easydiffraction/core/collection.py index 164f3d77..0560a37e 100644 --- a/src/easydiffraction/core/collection.py +++ b/src/easydiffraction/core/collection.py @@ -1,33 +1,41 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Lightweight container for guarded items with name-based indexing. +""" +Lightweight container for guarded items with name-based indexing. -`CollectionBase` maintains an ordered list of items and a lazily rebuilt -index by the item's identity key. It supports dict-like access for get, -set and delete, along with iteration over the items. +``CollectionBase`` maintains an ordered list of items and a lazily +rebuilt index by the item's identity key. It supports dict-like access +for get, set and delete, along with iteration over the items. """ from __future__ import annotations +from typing import Generator +from typing import Iterator + from easydiffraction.core.guard import GuardedBase class CollectionBase(GuardedBase): - """A minimal collection with stable iteration and name indexing. + """ + A minimal collection with stable iteration and name indexing. - Args: - item_type: Type of items accepted by the collection. Used for - validation and tooling; not enforced at runtime here. + Parameters + ---------- + item_type : type + Type of items accepted by the collection. Used for validation + and tooling; not enforced at runtime here. """ - def __init__(self, item_type) -> None: + def __init__(self, item_type: type) -> None: super().__init__() self._items: list = [] self._index: dict = {} self._item_type = item_type - def __getitem__(self, name: str): - """Return an item by its identity key. + def __getitem__(self, name: str) -> GuardedBase: + """ + Return an item by its identity key. Rebuilds the internal index on a cache miss to stay consistent with recent mutations. @@ -38,7 +46,7 @@ def __getitem__(self, name: str): self._rebuild_index() return self._index[name] - def __setitem__(self, name: str, item) -> None: + def __setitem__(self, name: str, item: GuardedBase) -> None: """Insert or replace an item under the given identity key.""" # Check if item with same key exists; if so, replace it for i, existing_item in enumerate(self._items): @@ -66,7 +74,7 @@ def __contains__(self, name: str) -> bool: self._rebuild_index() return name in self._index - def __iter__(self): + def __iter__(self) -> Iterator[GuardedBase]: """Iterate over items in insertion order.""" return iter(self._items) @@ -75,18 +83,27 @@ def __len__(self) -> int: return len(self._items) def remove(self, name: str) -> None: - """Remove an item by its key. + """ + Remove an item by its key. - Args: - name: Identity key of the item to remove. + Parameters + ---------- + name : str + Identity key of the item to remove. - Raises: - KeyError: If no item with the given key exists. + Raises + ------ + KeyError + If no item with the given key exists. """ - del self[name] + try: + del self[name] + except KeyError: + raise - def _key_for(self, item): - """Return the identity key for *item*. + def _key_for(self, item: GuardedBase) -> str | None: + """ + Return the identity key for *item*. Subclasses must override to return the appropriate key (``category_entry_name`` or ``datablock_entry_name``). @@ -101,20 +118,20 @@ def _rebuild_index(self) -> None: if key: self._index[key] = item - def keys(self): + def keys(self) -> Generator[str | None, None, None]: """Yield keys for all items in insertion order.""" return (self._key_for(item) for item in self._items) - def values(self): + def values(self) -> Generator[GuardedBase, None, None]: """Yield items in insertion order.""" return (item for item in self._items) - def items(self): + def items(self) -> Generator[tuple[str | None, GuardedBase], None, None]: """Yield ``(key, item)`` pairs in insertion order.""" return ((self._key_for(item), item) for item in self._items) @property - def names(self): + def names(self) -> list[str | None]: """List of all item keys in the collection.""" return list(self.keys()) diff --git a/src/easydiffraction/core/datablock.py b/src/easydiffraction/core/datablock.py index 221845b6..6a1ef289 100644 --- a/src/easydiffraction/core/datablock.py +++ b/src/easydiffraction/core/datablock.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -13,7 +13,7 @@ class DatablockItem(GuardedBase): """Base class for items in a datablock collection.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._need_categories_update = True @@ -33,7 +33,7 @@ def __repr__(self) -> str: def _update_categories( self, - called_by_minimizer=False, + called_by_minimizer: bool = False, ) -> None: # TODO: Make abstract method and implement in subclasses. # This should call apply_symmetry and apply_constraints in the @@ -62,11 +62,13 @@ def _update_categories( self._need_categories_update = False @property - def unique_name(self): + def unique_name(self) -> str | None: + """Unique name of this datablock item (from identity).""" return self._identity.datablock_entry_name @property - def categories(self): + def categories(self) -> list: + """All category objects in this datablock by priority.""" cats = [ v for v in vars(self).values() if isinstance(v, (CategoryItem, CategoryCollection)) ] @@ -74,10 +76,8 @@ def categories(self): return sorted(cats, key=lambda c: type(c)._update_priority) @property - def parameters(self): - """All parameters from all categories contained in this - datablock. - """ + def parameters(self) -> list: + """All parameters from all categories in this datablock.""" params = [] for v in self.categories: params.extend(v.parameters) @@ -118,26 +118,29 @@ def help(self) -> None: class DatablockCollection(CollectionBase): - """Handles top-level category collections (e.g. Structures, - Experiments). + """ + Collection of top-level datablocks (e.g. Structures, Experiments). Each item is a DatablockItem. - Subclasses provide explicit ``add_from_*`` convenience methods - that delegate to the corresponding factory classmethods, then - call :meth:`add` with the resulting item. + Subclasses provide explicit ``add_from_*`` convenience methods that + delegate to the corresponding factory classmethods, then call + :meth:`add` with the resulting item. """ - def _key_for(self, item): + def _key_for(self, item: object) -> str | None: """Return the datablock-level identity key for *item*.""" return item._identity.datablock_entry_name - def add(self, item) -> None: - """Add a pre-built item to the collection. + def add(self, item: object) -> None: + """ + Add a pre-built item to the collection. - Args: - item: A ``DatablockItem`` instance (e.g. a ``Structure`` - or ``ExperimentBase`` subclass). + Parameters + ---------- + item : object + A ``DatablockItem`` instance (e.g. a ``Structure`` or + ``ExperimentBase`` subclass). """ self[item._identity.datablock_entry_name] = item @@ -148,25 +151,26 @@ def __str__(self) -> str: return f'<{name} collection ({size} items)>' @property - def unique_name(self): + def unique_name(self) -> str | None: + """Return None; collections have no unique name.""" return None @property - def parameters(self): + def parameters(self) -> list: """All parameters from all datablocks in this collection.""" params = [] for db in self._items: params.extend(db.parameters) return params - # was in class AbstractDatablock(ABC): @property def fittable_parameters(self) -> list: + """All non-constrained Parameters in this collection.""" return [p for p in self.parameters if isinstance(p, Parameter) and not p.constrained] - # was in class AbstractDatablock(ABC): @property def free_parameters(self) -> list: + """All fittable parameters that are currently marked as free.""" return [p for p in self.fittable_parameters if p.free] @property diff --git a/src/easydiffraction/core/diagnostic.py b/src/easydiffraction/core/diagnostic.py index 74c2ab3b..dc5ea4c4 100644 --- a/src/easydiffraction/core/diagnostic.py +++ b/src/easydiffraction/core/diagnostic.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Diagnostics helpers for logging validation messages. +""" +Diagnostics helpers for logging validation messages. This module centralizes human-friendly error and debug logs for attribute validation and configuration checks. @@ -19,8 +20,9 @@ class Diagnostics: # ============================================================== @staticmethod - def type_override_error(cls_name: str, expected, got): - """Report an invalid DataTypes override. + def type_override_error(cls_name: str, expected: object, got: object) -> None: + """ + Report an invalid DataTypes override. Used when descriptor and AttributeSpec types conflict. """ @@ -41,7 +43,7 @@ def type_override_error(cls_name: str, expected, got): def readonly_error( name: str, key: str | None = None, - ): + ) -> None: """Log an attempt to change a read-only attribute.""" Diagnostics._log_error( f"Cannot modify read-only attribute '{key}' of <{name}>.", @@ -53,11 +55,9 @@ def attr_error( name: str, key: str, allowed: set[str], - label='Allowed', - ): - """Log access to an unknown attribute and suggest closest - key. - """ + label: str = 'Allowed', + ) -> None: + """Log unknown attribute access and suggest closest key.""" suggestion = Diagnostics._build_suggestion(key, allowed) # Use consistent (label) logic for allowed hint = suggestion or Diagnostics._build_allowed(allowed, label=label) @@ -73,11 +73,11 @@ def attr_error( @staticmethod def type_mismatch( name: str, - value, - expected_type, - current=None, - default=None, - ): + value: object, + expected_type: object, + current: object = None, + default: object = None, + ) -> None: """Log a type mismatch and keep current or default value.""" got_type = type(value).__name__ msg = ( @@ -91,12 +91,12 @@ def type_mismatch( @staticmethod def range_mismatch( name: str, - value, - ge, - le, - current=None, - default=None, - ): + value: object, + ge: float, + le: float, + current: object = None, + default: object = None, + ) -> None: """Log range violation for a numeric value.""" msg = f'Value mismatch for <{name}>. Provided {value!r} outside [{ge}, {le}].' Diagnostics._log_error_with_fallback( @@ -106,11 +106,11 @@ def range_mismatch( @staticmethod def choice_mismatch( name: str, - value, - allowed, - current=None, - default=None, - ): + value: object, + allowed: object, + current: object = None, + default: object = None, + ) -> None: """Log an invalid choice against allowed values.""" msg = f'Value mismatch for <{name}>. Provided {value!r} is unknown.' if allowed is not None: @@ -122,11 +122,11 @@ def choice_mismatch( @staticmethod def regex_mismatch( name: str, - value, - pattern, - current=None, - default=None, - ): + value: object, + pattern: str, + current: object = None, + default: object = None, + ) -> None: """Log a regex mismatch with the expected pattern.""" msg = ( f"Value mismatch for <{name}>. Provided {value!r} does not match pattern '{pattern}'." @@ -136,24 +136,24 @@ def regex_mismatch( ) @staticmethod - def no_value(name, default): + def no_value(name: str, default: object) -> None: """Log that default will be used due to missing value.""" Diagnostics._log_debug(f'No value provided for <{name}>. Using default {default!r}.') @staticmethod - def none_value(name): + def none_value(name: str) -> None: """Log explicit None provided by a user.""" Diagnostics._log_debug(f'Using `None` explicitly provided for <{name}>.') @staticmethod - def none_value_skip_range(name): + def none_value_skip_range(name: str) -> None: """Log that range validation is skipped due to None.""" Diagnostics._log_debug( f'Skipping range validation as `None` is explicitly provided for <{name}>.' ) @staticmethod - def validated(name, value, stage: str | None = None): + def validated(name: str, value: object, stage: str | None = None) -> None: """Log that a value passed a validation stage.""" stage_info = f' {stage}' if stage else '' Diagnostics._log_debug(f'Value {value!r} for <{name}> passed{stage_info} validation.') @@ -163,17 +163,17 @@ def validated(name, value, stage: str | None = None): # ============================================================== @staticmethod - def _log_error(msg, exc_type=Exception): + def _log_error(msg: str, exc_type: type[Exception] = Exception) -> None: """Emit an error-level message via shared logger.""" log.error(msg, exc_type=exc_type) @staticmethod def _log_error_with_fallback( - msg, - current=None, - default=None, - exc_type=Exception, - ): + msg: str, + current: object = None, + default: object = None, + exc_type: type[Exception] = Exception, + ) -> None: """Emit an error message and mention kept or default value.""" if current is not None: msg += f' Keeping current {current!r}.' @@ -182,7 +182,7 @@ def _log_error_with_fallback( log.error(msg, exc_type=exc_type) @staticmethod - def _log_debug(msg): + def _log_debug(msg: str) -> None: """Emit a debug-level message via shared logger.""" log.debug(msg) @@ -191,7 +191,7 @@ def _log_debug(msg): # ============================================================== @staticmethod - def _suggest(key: str, allowed: set[str]): + def _suggest(key: str, allowed: set[str]) -> str | None: """Suggest closest allowed key using string similarity.""" if not allowed: return None @@ -200,12 +200,12 @@ def _suggest(key: str, allowed: set[str]): return matches[0] if matches else None @staticmethod - def _build_suggestion(key: str, allowed: set[str]): + def _build_suggestion(key: str, allowed: set[str]) -> str: s = Diagnostics._suggest(key, allowed) return f" Did you mean '{s}'?" if s else '' @staticmethod - def _build_allowed(allowed, label='Allowed attributes'): + def _build_allowed(allowed: object, label: str = 'Allowed attributes') -> str: # allowed may be a set, list, or other iterable if allowed: allowed_list = list(allowed) diff --git a/src/easydiffraction/core/factory.py b/src/easydiffraction/core/factory.py index 8e699085..af99217a 100644 --- a/src/easydiffraction/core/factory.py +++ b/src/easydiffraction/core/factory.py @@ -1,10 +1,10 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Base factory with registration, lookup, and context-dependent -defaults. +""" +Base factory with registration, lookup, and context-dependent defaults. -Concrete factories inherit from ``FactoryBase`` and only need to -define ``_default_rules``. +Concrete factories inherit from ``FactoryBase`` and only need to define +``_default_rules``. """ from __future__ import annotations @@ -21,12 +21,13 @@ class FactoryBase: - """Shared base for all factories. + """ + Shared base for all factories. Subclasses must set: * ``_default_rules`` -- mapping of ``frozenset`` conditions to tag - strings. Use ``frozenset(): 'tag'`` for a universal default. + strings. Use ``frozenset(): 'tag'`` for a universal default. The ``__init_subclass__`` hook ensures every subclass gets its own independent ``_registry`` list. @@ -35,7 +36,7 @@ class FactoryBase: _registry: List[Type] = [] _default_rules: Dict[FrozenSet[Tuple[str, Any]], str] = {} - def __init_subclass__(cls, **kwargs): + def __init_subclass__(cls, **kwargs: object) -> None: """Give each subclass its own independent registry and rules.""" super().__init_subclass__(**kwargs) cls._registry = [] @@ -47,14 +48,14 @@ def __init_subclass__(cls, **kwargs): # ------------------------------------------------------------------ @classmethod - def register(cls, klass): - """Class decorator to register a concrete class. + def register(cls, klass: type) -> type: + """ + Class decorator to register a concrete class. Usage:: - @SomeFactory.register - class MyClass(SomeBase): - type_info = TypeInfo(...) + @SomeFactory.register class MyClass(SomeBase): type_info = + TypeInfo(...) Returns the class unmodified. """ @@ -80,22 +81,29 @@ def supported_tags(cls) -> List[str]: # ------------------------------------------------------------------ @classmethod - def default_tag(cls, **conditions) -> str: - """Resolve the default tag for a given experimental context. + def default_tag(cls, **conditions: object) -> str: + """ + Resolve the default tag for a given experimental context. Uses *largest-subset matching*: the rule whose key is the - biggest subset of the given conditions wins. A rule with an + biggest subset of the given conditions wins. A rule with an empty key (``frozenset()``) acts as a universal fallback. - Args: - **conditions: Experimental-axis values, e.g. - ``scattering_type=ScatteringTypeEnum.BRAGG``. + Parameters + ---------- + **conditions : object + Experimental-axis values, e.g. + ``scattering_type=ScatteringTypeEnum.BRAGG``. - Returns: + Returns + ------- + str The resolved default tag string. - Raises: - ValueError: If no rule matches the given conditions. + Raises + ------ + ValueError + If no rule matches the given conditions. """ condition_set = frozenset(conditions.items()) best_match_tag: str | None = None @@ -118,15 +126,26 @@ def default_tag(cls, **conditions) -> str: # ------------------------------------------------------------------ @classmethod - def create(cls, tag: str, **kwargs) -> Any: - """Instantiate a registered class by *tag*. - - Args: - tag: ``type_info.tag`` value. - **kwargs: Forwarded to the class constructor. - - Raises: - ValueError: If *tag* is not in the registry. + def create(cls, tag: str, **kwargs: object) -> object: + """ + Instantiate a registered class by *tag*. + + Parameters + ---------- + tag : str + ``type_info.tag`` value. + **kwargs : object + Forwarded to the class constructor. + + Returns + ------- + object + A new instance of the registered class. + + Raises + ------ + ValueError + If *tag* is not in the registry. """ supported = cls._supported_map() if tag not in supported: @@ -134,13 +153,21 @@ def create(cls, tag: str, **kwargs) -> Any: return supported[tag](**kwargs) @classmethod - def create_default_for(cls, **conditions) -> Any: - """Instantiate the default class for a given context. + def create_default_for(cls, **conditions: object) -> object: + """ + Instantiate the default class for a given context. Combines ``default_tag(**conditions)`` with ``create(tag)``. - Args: - **conditions: Experimental-axis values. + Parameters + ---------- + **conditions : object + Experimental-axis values. + + Returns + ------- + object + A new instance of the default class. """ tag = cls.default_tag(**conditions) return cls.create(tag) @@ -153,20 +180,32 @@ def create_default_for(cls, **conditions) -> Any: def supported_for( cls, *, - calculator=None, - sample_form=None, - scattering_type=None, - beam_mode=None, - radiation_probe=None, + calculator: object = None, + sample_form: object = None, + scattering_type: object = None, + beam_mode: object = None, + radiation_probe: object = None, ) -> List[Type]: - """Return classes matching conditions and/or calculator. - - Args: - calculator: Optional ``CalculatorEnum`` value. - sample_form: Optional ``SampleFormEnum`` value. - scattering_type: Optional ``ScatteringTypeEnum`` value. - beam_mode: Optional ``BeamModeEnum`` value. - radiation_probe: Optional ``RadiationProbeEnum`` value. + """ + Return classes matching conditions and/or calculator. + + Parameters + ---------- + calculator : object, default=None + Optional ``CalculatorEnum`` value. + sample_form : object, default=None + Optional ``SampleFormEnum`` value. + scattering_type : object, default=None + Optional ``ScatteringTypeEnum`` value. + beam_mode : object, default=None + Optional ``BeamModeEnum`` value. + radiation_probe : object, default=None + Optional ``RadiationProbeEnum`` value. + + Returns + ------- + List[Type] + Classes matching the given conditions. """ result = [] for klass in cls._supported_map().values(): @@ -192,20 +231,27 @@ def supported_for( def show_supported( cls, *, - calculator=None, - sample_form=None, - scattering_type=None, - beam_mode=None, - radiation_probe=None, + calculator: object = None, + sample_form: object = None, + scattering_type: object = None, + beam_mode: object = None, + radiation_probe: object = None, ) -> None: - """Pretty-print a table of supported types. - - Args: - calculator: Optional ``CalculatorEnum`` filter. - sample_form: Optional ``SampleFormEnum`` filter. - scattering_type: Optional ``ScatteringTypeEnum`` filter. - beam_mode: Optional ``BeamModeEnum`` filter. - radiation_probe: Optional ``RadiationProbeEnum`` filter. + """ + Pretty-print a table of supported types. + + Parameters + ---------- + calculator : object, default=None + Optional ``CalculatorEnum`` filter. + sample_form : object, default=None + Optional ``SampleFormEnum`` filter. + scattering_type : object, default=None + Optional ``ScatteringTypeEnum`` filter. + beam_mode : object, default=None + Optional ``BeamModeEnum`` filter. + radiation_probe : object, default=None + Optional ``RadiationProbeEnum`` filter. """ matching = cls.supported_for( calculator=calculator, diff --git a/src/easydiffraction/core/guard.py b/src/easydiffraction/core/guard.py index a0033d14..17b60fb5 100644 --- a/src/easydiffraction/core/guard.py +++ b/src/easydiffraction/core/guard.py @@ -1,33 +1,35 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations from abc import ABC from abc import abstractmethod +from typing import Generator from easydiffraction.core.diagnostic import Diagnostics from easydiffraction.core.identity import Identity class GuardedBase(ABC): - """Base class enforcing controlled attribute access and parent - linkage. - """ + """Base class enforcing controlled attribute access and linkage.""" _diagnoser = Diagnostics() - def __init__(self): + def __init__(self) -> None: super().__init__() self._identity = Identity(owner=self) def __str__(self) -> str: + """Return the string representation of this object.""" return f'<{self.unique_name}>' def __repr__(self) -> str: + """Return the developer representation of this object.""" return self.__str__() - def __getattr__(self, key: str): + def __getattr__(self, key: str) -> None: + """Raise a descriptive error for unknown attribute access.""" cls = type(self) allowed = cls._public_attrs() if key not in allowed: @@ -38,7 +40,8 @@ def __getattr__(self, key: str): label='Allowed readable/writable', ) - def __setattr__(self, key: str, value): + def __setattr__(self, key: str, value: object) -> None: + """Set an attribute with access-control diagnostics.""" # Always allow private or special attributes without diagnostics if key.startswith('_'): object.__setattr__(self, key, value) @@ -70,20 +73,21 @@ def __setattr__(self, key: str, value): self._assign_attr(key, value) - def _assign_attr(self, key, value): + def _assign_attr(self, key: str, value: object) -> None: """Low-level assignment with parent linkage.""" object.__setattr__(self, key, value) if key != '_parent' and isinstance(value, GuardedBase): object.__setattr__(value, '_parent', self) @classmethod - def _iter_properties(cls): - """Iterate over all public properties defined in the class - hierarchy. + def _iter_properties(cls) -> Generator[tuple[str, property], None, None]: + """ + Iterate over all public properties in the class hierarchy. - Yields: - tuple[str, property]: Each (key, property) pair for public - attributes. + Yields + ------ + tuple[str, property] + Each (key, property) pair for public attributes. """ for base in cls.mro(): for key, attr in base.__dict__.items(): @@ -92,12 +96,12 @@ def _iter_properties(cls): yield key, attr @classmethod - def _public_attrs(cls): + def _public_attrs(cls) -> set[str]: """All public properties (read-only + writable).""" return {key for key, _ in cls._iter_properties()} @classmethod - def _public_readonly_attrs(cls): + def _public_readonly_attrs(cls) -> set[str]: """Public properties without a setter.""" return {key for key, prop in cls._iter_properties() if prop.fset is None} @@ -106,18 +110,19 @@ def _public_writable_attrs(cls) -> set[str]: """Public properties with a setter.""" return {key for key, prop in cls._iter_properties() if prop.fset is not None} - def _allowed_attrs(self, writable_only=False): + def _allowed_attrs(self, writable_only: bool = False) -> set[str]: cls = type(self) if writable_only: return cls._public_writable_attrs() return cls._public_attrs() @property - def _log_name(self): + def _log_name(self) -> str: return self.unique_name or type(self).__name__ @property - def unique_name(self): + def unique_name(self) -> str: + """Fallback unique name: the class name.""" return type(self).__name__ # @property @@ -131,23 +136,20 @@ def unique_name(self): @property @abstractmethod - def parameters(self): - """Return a list of parameter objects (to be implemented by - subclasses). - """ + def parameters(self) -> list: + """Return a list of parameters (implemented by subclasses).""" raise NotImplementedError @property @abstractmethod def as_cif(self) -> str: - """Return CIF representation of this object (to be implemented - by subclasses). - """ + """Return CIF representation (implemented by subclasses).""" raise NotImplementedError @staticmethod def _first_sentence(docstring: str | None) -> str: - """Extract the first paragraph from a docstring. + """ + Extract the first paragraph from a docstring. Returns text before the first blank line, with continuation lines joined into a single string. @@ -158,11 +160,14 @@ def _first_sentence(docstring: str | None) -> str: return ' '.join(line.strip() for line in first_para.splitlines()) @classmethod - def _iter_methods(cls): - """Iterate over public methods in the class hierarchy. + def _iter_methods(cls) -> Generator[tuple[str, object], None, None]: + """ + Iterate over public methods in the class hierarchy. - Yields: - tuple[str, callable]: Each (name, function) pair. + Yields + ------ + tuple[str, object] + Each (name, function) pair. """ seen: set = set() for base in cls.mro(): diff --git a/src/easydiffraction/core/identity.py b/src/easydiffraction/core/identity.py index 5848bf18..d64fce81 100644 --- a/src/easydiffraction/core/identity.py +++ b/src/easydiffraction/core/identity.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Identity helpers to build CIF-like hierarchical names. +""" +Identity helpers to build CIF-like hierarchical names. Used by containers and items to expose datablock/category/entry names without tight coupling. @@ -19,13 +20,13 @@ def __init__( datablock_entry: Callable | None = None, category_code: str | None = None, category_entry: Callable | None = None, - ): + ) -> None: self._owner = owner self._datablock_entry = datablock_entry self._category_code = category_code self._category_entry = category_entry - def _resolve_up(self, attr: str, visited=None): + def _resolve_up(self, attr: str, visited: set[int] | None = None) -> str | None: """Resolve attribute by walking up parent chain safely.""" if visited is None: visited = set() @@ -47,31 +48,31 @@ def _resolve_up(self, attr: str, visited=None): return None @property - def datablock_entry_name(self): + def datablock_entry_name(self) -> str | None: """Datablock entry name or None if not set.""" return self._resolve_up('datablock_entry') @datablock_entry_name.setter - def datablock_entry_name(self, func: callable): + def datablock_entry_name(self, func: callable) -> None: """Set callable returning datablock entry name.""" self._datablock_entry = func @property - def category_code(self): + def category_code(self) -> str | None: """Category code like 'atom_site' or 'background'.""" return self._resolve_up('category_code') @category_code.setter - def category_code(self, value: str): + def category_code(self, value: str) -> None: """Set category code value.""" self._category_code = value @property - def category_entry_name(self): + def category_entry_name(self) -> str | None: """Category entry name or None if not set.""" return self._resolve_up('category_entry') @category_entry_name.setter - def category_entry_name(self, func: callable): + def category_entry_name(self, func: callable) -> None: """Set callable returning category entry name.""" self._category_entry = func diff --git a/src/easydiffraction/core/metadata.py b/src/easydiffraction/core/metadata.py index 4a820515..318d64bb 100644 --- a/src/easydiffraction/core/metadata.py +++ b/src/easydiffraction/core/metadata.py @@ -1,12 +1,13 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Metadata dataclasses for factory-created classes. +""" +Metadata dataclasses for factory-created classes. Three frozen dataclasses describe a concrete class: -- ``TypeInfo`` — stable tag and human-readable description. -- ``Compatibility`` — experimental conditions (multiple fields). -- ``CalculatorSupport`` — which calculation engines can handle it. +- ``TypeInfo`` — stable tag and human-readable description. - +``Compatibility`` — experimental conditions (multiple fields). - +``CalculatorSupport`` — which calculation engines can handle it. """ from __future__ import annotations @@ -17,16 +18,19 @@ @dataclass(frozen=True) class TypeInfo: - """Stable identity and human-readable description for a factory- - created class. - - Attributes: - tag: Short, stable string identifier used for serialization, - user-facing selection, and factory lookup. Must be unique - within a factory's registry. Examples: ``'line-segment'``, - ``'pseudo-voigt'``, ``'cryspy'``. - description: One-line human-readable explanation. Used in - ``show_supported()`` tables and documentation. + """ + Stable identity and description for a factory-created class. + + Attributes + ---------- + tag : str + Short, stable string identifier used for serialization, + user-facing selection, and factory lookup. Must be unique within + a factory's registry. Examples: ``'line-segment'``, + ``'pseudo-voigt'``, ``'cryspy'``. + description : str, default='' + One-line human-readable explanation. Used in + ``show_supported()`` tables and documentation. """ tag: str @@ -35,7 +39,8 @@ class TypeInfo: @dataclass(frozen=True) class Compatibility: - """Experimental conditions under which a class can be used. + """ + Experimental conditions under which a class can be used. Each field is a frozenset of enum values representing the set of supported values for that axis. An empty frozenset means @@ -49,12 +54,13 @@ class Compatibility: def supports( self, - sample_form=None, - scattering_type=None, - beam_mode=None, - radiation_probe=None, + sample_form: object = None, + scattering_type: object = None, + beam_mode: object = None, + radiation_probe: object = None, ) -> bool: - """Check if this compatibility matches the given conditions. + """ + Check if this compatibility matches the given conditions. Each argument is an optional enum member. Returns ``True`` if every provided value is in the corresponding frozenset (or the @@ -62,10 +68,8 @@ def supports( Example:: - compat.supports( - scattering_type=ScatteringTypeEnum.BRAGG, - beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH, - ) + compat.supports( scattering_type=ScatteringTypeEnum.BRAGG, + beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH, ) """ for axis, value in ( ('sample_form', sample_form), @@ -83,22 +87,30 @@ def supports( @dataclass(frozen=True) class CalculatorSupport: - """Which calculation engines can handle this class. + """ + Which calculation engines can handle this class. - Attributes: - calculators: Frozenset of ``CalculatorEnum`` values. Empty - means "any calculator" (no restriction). + Attributes + ---------- + calculators : FrozenSet, default=frozenset() + Frozenset of ``CalculatorEnum`` values. Empty means "any + calculator" (no restriction). """ calculators: FrozenSet = frozenset() - def supports(self, calculator) -> bool: - """Check if a specific calculator can handle this class. + def supports(self, calculator: object) -> bool: + """ + Check if a specific calculator can handle this class. - Args: - calculator: A ``CalculatorEnum`` value. + Parameters + ---------- + calculator : object + A ``CalculatorEnum`` value. - Returns: + Returns + ------- + bool ``True`` if the calculator is in the set, or if the set is empty (meaning any calculator is accepted). """ diff --git a/src/easydiffraction/core/singleton.py b/src/easydiffraction/core/singleton.py index 10af4667..9033822a 100644 --- a/src/easydiffraction/core/singleton.py +++ b/src/easydiffraction/core/singleton.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from typing import Any @@ -16,7 +16,8 @@ class SingletonBase: - """Base class to implement Singleton pattern. + """ + Base class to implement Singleton pattern. Ensures only one shared instance of a class is ever created. Useful for managing shared state across the library. @@ -26,7 +27,7 @@ class SingletonBase: @classmethod def get(cls: Type[T]) -> T: - """Returns the shared instance, creating it if needed.""" + """Return the shared instance, creating it if needed.""" if cls._instance is None: cls._instance = cls() return cls._instance @@ -43,11 +44,12 @@ def __init__(self) -> None: self._uid_map: Dict[str, Any] = {} def get_uid_map(self) -> Dict[str, Any]: - """Returns the current UID-to-Parameter map.""" + """Return the current UID-to-Parameter map.""" return self._uid_map - def add_to_uid_map(self, parameter): - """Adds a single Parameter or Descriptor object to the UID map. + def add_to_uid_map(self, parameter: object) -> None: + """ + Add a single Parameter or Descriptor object to the UID map. Only Descriptor or Parameter instances are allowed (not Components or others). @@ -61,8 +63,9 @@ def add_to_uid_map(self, parameter): ) self._uid_map[parameter.uid] = parameter - def replace_uid(self, old_uid, new_uid): - """Replaces an existing UID key in the UID map with a new UID. + def replace_uid(self, old_uid: str, new_uid: str) -> None: + """ + Replace an existing UID key in the UID map with a new UID. Moves the associated parameter from old_uid to new_uid. Raises a KeyError if the old_uid doesn't exist. @@ -82,8 +85,8 @@ def replace_uid(self, old_uid, new_uid): # TODO: Implement changing atrr '.constrained' back to False # when removing constraints class ConstraintsHandler(SingletonBase): - """Manages user-defined parameter constraints using aliases and - expressions. + """ + Manage parameter constraints using aliases and expressions. Uses the asteval interpreter for safe evaluation of mathematical expressions. Constraints are defined as: lhs_alias = @@ -102,29 +105,27 @@ def __init__(self) -> None: # Internally parsed constraints as (lhs_alias, rhs_expr) tuples self._parsed_constraints: List[Tuple[str, str]] = [] - def set_aliases(self, aliases): - """Sets the alias map (name → parameter wrapper). + def set_aliases(self, aliases: object) -> None: + """ + Set the alias map (name → parameter wrapper). Called when user registers parameter aliases like: - alias='biso_La', param=model.atom_sites['La'].b_iso + alias='biso_La', param=model.atom_sites['La'].b_iso """ self._alias_to_param = dict(aliases.items()) - def set_constraints(self, constraints): - """Sets the constraints and triggers parsing into internal - format. + def set_constraints(self, constraints: object) -> None: + """ + Set the constraints and triggers parsing into internal format. - Called when user registers expressions like: - lhs_alias='occ_Ba', rhs_expr='1 - occ_La' + Called when user registers expressions like: lhs_alias='occ_Ba', + rhs_expr='1 - occ_La' """ self._constraints = constraints._items self._parse_constraints() def _parse_constraints(self) -> None: - """Converts raw expression input into a normalized internal list - of (lhs_alias, rhs_expr) pairs, stripping whitespace and - skipping invalid entries. - """ + """Parse raw expressions into (lhs_alias, rhs_expr) pairs.""" self._parsed_constraints = [] for expr_obj in self._constraints: @@ -136,12 +137,11 @@ def _parse_constraints(self) -> None: self._parsed_constraints.append(constraint) def apply(self) -> None: - """Evaluates constraints and applies them to dependent - parameters. + """ + Evaluate constraints and applies them to dependent parameters. - For each constraint: - - Evaluate RHS using current values of aliases - - Locate the dependent parameter by alias → uid → param + For each constraint: - Evaluate RHS using current values of + aliases - Locate the dependent parameter by alias → uid → param - Update its value and mark it as constrained """ if not self._parsed_constraints: diff --git a/src/easydiffraction/core/validation.py b/src/easydiffraction/core/validation.py index 1fd268d9..d3c411af 100644 --- a/src/easydiffraction/core/validation.py +++ b/src/easydiffraction/core/validation.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Lightweight runtime validation utilities. +""" +Lightweight runtime validation utilities. Provides DataTypes, type/content validators, and AttributeSpec used by descriptors and parameters. Only documentation was added here. @@ -23,6 +24,8 @@ # TODO: MkDocs doesn't unpack types class DataTypeHints: + """Type hint aliases for numeric, string, and boolean types.""" + Numeric = int | float | np.integer | np.floating String = str Bool = bool @@ -32,16 +35,19 @@ class DataTypeHints: class DataTypes(Enum): + """Enumeration of supported data types for descriptors.""" + NUMERIC = (int, float, np.integer, np.floating) STRING = (str,) BOOL = (bool,) ANY = (object,) # fallback for unconstrained - def __str__(self): + def __str__(self) -> str: + """Return the lowercase name of the data type.""" return self.name.lower() @property - def expected_type(self): + def expected_type(self) -> tuple: """Convenience alias for tuple of allowed Python types.""" return self.value @@ -59,7 +65,8 @@ class ValidationStage(Enum): MEMBERSHIP = auto() REGEX = auto() - def __str__(self): + def __str__(self) -> str: + """Return the lowercase name of the validation stage.""" return self.name.lower() @@ -72,8 +79,15 @@ class ValidatorBase(ABC): """Abstract base class for all validators.""" @abstractmethod - def validated(self, value, name, default=None, current=None): - """Return a validated value or fallback. + def validated( + self, + value: object, + name: str, + default: object = None, + current: object = None, + ) -> object: + """ + Return a validated value or fallback. Subclasses must implement this method. """ @@ -81,9 +95,9 @@ def validated(self, value, name, default=None, current=None): def _fallback( self, - current=None, - default=None, - ): + current: object = None, + default: object = None, + ) -> object: """Return current if set, else default.""" return current if current is not None else default @@ -94,7 +108,7 @@ def _fallback( class TypeValidator(ValidatorBase): """Ensure a value is of the expected data type.""" - def __init__(self, expected_type: DataTypes): + def __init__(self, expected_type: DataTypes) -> None: if isinstance(expected_type, DataTypes): self.expected_type = expected_type self.expected_label = str(expected_type) @@ -103,13 +117,14 @@ def __init__(self, expected_type: DataTypes): def validated( self, - value, - name, - default=None, - current=None, - allow_none=False, - ): - """Validate type and return value or fallback. + value: object, + name: str, + default: object = None, + current: object = None, + allow_none: bool = False, + ) -> object: + """ + Validate type and return value or fallback. If allow_none is True, None bypasses content checks. """ @@ -151,18 +166,18 @@ class RangeValidator(ValidatorBase): def __init__( self, *, - ge=-np.inf, - le=np.inf, - ): + ge: float = -np.inf, + le: float = np.inf, + ) -> None: self.ge, self.le = ge, le def validated( self, - value, - name, - default=None, - current=None, - ): + value: object, + name: str, + default: object = None, + current: object = None, + ) -> object: """Validate range and return value or fallback.""" if not (self.ge <= value <= self.le): Diagnostics.range_mismatch( @@ -187,22 +202,23 @@ def validated( class MembershipValidator(ValidatorBase): - """Ensure that a value is among allowed choices. + """ + Ensure that a value is among allowed choices. - `allowed` may be an iterable or a callable returning a collection. + ``allowed`` may be an iterable or a callable returning a collection. """ - def __init__(self, allowed): + def __init__(self, allowed: object) -> None: # Do not convert immediately to list — may be callable self.allowed = allowed def validated( self, - value, - name, - default=None, - current=None, - ): + value: object, + name: str, + default: object = None, + current: object = None, + ) -> object: """Validate membership and return value or fallback.""" # Dynamically evaluate allowed if callable (e.g. lambda) allowed_values = self.allowed() if callable(self.allowed) else self.allowed @@ -231,16 +247,16 @@ def validated( class RegexValidator(ValidatorBase): """Ensure that a string matches a given regular expression.""" - def __init__(self, pattern): + def __init__(self, pattern: str) -> None: self.pattern = re.compile(pattern) def validated( self, - value, - name, - default=None, - current=None, - ): + value: object, + name: str, + default: object = None, + current: object = None, + ) -> object: """Validate regex and return value or fallback.""" if not self.pattern.fullmatch(value): Diagnostics.regex_mismatch( @@ -271,11 +287,11 @@ class AttributeSpec: def __init__( self, *, - default=None, - data_type=None, - validator=None, + default: object = None, + data_type: DataTypes | None = None, + validator: ValidatorBase | None = None, allow_none: bool = False, - ): + ) -> None: self.default = default self.allow_none = allow_none self._data_type_validator = TypeValidator(data_type) if data_type else None @@ -283,11 +299,12 @@ def __init__( def validated( self, - value, - name, - current=None, - ): - """Validate through type and content validators. + value: object, + name: str, + current: object = None, + ) -> object: + """ + Validate through type and content validators. Returns validated value, possibly default or current if errors occur. None may short-circuit further checks when allowed. diff --git a/src/easydiffraction/core/variable.py b/src/easydiffraction/core/variable.py index 6b33a39e..8e06b2a9 100644 --- a/src/easydiffraction/core/variable.py +++ b/src/easydiffraction/core/variable.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -6,7 +6,6 @@ import secrets import string from typing import TYPE_CHECKING -from typing import Any import numpy as np @@ -27,18 +26,14 @@ class GenericDescriptorBase(GuardedBase): - """Base class for all parameter-like descriptors. + """ + Base class for all parameter-like descriptors. A descriptor encapsulates a typed value with validation, human-readable name/description and a globally unique identifier that is stable across the session. Concrete subclasses specialize - the expected data type and can extend the public API with - additional behavior (e.g. units). - - Attributes: - name: Local parameter name (e.g. 'a', 'b_iso'). - description: Optional human-readable description. - uid: Stable random identifier for external references. + the expected data type and can extend the public API with additional + behavior (e.g. units). """ _BOOL_SPEC_TEMPLATE = AttributeSpec( @@ -52,13 +47,18 @@ def __init__( value_spec: AttributeSpec, name: str, description: str = None, - ): - """Initialize the descriptor with validation and identity. - - Args: - value_spec: Validation specification for the value. - name: Local name of the descriptor within its category. - description: Optional human-readable description. + ) -> None: + """ + Initialize the descriptor with validation and identity. + + Parameters + ---------- + value_spec : AttributeSpec + Validation specification for the value. + name : str + Local name of the descriptor within its category. + description : str, default=None + Optional human-readable description. """ super().__init__() @@ -98,6 +98,7 @@ def __init__( self._value = default() if callable(default) else default def __str__(self) -> str: + """Return the string representation of this descriptor.""" return f'<{self.unique_name} = {self.value!r}>' @property @@ -106,11 +107,8 @@ def name(self) -> str: return self._name @property - def unique_name(self): - """Fully qualified name including datablock, category and entry - name. - """ - # 7c: Use filter(None, [...]) + def unique_name(self) -> str: + """Fully qualified name: datablock, category and entry.""" parts = [ self._identity.datablock_entry_name, self._identity.category_code, @@ -119,10 +117,8 @@ def unique_name(self): ] return '.'.join(filter(None, parts)) - def _parent_of_type(self, cls): - """Walk up the parent chain and return the first parent of type - `cls`. - """ + def _parent_of_type(self, cls: type) -> object | None: + """Traverse parents and return the first of type cls.""" obj = getattr(self, '_parent', None) visited = set() while obj is not None and id(obj) not in visited: @@ -132,19 +128,19 @@ def _parent_of_type(self, cls): obj = getattr(obj, '_parent', None) return None - def _datablock_item(self): + def _datablock_item(self) -> object | None: """Return the DatablockItem ancestor, if any.""" from easydiffraction.core.datablock import DatablockItem return self._parent_of_type(DatablockItem) @property - def value(self): + def value(self) -> object: """Current validated value.""" return self._value @value.setter - def value(self, v): + def value(self, v: object) -> None: """Set a new value after validating against the spec.""" # Do nothing if the value is unchanged if self._value == v: @@ -163,19 +159,20 @@ def value(self, v): if parent_datablock is not None: parent_datablock._need_categories_update = True - def _set_value_from_minimizer(self, v) -> None: - """Set the value from a minimizer, bypassing validation. + def _set_value_from_minimizer(self, v: object) -> None: + """ + Set the value from a minimizer, bypassing validation. - Writes ``_value`` directly — no type or range checks — but - still marks the owning :class:`DatablockItem` dirty so that + Writes ``_value`` directly — no type or range checks — but still + marks the owning :class:`DatablockItem` dirty so that ``_update_categories()`` knows work is needed. This exists because: 1. Physical-range validators (e.g. intensity ≥ 0) would reject - trial values the minimizer needs to explore. - 2. Validation overhead is measurable over thousands of - objective-function evaluations. + trial values the minimizer needs to explore. 2. Validation + overhead is measurable over thousands of objective-function + evaluations. """ self._value = v parent_datablock = self._datablock_item() @@ -183,13 +180,14 @@ def _set_value_from_minimizer(self, v) -> None: parent_datablock._need_categories_update = True @property - def description(self): + def description(self) -> str | None: """Optional human-readable description.""" return self._description @property - def parameters(self): - """Return a flat list of parameters contained by this object. + def parameters(self) -> list[GenericDescriptorBase]: + """ + Return a flat list of parameters contained by this object. For a single descriptor, it returns a one-element list with itself. Composite objects override this to flatten nested @@ -202,7 +200,7 @@ def as_cif(self) -> str: """Serialize this descriptor to a CIF-formatted string.""" return param_to_cif(self) - def from_cif(self, block, idx=0): + def from_cif(self, block: object, idx: int = 0) -> None: """Populate this parameter from a CIF block.""" param_from_cif(self, block, idx) @@ -211,11 +209,13 @@ def from_cif(self, block, idx=0): class GenericStringDescriptor(GenericDescriptorBase): + """Base descriptor that constrains values to strings.""" + _value_type = DataTypes.STRING def __init__( self, - **kwargs: Any, + **kwargs: object, ) -> None: super().__init__(**kwargs) @@ -224,18 +224,21 @@ def __init__( class GenericNumericDescriptor(GenericDescriptorBase): + """Base descriptor that constrains values to numbers.""" + _value_type = DataTypes.NUMERIC def __init__( self, *, units: str = '', - **kwargs: Any, + **kwargs: object, ) -> None: super().__init__(**kwargs) self._units: str = units def __str__(self) -> str: + """Return the string representation including units.""" s: str = super().__str__() s = s[1:-1] # strip <> if self.units: @@ -252,7 +255,8 @@ def units(self) -> str: class GenericParameter(GenericNumericDescriptor): - """Numeric descriptor extended with fitting-related attributes. + """ + Numeric descriptor extended with fitting-related attributes. Adds standard attributes used by minimizers: "free" flag, uncertainty, bounds and an optional starting value. Subclasses can @@ -261,8 +265,8 @@ class GenericParameter(GenericNumericDescriptor): def __init__( self, - **kwargs: Any, - ): + **kwargs: object, + ) -> None: super().__init__(**kwargs) # Initial validated states @@ -287,6 +291,7 @@ def __init__( UidMapHandler.get().add_to_uid_map(self) def __str__(self) -> str: + """Return string representation with uncertainty and free.""" s = GenericDescriptorBase.__str__(self) s = s[1:-1] # strip <> if self.uncertainty is not None: @@ -302,23 +307,24 @@ def _generate_uid(length: int = 16) -> str: return ''.join(secrets.choice(letters) for _ in range(length)) @property - def uid(self): + def uid(self) -> str: """Stable random identifier for this descriptor.""" return self._uid @property - def _minimizer_uid(self): + def _minimizer_uid(self) -> str: """Variant of uid that is safe for minimizer engines.""" # return self.unique_name.replace('.', '__') return self.uid @property - def constrained(self): + def constrained(self) -> bool: """Whether this parameter is part of a constraint expression.""" return self._constrained - def _set_value_constrained(self, v) -> None: - """Set the value from a constraint expression. + def _set_value_constrained(self, v: object) -> None: + """ + Set the value from a constraint expression. Validates against the spec, marks the parent datablock dirty, and flags the parameter as constrained. Used exclusively by @@ -328,50 +334,48 @@ def _set_value_constrained(self, v) -> None: self._constrained = True @property - def free(self): + def free(self) -> bool: """Whether this parameter is currently varied during fitting.""" return self._free @free.setter - def free(self, v): + def free(self, v: bool) -> None: """Set the "free" flag after validation.""" self._free = self._free_spec.validated( v, name=f'{self.unique_name}.free', current=self._free ) @property - def uncertainty(self): - """Estimated standard uncertainty of the fitted value, if - available. - """ + def uncertainty(self) -> float | None: + """Estimated standard uncertainty of the fitted value.""" return self._uncertainty @uncertainty.setter - def uncertainty(self, v): + def uncertainty(self, v: float | None) -> None: """Set the uncertainty value (must be non-negative or None).""" self._uncertainty = self._uncertainty_spec.validated( v, name=f'{self.unique_name}.uncertainty', current=self._uncertainty ) @property - def fit_min(self): + def fit_min(self) -> float: """Lower fitting bound.""" return self._fit_min @fit_min.setter - def fit_min(self, v): + def fit_min(self, v: float) -> None: """Set the lower bound for the parameter value.""" self._fit_min = self._fit_min_spec.validated( v, name=f'{self.unique_name}.fit_min', current=self._fit_min ) @property - def fit_max(self): + def fit_max(self) -> float: """Upper fitting bound.""" return self._fit_max @fit_max.setter - def fit_max(self, v): + def fit_max(self, v: float) -> None: """Set the upper bound for the parameter value.""" self._fit_max = self._fit_max_spec.validated( v, name=f'{self.unique_name}.fit_max', current=self._fit_max @@ -382,17 +386,23 @@ def fit_max(self, v): class StringDescriptor(GenericStringDescriptor): + """String descriptor bound to a CIF handler.""" + def __init__( self, *, cif_handler: CifHandler, - **kwargs: Any, + **kwargs: object, ) -> None: - """String descriptor bound to a CIF handler. - - Args: - cif_handler: Object that tracks CIF identifiers. - **kwargs: Forwarded to GenericStringDescriptor. + """ + Initialize a string descriptor bound to a CIF handler. + + Parameters + ---------- + cif_handler : CifHandler + Object that tracks CIF identifiers. + **kwargs : object + Forwarded to GenericStringDescriptor. """ super().__init__(**kwargs) self._cif_handler = cif_handler @@ -403,17 +413,23 @@ def __init__( class NumericDescriptor(GenericNumericDescriptor): + """Numeric descriptor bound to a CIF handler.""" + def __init__( self, *, cif_handler: CifHandler, - **kwargs: Any, + **kwargs: object, ) -> None: - """Numeric descriptor bound to a CIF handler. - - Args: - cif_handler: Object that tracks CIF identifiers. - **kwargs: Forwarded to GenericNumericDescriptor. + """ + Numeric descriptor bound to a CIF handler. + + Parameters + ---------- + cif_handler : CifHandler + Object that tracks CIF identifiers. + **kwargs : object + Forwarded to GenericNumericDescriptor. """ super().__init__(**kwargs) self._cif_handler = cif_handler @@ -424,17 +440,23 @@ def __init__( class Parameter(GenericParameter): + """Fittable parameter bound to a CIF handler.""" + def __init__( self, *, cif_handler: CifHandler, - **kwargs: Any, + **kwargs: object, ) -> None: - """Fittable parameter bound to a CIF handler. - - Args: - cif_handler: Object that tracks CIF identifiers. - **kwargs: Forwarded to GenericParameter. + """ + Fittable parameter bound to a CIF handler. + + Parameters + ---------- + cif_handler : CifHandler + Object that tracks CIF identifiers. + **kwargs : object + Forwarded to GenericParameter. """ super().__init__(**kwargs) self._cif_handler = cif_handler diff --git a/src/easydiffraction/crystallography/__init__.py b/src/easydiffraction/crystallography/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/crystallography/__init__.py +++ b/src/easydiffraction/crystallography/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/crystallography/crystallography.py b/src/easydiffraction/crystallography/crystallography.py index c7ff6203..bc90383f 100644 --- a/src/easydiffraction/crystallography/crystallography.py +++ b/src/easydiffraction/crystallography/crystallography.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from typing import Any @@ -21,14 +21,19 @@ def apply_cell_symmetry_constraints( cell: Dict[str, float], name_hm: str, ) -> Dict[str, float]: - """Apply symmetry constraints to unit cell parameters based on space - group. - - Args: - cell: Dictionary containing lattice parameters. - name_hm: Hermann-Mauguin symbol of the space group. - - Returns: + """ + Apply symmetry constraints to unit cell parameters. + + Parameters + ---------- + cell : Dict[str, float] + Dictionary containing lattice parameters. + name_hm : str + Hermann-Mauguin symbol of the space group. + + Returns + ------- + Dict[str, float] The cell dictionary with applied symmetry constraints. """ it_number = get_it_number_by_name_hm_short(name_hm) @@ -90,16 +95,23 @@ def apply_atom_site_symmetry_constraints( coord_code: int, wyckoff_letter: str, ) -> Dict[str, Any]: - """Apply symmetry constraints to atomic coordinates based on site - symmetry. - - Args: - atom_site: Dictionary containing atom position data. - name_hm: Hermann-Mauguin symbol of the space group. - coord_code: Coordinate system code. - wyckoff_letter: Wyckoff position letter. - - Returns: + """ + Apply symmetry constraints to atom site coordinates. + + Parameters + ---------- + atom_site : Dict[str, Any] + Dictionary containing atom position data. + name_hm : str + Hermann-Mauguin symbol of the space group. + coord_code : int + Coordinate system code. + wyckoff_letter : str + Wyckoff position letter. + + Returns + ------- + Dict[str, Any] The atom_site dictionary with applied symmetry constraints. """ it_number = get_it_number_by_name_hm_short(name_hm) diff --git a/src/easydiffraction/crystallography/space_groups.py b/src/easydiffraction/crystallography/space_groups.py index 114b3467..4047b8c5 100644 --- a/src/easydiffraction/crystallography/space_groups.py +++ b/src/easydiffraction/crystallography/space_groups.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Space group reference data. +""" +Space group reference data. Loads a gzipped, packaged pickle with crystallographic space-group information. The file is part of the distribution; user input is not @@ -10,11 +11,11 @@ import gzip import pickle # noqa: S403 - trusted internal pickle file (package data only) from pathlib import Path -from typing import Any -def _restricted_pickle_load(file_obj) -> Any: - """Load pickle data from an internal gz file (trusted boundary). +def _restricted_pickle_load(file_obj: object) -> object: + """ + Load pickle data from an internal gz file (trusted boundary). The archive lives in the package; no user-controlled input enters this function. If distribution process changes, revisit. @@ -23,7 +24,7 @@ def _restricted_pickle_load(file_obj) -> Any: return data -def _load(): +def _load() -> object: """Load space-group data from the packaged archive.""" path = Path(__file__).with_name('space_groups.pkl.gz') with gzip.open(path, 'rb') as f: diff --git a/src/easydiffraction/datablocks/__init__.py b/src/easydiffraction/datablocks/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/datablocks/__init__.py +++ b/src/easydiffraction/datablocks/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/datablocks/experiment/__init__.py b/src/easydiffraction/datablocks/experiment/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/datablocks/experiment/__init__.py +++ b/src/easydiffraction/datablocks/experiment/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/datablocks/experiment/categories/__init__.py b/src/easydiffraction/datablocks/experiment/categories/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/datablocks/experiment/categories/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/datablocks/experiment/categories/background/__init__.py b/src/easydiffraction/datablocks/experiment/categories/background/__init__.py index b7b3b47d..7ffe8f22 100644 --- a/src/easydiffraction/datablocks/experiment/categories/background/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/background/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.background.chebyshev import ( diff --git a/src/easydiffraction/datablocks/experiment/categories/background/base.py b/src/easydiffraction/datablocks/experiment/categories/background/base.py index 78cc5ef1..913cb764 100644 --- a/src/easydiffraction/datablocks/experiment/categories/background/base.py +++ b/src/easydiffraction/datablocks/experiment/categories/background/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -9,7 +9,8 @@ class BackgroundBase(CategoryCollection): - """Abstract base for background subcategories in experiments. + """ + Abstract base for background subcategories in experiments. Concrete implementations provide parameterized background models and compute background intensities on the experiment grid. diff --git a/src/easydiffraction/datablocks/experiment/categories/background/chebyshev.py b/src/easydiffraction/datablocks/experiment/categories/background/chebyshev.py index 4a6a714d..098c4268 100644 --- a/src/easydiffraction/datablocks/experiment/categories/background/chebyshev.py +++ b/src/easydiffraction/datablocks/experiment/categories/background/chebyshev.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Chebyshev polynomial background model. +""" +Chebyshev polynomial background model. Provides a collection of polynomial terms and evaluation helpers. """ @@ -34,7 +35,8 @@ class PolynomialTerm(CategoryItem): - """Chebyshev polynomial term. + """ + Chebyshev polynomial term. New public attribute names: ``order`` and ``coef`` replacing the longer ``chebyshev_order`` / ``chebyshev_coef``. Backward-compatible @@ -47,7 +49,7 @@ def __init__(self) -> None: self._id = StringDescriptor( name='id', - description='Identifier for this background polynomial term.', + description='Identifier for this background polynomial term', value_spec=AttributeSpec( default='0', # TODO: the following pattern is valid for dict key @@ -84,32 +86,54 @@ def __init__(self) -> None: # ------------------------------------------------------------------ @property - def id(self): + def id(self) -> StringDescriptor: + """ + Identifier for this background polynomial term. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._id @id.setter - def id(self, value): + def id(self, value: str) -> None: self._id.value = value @property - def order(self): + def order(self) -> NumericDescriptor: + """ + Order used in a Chebyshev polynomial background term. + + Reading this property returns the underlying + ``NumericDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._order @order.setter - def order(self, value): + def order(self, value: float) -> None: self._order.value = value @property - def coef(self): + def coef(self) -> Parameter: + """ + Coefficient used in a Chebyshev polynomial background term. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._coef @coef.setter - def coef(self, value): + def coef(self, value: float) -> None: self._coef.value = value @BackgroundFactory.register class ChebyshevPolynomialBackground(BackgroundBase): + """Chebyshev polynomial background model.""" + type_info = TypeInfo( tag='chebyshev', description='Chebyshev polynomial background', @@ -127,10 +151,10 @@ class ChebyshevPolynomialBackground(BackgroundBase): }), ) - def __init__(self): + def __init__(self) -> None: super().__init__(item_type=PolynomialTerm) - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: """Evaluate polynomial background over x data.""" del called_by_minimizer diff --git a/src/easydiffraction/datablocks/experiment/categories/background/enums.py b/src/easydiffraction/datablocks/experiment/categories/background/enums.py index 2356702a..9e78effc 100644 --- a/src/easydiffraction/datablocks/experiment/categories/background/enums.py +++ b/src/easydiffraction/datablocks/experiment/categories/background/enums.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Enumerations for background model types.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/background/factory.py b/src/easydiffraction/datablocks/experiment/categories/background/factory.py index c52b7dd7..c4d300c8 100644 --- a/src/easydiffraction/datablocks/experiment/categories/background/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/background/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Background factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/background/line_segment.py b/src/easydiffraction/datablocks/experiment/categories/background/line_segment.py index 822f6e0d..2f0a5496 100644 --- a/src/easydiffraction/datablocks/experiment/categories/background/line_segment.py +++ b/src/easydiffraction/datablocks/experiment/categories/background/line_segment.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Line-segment background model. +""" +Line-segment background model. Interpolate user-specified points to form a background curve. """ @@ -40,7 +41,7 @@ def __init__(self) -> None: self._id = StringDescriptor( name='id', - description='Identifier for this background line segment.', + description='Identifier for this background line segment', value_spec=AttributeSpec( default='0', # TODO: the following pattern is valid for dict key @@ -52,10 +53,7 @@ def __init__(self) -> None: ) self._x = NumericDescriptor( name='x', - description=( - 'X-coordinates used to create many straight-line segments ' - 'representing the background in a calculated diffractogram.' - ), + description='X-coordinates used to create many straight-line segments', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -69,10 +67,7 @@ def __init__(self) -> None: ) self._y = Parameter( name='y', # TODO: rename to intensity - description=( - 'Intensity used to create many straight-line segments ' - 'representing the background in a calculated diffractogram' - ), + description='Intensity used to create many straight-line segments', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -93,32 +88,54 @@ def __init__(self) -> None: # ------------------------------------------------------------------ @property - def id(self): + def id(self) -> StringDescriptor: + """ + Identifier for this background line segment. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._id @id.setter - def id(self, value): + def id(self, value: str) -> None: self._id.value = value @property - def x(self): + def x(self) -> NumericDescriptor: + """ + X-coordinates used to create many straight-line segments. + + Reading this property returns the underlying + ``NumericDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._x @x.setter - def x(self, value): + def x(self, value: float) -> None: self._x.value = value @property - def y(self): + def y(self) -> Parameter: + """ + Intensity used to create many straight-line segments. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._y @y.setter - def y(self, value): + def y(self, value: float) -> None: self._y.value = value @BackgroundFactory.register class LineSegmentBackground(BackgroundBase): + """Linear-interpolation background between user-defined points.""" + type_info = TypeInfo( tag='line-segment', description='Linear interpolation between points', @@ -130,10 +147,10 @@ class LineSegmentBackground(BackgroundBase): calculators=frozenset({CalculatorEnum.CRYSPY, CalculatorEnum.CRYSFML}), ) - def __init__(self): + def __init__(self) -> None: super().__init__(item_type=LineSegment) - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: """Interpolate background points over x data.""" del called_by_minimizer diff --git a/src/easydiffraction/datablocks/experiment/categories/data/__init__.py b/src/easydiffraction/datablocks/experiment/categories/data/__init__.py index c228ecd8..3599f3b5 100644 --- a/src/easydiffraction/datablocks/experiment/categories/data/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/data/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.data.bragg_pd import PdCwlData diff --git a/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py b/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py index 73402d30..883f7f80 100644 --- a/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py +++ b/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -29,12 +29,12 @@ class PdDataPointBaseMixin: """Single base data point mixin for powder diffraction data.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._point_id = StringDescriptor( name='point_id', - description='Identifier for this data point in the dataset.', + description='Identifier for this data point in the dataset', value_spec=AttributeSpec( default='0', # TODO: the following pattern is valid for dict key @@ -50,7 +50,7 @@ def __init__(self): ) self._d_spacing = NumericDescriptor( name='d_spacing', - description='d-spacing value corresponding to this data point.', + description='d-spacing value corresponding to this data point', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -59,7 +59,7 @@ def __init__(self): ) self._intensity_meas = NumericDescriptor( name='intensity_meas', - description='Intensity recorded at each measurement point as a function of angle/time', + description='Intensity recorded at each measurement point (angle/time)', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -73,7 +73,7 @@ def __init__(self): ) self._intensity_meas_su = NumericDescriptor( name='intensity_meas_su', - description='Standard uncertainty of the measured intensity at this data point.', + description='Standard uncertainty of the measured intensity at this point', value_spec=AttributeSpec( default=1.0, validator=RangeValidator(ge=0), @@ -87,7 +87,7 @@ def __init__(self): ) self._intensity_calc = NumericDescriptor( name='intensity_calc', - description='Intensity value for a computed diffractogram at this data point.', + description='Intensity of a computed diffractogram at this point', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -96,7 +96,7 @@ def __init__(self): ) self._intensity_bkg = NumericDescriptor( name='intensity_bkg', - description='Intensity value for a computed background at this data point.', + description='Intensity of a computed background at this point', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -105,7 +105,7 @@ def __init__(self): ) self._calc_status = StringDescriptor( name='calc_status', - description='Status code of the data point in the calculation process.', + description='Status code of the data point in the calculation process', value_spec=AttributeSpec( default='incl', # TODO: Make Enum validator=MembershipValidator(allowed=['incl', 'excl']), @@ -123,39 +123,79 @@ def __init__(self): @property def point_id(self) -> StringDescriptor: + """ + Identifier for this data point in the dataset. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._point_id @property def d_spacing(self) -> NumericDescriptor: + """ + d-spacing value corresponding to this data point. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._d_spacing @property def intensity_meas(self) -> NumericDescriptor: + """ + Intensity recorded at each measurement point (angle/time). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._intensity_meas @property def intensity_meas_su(self) -> NumericDescriptor: + """ + Standard uncertainty of the measured intensity at this point. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._intensity_meas_su @property def intensity_calc(self) -> NumericDescriptor: + """ + Intensity of a computed diffractogram at this point. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._intensity_calc @property def intensity_bkg(self) -> NumericDescriptor: + """ + Intensity of a computed background at this point. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._intensity_bkg @property def calc_status(self) -> StringDescriptor: + """ + Status code of the data point in the calculation process. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._calc_status class PdCwlDataPointMixin: - """Mixin for powder diffraction data points with constant - wavelength. - """ + """Mixin for CWL powder diffraction data points.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._two_theta = NumericDescriptor( @@ -179,14 +219,20 @@ def __init__(self): # ------------------------------------------------------------------ @property - def two_theta(self): + def two_theta(self) -> NumericDescriptor: + """ + Measured 2θ diffraction angle (deg). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._two_theta class PdTofDataPointMixin: """Mixin for powder diffraction data points with time-of-flight.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._time_of_flight = NumericDescriptor( @@ -205,7 +251,13 @@ def __init__(self): # ------------------------------------------------------------------ @property - def time_of_flight(self): + def time_of_flight(self) -> NumericDescriptor: + """ + Measured time for time-of-flight neutron measurement (µs). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._time_of_flight @@ -222,9 +274,7 @@ class PdCwlDataPoint( # But also says, that in fact, it is just for consistency. And both # orders work. ): - """Powder diffraction data point for constant-wavelength - experiments. - """ + """Powder diffraction data point for CWL experiments.""" def __init__(self) -> None: super().__init__() @@ -246,6 +296,8 @@ def __init__(self) -> None: class PdDataBase(CategoryCollection): + """Base class for powder diffraction data collections.""" + # TODO: ??? # Redefine update priority to ensure data updated after other @@ -260,42 +312,40 @@ class PdDataBase(CategoryCollection): # Should be set only once - def _set_point_id(self, values) -> None: - """Helper method to set point IDs.""" + def _set_point_id(self, values: object) -> None: + """Set point IDs.""" for p, v in zip(self._items, values, strict=True): p.point_id._value = v - def _set_intensity_meas(self, values) -> None: - """Helper method to set measured intensity.""" + def _set_intensity_meas(self, values: object) -> None: + """Set measured intensity.""" for p, v in zip(self._items, values, strict=True): p.intensity_meas._value = v - def _set_intensity_meas_su(self, values) -> None: - """Helper method to set standard uncertainty of measured - intensity. - """ + def _set_intensity_meas_su(self, values: object) -> None: + """Set standard uncertainty of measured intensity values.""" for p, v in zip(self._items, values, strict=True): p.intensity_meas_su._value = v # Can be set multiple times - def _set_d_spacing(self, values) -> None: - """Helper method to set d-spacing values.""" + def _set_d_spacing(self, values: object) -> None: + """Set d-spacing values.""" for p, v in zip(self._calc_items, values, strict=True): p.d_spacing._value = v - def _set_intensity_calc(self, values) -> None: - """Helper method to set calculated intensity.""" + def _set_intensity_calc(self, values: object) -> None: + """Set calculated intensity.""" for p, v in zip(self._calc_items, values, strict=True): p.intensity_calc._value = v - def _set_intensity_bkg(self, values) -> None: - """Helper method to set background intensity.""" + def _set_intensity_bkg(self, values: object) -> None: + """Set background intensity.""" for p, v in zip(self._calc_items, values, strict=True): p.intensity_bkg._value = v - def _set_calc_status(self, values) -> None: - """Helper method to set refinement status.""" + def _set_calc_status(self, values: object) -> None: + """Set refinement status.""" for p, v in zip(self._items, values, strict=True): if v: p.calc_status._value = 'incl' @@ -311,13 +361,13 @@ def _calc_mask(self) -> np.ndarray: return self.calc_status == 'incl' @property - def _calc_items(self): + def _calc_items(self) -> list: """Get only the items included in calculations.""" return [item for item, mask in zip(self._items, self._calc_mask, strict=False) if mask] # Misc - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: experiment = self._parent experiments = experiment._parent project = experiments._parent @@ -353,6 +403,7 @@ def _update(self, called_by_minimizer=False): @property def calc_status(self) -> np.ndarray: + """Refinement-status flags for each data point as an array.""" return np.fromiter( (p.calc_status.value for p in self._items), dtype=object, # TODO: needed? DataTypes.NUMERIC? @@ -360,6 +411,7 @@ def calc_status(self) -> np.ndarray: @property def d_spacing(self) -> np.ndarray: + """D-spacing values for active (non-excluded) data points.""" return np.fromiter( (p.d_spacing.value for p in self._calc_items), dtype=float, # TODO: needed? DataTypes.NUMERIC? @@ -367,6 +419,7 @@ def d_spacing(self) -> np.ndarray: @property def intensity_meas(self) -> np.ndarray: + """Measured intensities for active data points.""" return np.fromiter( (p.intensity_meas.value for p in self._calc_items), dtype=float, # TODO: needed? DataTypes.NUMERIC? @@ -374,6 +427,12 @@ def intensity_meas(self) -> np.ndarray: @property def intensity_meas_su(self) -> np.ndarray: + """ + Standard uncertainties of the measured intensities. + + Values smaller than 0.0001 are replaced with 1.0 to prevent + fitting failures. + """ # TODO: The following is a temporary workaround to handle zero # or near-zero uncertainties in the data, when dats is loaded # from CIF files. This is necessary because zero uncertainties @@ -396,6 +455,7 @@ def intensity_meas_su(self) -> np.ndarray: @property def intensity_calc(self) -> np.ndarray: + """Calculated intensities for active data points.""" return np.fromiter( (p.intensity_calc.value for p in self._calc_items), dtype=float, # TODO: needed? DataTypes.NUMERIC? @@ -403,6 +463,7 @@ def intensity_calc(self) -> np.ndarray: @property def intensity_bkg(self) -> np.ndarray: + """Background intensities for active data points.""" return np.fromiter( (p.intensity_bkg.value for p in self._calc_items), dtype=float, # TODO: needed? DataTypes.NUMERIC? @@ -411,6 +472,8 @@ def intensity_bkg(self) -> np.ndarray: @DataFactory.register class PdCwlData(PdDataBase): + """Bragg powder CWL data collection.""" + # TODO: ??? # _description: str = 'Powder diffraction data points for # constant-wavelength experiments.' @@ -424,7 +487,7 @@ class PdCwlData(PdDataBase): calculators=frozenset({CalculatorEnum.CRYSPY}), ) - def __init__(self): + def __init__(self) -> None: super().__init__(item_type=PdCwlDataPoint) ################# @@ -433,8 +496,8 @@ def __init__(self): # Should be set only once - def _create_items_set_xcoord_and_id(self, values) -> None: - """Helper method to set 2θ values.""" + def _create_items_set_xcoord_and_id(self, values: object) -> None: + """Set 2θ values.""" # TODO: split into multiple methods # Create items @@ -449,7 +512,7 @@ def _create_items_set_xcoord_and_id(self, values) -> None: # Misc - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: super()._update(called_by_minimizer) experiment = self._parent @@ -465,9 +528,7 @@ def _update(self, called_by_minimizer=False): @property def two_theta(self) -> np.ndarray: - """Get the 2θ values for data points included in - calculations. - """ + """Get 2θ values for data points included in calculations.""" return np.fromiter( (p.two_theta.value for p in self._calc_items), dtype=float, # TODO: needed? DataTypes.NUMERIC? @@ -489,6 +550,8 @@ def unfiltered_x(self) -> np.ndarray: @DataFactory.register class PdTofData(PdDataBase): + """Bragg powder TOF data collection.""" + type_info = TypeInfo(tag='bragg-pd-tof', description='Bragg powder TOF data') compatibility = Compatibility( sample_form=frozenset({SampleFormEnum.POWDER}), @@ -499,7 +562,7 @@ class PdTofData(PdDataBase): calculators=frozenset({CalculatorEnum.CRYSPY, CalculatorEnum.CRYSFML}), ) - def __init__(self): + def __init__(self) -> None: super().__init__(item_type=PdTofDataPoint) ################# @@ -508,8 +571,8 @@ def __init__(self): # Should be set only once - def _create_items_set_xcoord_and_id(self, values) -> None: - """Helper method to set time-of-flight values.""" + def _create_items_set_xcoord_and_id(self, values: object) -> None: + """Set time-of-flight values.""" # TODO: split into multiple methods # Create items @@ -524,7 +587,7 @@ def _create_items_set_xcoord_and_id(self, values) -> None: # Misc - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: super()._update(called_by_minimizer) experiment = self._parent @@ -542,9 +605,7 @@ def _update(self, called_by_minimizer=False): @property def time_of_flight(self) -> np.ndarray: - """Get the TOF values for data points included in - calculations. - """ + """Get TOF values for data points included in calculations.""" return np.fromiter( (p.time_of_flight.value for p in self._calc_items), dtype=float, # TODO: needed? DataTypes.NUMERIC? diff --git a/src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py b/src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py index 39552b63..d19eb75f 100644 --- a/src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py +++ b/src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -26,16 +26,14 @@ class Refln(CategoryItem): - """Single reflection for single crystal diffraction data - category. - """ + """Single reflection for single-crystal diffraction data.""" def __init__(self) -> None: super().__init__() self._id = StringDescriptor( name='id', - description='Identifier of the reflection.', + description='Identifier of the reflection', value_spec=AttributeSpec( default='0', # TODO: the following pattern is valid for dict key @@ -47,7 +45,7 @@ def __init__(self) -> None: ) self._d_spacing = NumericDescriptor( name='d_spacing', - description='The distance between lattice planes in the crystal for this reflection.', + description='Distance between lattice planes for this reflection', units='Å', value_spec=AttributeSpec( default=0.0, @@ -57,7 +55,7 @@ def __init__(self) -> None: ) self._sin_theta_over_lambda = NumericDescriptor( name='sin_theta_over_lambda', - description='The sin(θ)/λ value for this reflection.', + description='The sin(θ)/λ value for this reflection', units='Å⁻¹', value_spec=AttributeSpec( default=0.0, @@ -67,7 +65,7 @@ def __init__(self) -> None: ) self._index_h = NumericDescriptor( name='index_h', - description='Miller index h of a measured reflection.', + description='Miller index h of a measured reflection', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -76,7 +74,7 @@ def __init__(self) -> None: ) self._index_k = NumericDescriptor( name='index_k', - description='Miller index k of a measured reflection.', + description='Miller index k of a measured reflection', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -85,7 +83,7 @@ def __init__(self) -> None: ) self._index_l = NumericDescriptor( name='index_l', - description='Miller index l of a measured reflection.', + description='Miller index l of a measured reflection', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -112,7 +110,7 @@ def __init__(self) -> None: ) self._intensity_calc = NumericDescriptor( name='intensity_calc', - description='The intensity of the reflection calculated from the atom site data.', + description='Intensity of the reflection calculated from atom site data', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -121,7 +119,7 @@ def __init__(self) -> None: ) self._wavelength = NumericDescriptor( name='wavelength', - description='The mean wavelength of radiation used to measure this reflection.', + description='Mean wavelength of radiation for this reflection', units='Å', value_spec=AttributeSpec( default=0.0, @@ -139,42 +137,102 @@ def __init__(self) -> None: @property def id(self) -> StringDescriptor: + """ + Identifier of the reflection. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._id @property def d_spacing(self) -> NumericDescriptor: + """ + Distance between lattice planes for this reflection (Å). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._d_spacing @property def sin_theta_over_lambda(self) -> NumericDescriptor: + """ + The sin(θ)/λ value for this reflection (Å⁻¹). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._sin_theta_over_lambda @property def index_h(self) -> NumericDescriptor: + """ + Miller index h of a measured reflection. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._index_h @property def index_k(self) -> NumericDescriptor: + """ + Miller index k of a measured reflection. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._index_k @property def index_l(self) -> NumericDescriptor: + """ + Miller index l of a measured reflection. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._index_l @property def intensity_meas(self) -> NumericDescriptor: + """ + The intensity of the reflection derived from the measurements. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._intensity_meas @property def intensity_meas_su(self) -> NumericDescriptor: + """ + Standard uncertainty of the measured intensity. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._intensity_meas_su @property def intensity_calc(self) -> NumericDescriptor: + """ + Intensity of the reflection calculated from atom site data. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._intensity_calc @property def wavelength(self) -> NumericDescriptor: + """ + Mean wavelength of radiation for this reflection (Å). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._wavelength @@ -194,7 +252,7 @@ class ReflnData(CategoryCollection): _update_priority = 100 - def __init__(self): + def __init__(self) -> None: super().__init__(item_type=Refln) ################# @@ -203,8 +261,13 @@ def __init__(self): # Should be set only once - def _create_items_set_hkl_and_id(self, indices_h, indices_k, indices_l) -> None: - """Helper method to set Miller indices.""" + def _create_items_set_hkl_and_id( + self, + indices_h: object, + indices_k: object, + indices_l: object, + ) -> None: + """Set Miller indices.""" # TODO: split into multiple methods # Create items @@ -221,48 +284,46 @@ def _create_items_set_hkl_and_id(self, indices_h, indices_k, indices_l) -> None: # Set reflection IDs self._set_id([str(i + 1) for i in range(indices_h.size)]) - def _set_id(self, values) -> None: - """Helper method to set reflection IDs.""" + def _set_id(self, values: object) -> None: + """Set reflection IDs.""" for p, v in zip(self._items, values, strict=True): p.id._value = v - def _set_intensity_meas(self, values) -> None: - """Helper method to set measured intensity.""" + def _set_intensity_meas(self, values: object) -> None: + """Set measured intensity.""" for p, v in zip(self._items, values, strict=True): p.intensity_meas._value = v - def _set_intensity_meas_su(self, values) -> None: - """Helper method to set standard uncertainty of measured - intensity. - """ + def _set_intensity_meas_su(self, values: object) -> None: + """Set standard uncertainty of measured intensity values.""" for p, v in zip(self._items, values, strict=True): p.intensity_meas_su._value = v - def _set_wavelength(self, values) -> None: - """Helper method to set wavelength.""" + def _set_wavelength(self, values: object) -> None: + """Set wavelength.""" for p, v in zip(self._items, values, strict=True): p.wavelength._value = v # Can be set multiple times - def _set_d_spacing(self, values) -> None: - """Helper method to set d-spacing values.""" + def _set_d_spacing(self, values: object) -> None: + """Set d-spacing values.""" for p, v in zip(self._items, values, strict=True): p.d_spacing._value = v - def _set_sin_theta_over_lambda(self, values) -> None: - """Helper method to set sin(theta)/lambda values.""" + def _set_sin_theta_over_lambda(self, values: object) -> None: + """Set sin(theta)/lambda values.""" for p, v in zip(self._items, values, strict=True): p.sin_theta_over_lambda._value = v - def _set_intensity_calc(self, values) -> None: - """Helper method to set calculated intensity.""" + def _set_intensity_calc(self, values: object) -> None: + """Set calculated intensity.""" for p, v in zip(self._items, values, strict=True): p.intensity_calc._value = v # Misc - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: experiment = self._parent experiments = experiment._parent project = experiments._parent @@ -302,63 +363,72 @@ def _update(self, called_by_minimizer=False): @property def d_spacing(self) -> np.ndarray: + """D-spacing values for all reflection data points.""" return np.fromiter( (p.d_spacing.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def sin_theta_over_lambda(self) -> np.ndarray: + """sinθ/λ values for all reflection data points.""" return np.fromiter( (p.sin_theta_over_lambda.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def index_h(self) -> np.ndarray: + """Miller h indices for all reflection data points.""" return np.fromiter( (p.index_h.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def index_k(self) -> np.ndarray: + """Miller k indices for all reflection data points.""" return np.fromiter( (p.index_k.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def index_l(self) -> np.ndarray: + """Miller l indices for all reflection data points.""" return np.fromiter( (p.index_l.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def intensity_meas(self) -> np.ndarray: + """Measured structure-factor intensities for all reflections.""" return np.fromiter( (p.intensity_meas.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def intensity_meas_su(self) -> np.ndarray: + """Standard uncertainties of the measured intensities.""" return np.fromiter( (p.intensity_meas_su.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def intensity_calc(self) -> np.ndarray: + """Calculated intensities for all reflections.""" return np.fromiter( (p.intensity_calc.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def wavelength(self) -> np.ndarray: + """Wavelengths associated with each reflection.""" return np.fromiter( (p.wavelength.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) diff --git a/src/easydiffraction/datablocks/experiment/categories/data/factory.py b/src/easydiffraction/datablocks/experiment/categories/data/factory.py index 1ef25c0b..d8cdcf12 100644 --- a/src/easydiffraction/datablocks/experiment/categories/data/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/data/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Data collection factory — delegates to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py b/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py index 0aa63be5..c8d0aa9d 100644 --- a/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py +++ b/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Data categories for total scattering (PDF) experiments.""" @@ -26,7 +26,8 @@ class TotalDataPoint(CategoryItem): - """Total scattering (PDF) data point in r-space (real space). + """ + Total scattering (PDF) data point in r-space (real space). Note: PDF data is always in r-space regardless of whether the original measurement was CWL or TOF. @@ -37,7 +38,7 @@ def __init__(self) -> None: self._point_id = StringDescriptor( name='point_id', - description='Identifier for this data point in the dataset.', + description='Identifier for this data point in the dataset', value_spec=AttributeSpec( default='0', validator=RegexValidator(pattern=r'^[A-Za-z0-9_]*$'), @@ -50,7 +51,7 @@ def __init__(self) -> None: ) self._r = NumericDescriptor( name='r', - description='Interatomic distance in real space.', + description='Interatomic distance in real space', units='Å', value_spec=AttributeSpec( default=0.0, @@ -64,7 +65,7 @@ def __init__(self) -> None: ) self._g_r_meas = NumericDescriptor( name='g_r_meas', - description='Measured pair distribution function G(r).', + description='Measured pair distribution function G(r)', value_spec=AttributeSpec( default=0.0, ), @@ -76,7 +77,7 @@ def __init__(self) -> None: ) self._g_r_meas_su = NumericDescriptor( name='g_r_meas_su', - description='Standard uncertainty of measured G(r).', + description='Standard uncertainty of measured G(r)', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -89,7 +90,7 @@ def __init__(self) -> None: ) self._g_r_calc = NumericDescriptor( name='g_r_calc', - description='Calculated pair distribution function G(r).', + description='Calculated pair distribution function G(r)', value_spec=AttributeSpec( default=0.0, ), @@ -101,7 +102,7 @@ def __init__(self) -> None: ) self._calc_status = StringDescriptor( name='calc_status', - description='Status code of the data point in calculation.', + description='Status code of the data point in calculation', value_spec=AttributeSpec( default='incl', validator=MembershipValidator(allowed=['incl', 'excl']), @@ -122,26 +123,62 @@ def __init__(self) -> None: @property def point_id(self) -> StringDescriptor: + """ + Identifier for this data point in the dataset. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._point_id @property def r(self) -> NumericDescriptor: + """ + Interatomic distance in real space (Å). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._r @property def g_r_meas(self) -> NumericDescriptor: + """ + Measured pair distribution function G(r). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._g_r_meas @property def g_r_meas_su(self) -> NumericDescriptor: + """ + Standard uncertainty of measured G(r). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._g_r_meas_su @property def g_r_calc(self) -> NumericDescriptor: + """ + Calculated pair distribution function G(r). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._g_r_calc @property def calc_status(self) -> StringDescriptor: + """ + Status code of the data point in calculation. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._calc_status @@ -156,32 +193,30 @@ class TotalDataBase(CategoryCollection): # Should be set only once - def _set_point_id(self, values) -> None: - """Helper method to set point IDs.""" + def _set_point_id(self, values: object) -> None: + """Set point IDs.""" for p, v in zip(self._items, values, strict=True): p.point_id._value = v - def _set_g_r_meas(self, values) -> None: - """Helper method to set measured G(r).""" + def _set_g_r_meas(self, values: object) -> None: + """Set measured G(r).""" for p, v in zip(self._items, values, strict=True): p.g_r_meas._value = v - def _set_g_r_meas_su(self, values) -> None: - """Helper method to set standard uncertainty of measured - G(r). - """ + def _set_g_r_meas_su(self, values: object) -> None: + """Set standard uncertainty of measured G(r) values.""" for p, v in zip(self._items, values, strict=True): p.g_r_meas_su._value = v # Can be set multiple times - def _set_g_r_calc(self, values) -> None: - """Helper method to set calculated G(r).""" + def _set_g_r_calc(self, values: object) -> None: + """Set calculated G(r).""" for p, v in zip(self._calc_items, values, strict=True): p.g_r_calc._value = v - def _set_calc_status(self, values) -> None: - """Helper method to set calculation status.""" + def _set_calc_status(self, values: object) -> None: + """Set calculation status.""" for p, v in zip(self._items, values, strict=True): if v: p.calc_status._value = 'incl' @@ -197,13 +232,13 @@ def _calc_mask(self) -> np.ndarray: return self.calc_status == 'incl' @property - def _calc_items(self): + def _calc_items(self) -> list: """Get only the items included in calculations.""" return [item for item, mask in zip(self._items, self._calc_mask, strict=False) if mask] # Misc - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: experiment = self._parent experiments = experiment._parent project = experiments._parent @@ -239,30 +274,34 @@ def _update(self, called_by_minimizer=False): @property def calc_status(self) -> np.ndarray: + """Refinement-status flags for each data point as an array.""" return np.fromiter( (p.calc_status.value for p in self._items), - dtype=object, # TODO: needed? DataTypes.NUMERIC? + dtype=object, ) @property def intensity_meas(self) -> np.ndarray: + """Measured G(r) values for active data points.""" return np.fromiter( (p.g_r_meas.value for p in self._calc_items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def intensity_meas_su(self) -> np.ndarray: + """Standard uncertainties of the measured G(r) values.""" return np.fromiter( (p.g_r_meas_su.value for p in self._calc_items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def intensity_calc(self) -> np.ndarray: + """Calculated G(r) values for active data points.""" return np.fromiter( (p.g_r_calc.value for p in self._calc_items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property @@ -273,13 +312,17 @@ def intensity_bkg(self) -> np.ndarray: @DataFactory.register class TotalData(TotalDataBase): - """Total scattering (PDF) data collection in r-space. + """ + Total scattering (PDF) data collection in r-space. - Note: Works for both CWL and TOF measurements as PDF data - is always transformed to r-space. + Note: Works for both CWL and TOF measurements as PDF data is always + transformed to r-space. """ - type_info = TypeInfo(tag='total-pd', description='Total scattering (PDF) data') + type_info = TypeInfo( + tag='total-pd', + description='Total scattering (PDF) data', + ) compatibility = Compatibility( sample_form=frozenset({SampleFormEnum.POWDER}), scattering_type=frozenset({ScatteringTypeEnum.TOTAL}), @@ -289,7 +332,7 @@ class TotalData(TotalDataBase): calculators=frozenset({CalculatorEnum.PDFFIT}), ) - def __init__(self): + def __init__(self) -> None: super().__init__(item_type=TotalDataPoint) ################# @@ -298,8 +341,8 @@ def __init__(self): # Should be set only once - def _create_items_set_xcoord_and_id(self, values) -> None: - """Helper method to set r values.""" + def _create_items_set_xcoord_and_id(self, values: object) -> None: + """Set r values.""" # TODO: split into multiple methods # Create items diff --git a/src/easydiffraction/datablocks/experiment/categories/excluded_regions/__init__.py b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/__init__.py index 3356f4cf..8d232629 100644 --- a/src/easydiffraction/datablocks/experiment/categories/excluded_regions/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.excluded_regions.default import ( diff --git a/src/easydiffraction/datablocks/experiment/categories/excluded_regions/default.py b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/default.py index 2696f25c..fdf130c2 100644 --- a/src/easydiffraction/datablocks/experiment/categories/excluded_regions/default.py +++ b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/default.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Exclude ranges of x from fitting/plotting (masked regions).""" @@ -29,13 +29,13 @@ class ExcludedRegion(CategoryItem): """Closed interval [start, end] to be excluded.""" - def __init__(self): + def __init__(self) -> None: super().__init__() # TODO: Add point_id as for the background self._id = StringDescriptor( name='id', - description='Identifier for this excluded region.', + description='Identifier for this excluded region', value_spec=AttributeSpec( default='0', # TODO: the following pattern is valid for dict key @@ -71,33 +71,55 @@ def __init__(self): # ------------------------------------------------------------------ @property - def id(self): + def id(self) -> StringDescriptor: + """ + Identifier for this excluded region. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._id @id.setter - def id(self, value): + def id(self, value: str) -> None: self._id.value = value @property def start(self) -> NumericDescriptor: + """ + Start of the excluded region. + + Reading this property returns the underlying + ``NumericDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._start @start.setter - def start(self, value: float): + def start(self, value: float) -> None: self._start.value = value @property def end(self) -> NumericDescriptor: + """ + End of the excluded region. + + Reading this property returns the underlying + ``NumericDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._end @end.setter - def end(self, value: float): + def end(self, value: float) -> None: self._end.value = value @ExcludedRegionsFactory.register class ExcludedRegions(CategoryCollection): - """Collection of ExcludedRegion instances. + """ + Collection of ExcludedRegion instances. Excluded regions define closed intervals [start, end] on the x-axis that are to be excluded from calculations and, as a result, from @@ -112,10 +134,10 @@ class ExcludedRegions(CategoryCollection): sample_form=frozenset({SampleFormEnum.POWDER}), ) - def __init__(self): + def __init__(self) -> None: super().__init__(item_type=ExcludedRegion) - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: del called_by_minimizer data = self._parent.data diff --git a/src/easydiffraction/datablocks/experiment/categories/excluded_regions/factory.py b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/factory.py index 789e25e7..e12fb0c0 100644 --- a/src/easydiffraction/datablocks/experiment/categories/excluded_regions/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/factory.py @@ -1,6 +1,8 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Excluded-regions factory — delegates entirely to ``FactoryBase``.""" +""" +Excluded-regions factory — delegates entirely to ``FactoryBase``. +""" from __future__ import annotations diff --git a/src/easydiffraction/datablocks/experiment/categories/experiment_type/__init__.py b/src/easydiffraction/datablocks/experiment/categories/experiment_type/__init__.py index 63e6bb0b..197b5510 100644 --- a/src/easydiffraction/datablocks/experiment/categories/experiment_type/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/experiment_type/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.experiment_type.default import ExperimentType diff --git a/src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py b/src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py index 5221e444..1b9d3811 100644 --- a/src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py +++ b/src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py @@ -1,9 +1,10 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Experiment type descriptor (form, beam, probe, scattering). +""" +Experiment type descriptor (form, beam, probe, scattering). -This lightweight container stores the categorical attributes defining -an experiment configuration and handles CIF serialization via +This lightweight container stores the categorical attributes defining an +experiment configuration and handles CIF serialization via ``CifHandler``. """ @@ -26,27 +27,19 @@ @ExperimentTypeFactory.register class ExperimentType(CategoryItem): - """Container of categorical attributes defining experiment flavor. - - Args: - sample_form: Powder or Single crystal. - beam_mode: Constant wavelength (CW) or time-of-flight (TOF). - radiation_probe: Neutrons or X-rays. - scattering_type: Bragg or Total. - """ + """Container of attributes defining the experiment type.""" type_info = TypeInfo( tag='default', description='Experiment type descriptor', ) - def __init__(self): + def __init__(self) -> None: super().__init__() self._sample_form = StringDescriptor( name='sample_form', - description='Specifies whether the diffraction data corresponds to ' - 'powder diffraction or single crystal diffraction', + description='Powder diffraction or single crystal diffraction', value_spec=AttributeSpec( default=SampleFormEnum.default().value, validator=MembershipValidator(allowed=[member.value for member in SampleFormEnum]), @@ -56,8 +49,7 @@ def __init__(self): self._beam_mode = StringDescriptor( name='beam_mode', - description='Defines whether the measurement is performed with a ' - 'constant wavelength (CW) or time-of-flight (TOF) method', + description='Constant wavelength (CW) or time-of-flight (TOF) measurement', value_spec=AttributeSpec( default=BeamModeEnum.default().value, validator=MembershipValidator(allowed=[member.value for member in BeamModeEnum]), @@ -66,7 +58,7 @@ def __init__(self): ) self._radiation_probe = StringDescriptor( name='radiation_probe', - description='Specifies whether the measurement uses neutrons or X-rays', + description='Neutron or X-ray diffraction measurement', value_spec=AttributeSpec( default=RadiationProbeEnum.default().value, validator=MembershipValidator( @@ -77,9 +69,7 @@ def __init__(self): ) self._scattering_type = StringDescriptor( name='scattering_type', - description='Specifies whether the experiment uses Bragg scattering ' - '(for conventional structure refinement) or total scattering ' - '(for pair distribution function analysis - PDF)', + description='Conventional Bragg diffraction or total scattering (PDF)', value_spec=AttributeSpec( default=ScatteringTypeEnum.default().value, validator=MembershipValidator( @@ -112,17 +102,41 @@ def _set_scattering_type(self, value: str) -> None: # ------------------------------------------------------------------ @property - def sample_form(self): + def sample_form(self) -> StringDescriptor: + """ + Powder diffraction or single crystal diffraction. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._sample_form @property - def beam_mode(self): + def beam_mode(self) -> StringDescriptor: + """ + Constant wavelength (CW) or time-of-flight (TOF) measurement. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._beam_mode @property - def radiation_probe(self): + def radiation_probe(self) -> StringDescriptor: + """ + Neutron or X-ray diffraction measurement. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._radiation_probe @property - def scattering_type(self): + def scattering_type(self) -> StringDescriptor: + """ + Conventional Bragg diffraction or total scattering (PDF). + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._scattering_type diff --git a/src/easydiffraction/datablocks/experiment/categories/experiment_type/factory.py b/src/easydiffraction/datablocks/experiment/categories/experiment_type/factory.py index bf78fb53..05f0d2d9 100644 --- a/src/easydiffraction/datablocks/experiment/categories/experiment_type/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/experiment_type/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Experiment-type factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py b/src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py index f3d62fad..3e6fa39a 100644 --- a/src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.extinction.shelx import ShelxExtinction diff --git a/src/easydiffraction/datablocks/experiment/categories/extinction/factory.py b/src/easydiffraction/datablocks/experiment/categories/extinction/factory.py index fbeb32e7..4e4bd9ed 100644 --- a/src/easydiffraction/datablocks/experiment/categories/extinction/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/extinction/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Extinction factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py b/src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py index 67dfaf94..dd736a1a 100644 --- a/src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py +++ b/src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Shelx-style isotropic extinction correction.""" @@ -17,9 +17,7 @@ @ExtinctionFactory.register class ShelxExtinction(CategoryItem): - """Shelx-style isotropic extinction correction for single - crystals. - """ + """Shelx-style extinction correction for single crystals.""" type_info = TypeInfo( tag='shelx', @@ -34,7 +32,7 @@ def __init__(self) -> None: self._mosaicity = Parameter( name='mosaicity', - description='Mosaicity value for extinction correction.', + description='Mosaicity value for extinction correction', units='deg', value_spec=AttributeSpec( default=1.0, @@ -48,7 +46,7 @@ def __init__(self) -> None: ) self._radius = Parameter( name='radius', - description='Crystal radius for extinction correction.', + description='Crystal radius for extinction correction', units='µm', value_spec=AttributeSpec( default=1.0, @@ -68,17 +66,29 @@ def __init__(self) -> None: # ------------------------------------------------------------------ @property - def mosaicity(self): + def mosaicity(self) -> Parameter: + """ + Mosaicity value for extinction correction (deg). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._mosaicity @mosaicity.setter - def mosaicity(self, value): + def mosaicity(self, value: float) -> None: self._mosaicity.value = value @property - def radius(self): + def radius(self) -> Parameter: + """ + Crystal radius for extinction correction (µm). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._radius @radius.setter - def radius(self, value): + def radius(self, value: float) -> None: self._radius.value = value diff --git a/src/easydiffraction/datablocks/experiment/categories/instrument/__init__.py b/src/easydiffraction/datablocks/experiment/categories/instrument/__init__.py index e4a03696..d12b40c3 100644 --- a/src/easydiffraction/datablocks/experiment/categories/instrument/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/instrument/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.instrument.cwl import CwlPdInstrument diff --git a/src/easydiffraction/datablocks/experiment/categories/instrument/base.py b/src/easydiffraction/datablocks/experiment/categories/instrument/base.py index 0d1c04d5..a2568884 100644 --- a/src/easydiffraction/datablocks/experiment/categories/instrument/base.py +++ b/src/easydiffraction/datablocks/experiment/categories/instrument/base.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Instrument category base definitions for CWL/TOF instruments. +""" +Instrument category base definitions for CWL/TOF instruments. This module provides the shared parent used by concrete instrument implementations under the instrument category. @@ -12,7 +13,8 @@ class InstrumentBase(CategoryItem): - """Base class for instrument category items. + """ + Base class for instrument category items. This class sets the common ``category_code`` and is used as a base for concrete CWL/TOF instrument definitions. diff --git a/src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py b/src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py index 924158a0..3a3628bd 100644 --- a/src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py +++ b/src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.core.metadata import CalculatorSupport @@ -17,6 +17,8 @@ class CwlInstrumentBase(InstrumentBase): + """Base class for constant-wavelength instruments.""" + def __init__(self) -> None: super().__init__() @@ -36,19 +38,28 @@ def __init__(self) -> None: ) @property - def setup_wavelength(self): - """Incident wavelength parameter (Å).""" + def setup_wavelength(self) -> Parameter: + """ + Incident neutron or X-ray wavelength (Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._setup_wavelength @setup_wavelength.setter - def setup_wavelength(self, value): - """Set incident wavelength value (Å).""" + def setup_wavelength(self, value: float) -> None: self._setup_wavelength.value = value @InstrumentFactory.register class CwlScInstrument(CwlInstrumentBase): - type_info = TypeInfo(tag='cwl-sc', description='CW single-crystal diffractometer') + """CW single-crystal diffractometer.""" + + type_info = TypeInfo( + tag='cwl-sc', + description='CW single-crystal diffractometer', + ) compatibility = Compatibility( scattering_type=frozenset({ScatteringTypeEnum.BRAGG}), beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH}), @@ -64,7 +75,12 @@ def __init__(self) -> None: @InstrumentFactory.register class CwlPdInstrument(CwlInstrumentBase): - type_info = TypeInfo(tag='cwl-pd', description='CW powder diffractometer') + """CW powder diffractometer.""" + + type_info = TypeInfo( + tag='cwl-pd', + description='CW powder diffractometer', + ) compatibility = Compatibility( scattering_type=frozenset({ScatteringTypeEnum.BRAGG, ScatteringTypeEnum.TOTAL}), beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH}), @@ -97,11 +113,15 @@ def __init__(self) -> None: ) @property - def calib_twotheta_offset(self): - """Instrument misalignment two-theta offset (deg).""" + def calib_twotheta_offset(self) -> Parameter: + """ + Instrument misalignment offset (deg). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._calib_twotheta_offset @calib_twotheta_offset.setter - def calib_twotheta_offset(self, value): - """Set two-theta offset value (deg).""" + def calib_twotheta_offset(self, value: float) -> None: self._calib_twotheta_offset.value = value diff --git a/src/easydiffraction/datablocks/experiment/categories/instrument/factory.py b/src/easydiffraction/datablocks/experiment/categories/instrument/factory.py index 7d4286af..fce8ad5c 100644 --- a/src/easydiffraction/datablocks/experiment/categories/instrument/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/instrument/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Instrument factory — delegates to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/instrument/tof.py b/src/easydiffraction/datablocks/experiment/categories/instrument/tof.py index efbff40d..7e1db98e 100644 --- a/src/easydiffraction/datablocks/experiment/categories/instrument/tof.py +++ b/src/easydiffraction/datablocks/experiment/categories/instrument/tof.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.core.metadata import CalculatorSupport @@ -18,7 +18,12 @@ @InstrumentFactory.register class TofScInstrument(InstrumentBase): - type_info = TypeInfo(tag='tof-sc', description='TOF single-crystal diffractometer') + """TOF single-crystal diffractometer.""" + + type_info = TypeInfo( + tag='tof-sc', + description='TOF single-crystal diffractometer', + ) compatibility = Compatibility( scattering_type=frozenset({ScatteringTypeEnum.BRAGG}), beam_mode=frozenset({BeamModeEnum.TIME_OF_FLIGHT}), @@ -34,7 +39,12 @@ def __init__(self) -> None: @InstrumentFactory.register class TofPdInstrument(InstrumentBase): - type_info = TypeInfo(tag='tof-pd', description='TOF powder diffractometer') + """TOF powder diffractometer.""" + + type_info = TypeInfo( + tag='tof-pd', + description='TOF powder diffractometer', + ) compatibility = Compatibility( scattering_type=frozenset({ScatteringTypeEnum.BRAGG}), beam_mode=frozenset({BeamModeEnum.TIME_OF_FLIGHT}), @@ -99,41 +109,71 @@ def __init__(self) -> None: ) @property - def setup_twotheta_bank(self): + def setup_twotheta_bank(self) -> Parameter: + """ + Detector bank position (deg). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._setup_twotheta_bank @setup_twotheta_bank.setter - def setup_twotheta_bank(self, value): + def setup_twotheta_bank(self, value: float) -> None: self._setup_twotheta_bank.value = value @property - def calib_d_to_tof_offset(self): + def calib_d_to_tof_offset(self) -> Parameter: + """ + TOF offset (µs). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._calib_d_to_tof_offset @calib_d_to_tof_offset.setter - def calib_d_to_tof_offset(self, value): + def calib_d_to_tof_offset(self, value: float) -> None: self._calib_d_to_tof_offset.value = value @property - def calib_d_to_tof_linear(self): + def calib_d_to_tof_linear(self) -> Parameter: + """ + TOF linear conversion (µs/Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._calib_d_to_tof_linear @calib_d_to_tof_linear.setter - def calib_d_to_tof_linear(self, value): + def calib_d_to_tof_linear(self, value: float) -> None: self._calib_d_to_tof_linear.value = value @property - def calib_d_to_tof_quad(self): + def calib_d_to_tof_quad(self) -> Parameter: + """ + TOF quadratic correction (µs/Ų). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._calib_d_to_tof_quad @calib_d_to_tof_quad.setter - def calib_d_to_tof_quad(self, value): + def calib_d_to_tof_quad(self, value: float) -> None: self._calib_d_to_tof_quad.value = value @property - def calib_d_to_tof_recip(self): + def calib_d_to_tof_recip(self) -> Parameter: + """ + TOF reciprocal velocity correction (µs·Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._calib_d_to_tof_recip @calib_d_to_tof_recip.setter - def calib_d_to_tof_recip(self, value): + def calib_d_to_tof_recip(self, value: float) -> None: self._calib_d_to_tof_recip.value = value diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_crystal/__init__.py b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/__init__.py index 1a6b0b67..4b93121b 100644 --- a/src/easydiffraction/datablocks/experiment/categories/linked_crystal/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.linked_crystal.default import LinkedCrystal diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py index e1fa8b6a..d441aa9c 100644 --- a/src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py +++ b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Default linked-crystal reference (id + scale).""" @@ -21,9 +21,7 @@ @LinkedCrystalFactory.register class LinkedCrystal(CategoryItem): - """Linked crystal category for referencing from the experiment for - single crystal diffraction. - """ + """Linked crystal reference for single-crystal diffraction.""" type_info = TypeInfo( tag='default', @@ -38,7 +36,7 @@ def __init__(self) -> None: self._id = StringDescriptor( name='id', - description='Identifier of the linked crystal.', + description='Identifier of the linked crystal', value_spec=AttributeSpec( default='Si', validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), @@ -47,7 +45,7 @@ def __init__(self) -> None: ) self._scale = Parameter( name='scale', - description='Scale factor of the linked crystal.', + description='Scale factor of the linked crystal', value_spec=AttributeSpec( default=1.0, validator=RangeValidator(), @@ -63,16 +61,29 @@ def __init__(self) -> None: @property def id(self) -> StringDescriptor: + """ + Identifier of the linked crystal. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._id @id.setter - def id(self, value: str): + def id(self, value: str) -> None: self._id.value = value @property def scale(self) -> Parameter: + """ + Scale factor of the linked crystal. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._scale @scale.setter - def scale(self, value: float): + def scale(self, value: float) -> None: self._scale.value = value diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_crystal/factory.py b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/factory.py index 49ac1a64..b34b8073 100644 --- a/src/easydiffraction/datablocks/experiment/categories/linked_crystal/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Linked-crystal factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_phases/__init__.py b/src/easydiffraction/datablocks/experiment/categories/linked_phases/__init__.py index 6dd96b94..dda7d445 100644 --- a/src/easydiffraction/datablocks/experiment/categories/linked_phases/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/linked_phases/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.linked_phases.default import LinkedPhase diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py b/src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py index 067683a9..97d03c66 100644 --- a/src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py +++ b/src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Linked phases allow combining phases with scale factors.""" @@ -23,12 +23,12 @@ class LinkedPhase(CategoryItem): """Link to a phase by id with a scale factor.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._id = StringDescriptor( name='id', - description='Identifier of the linked phase.', + description='Identifier of the linked phase', value_spec=AttributeSpec( default='Si', validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), @@ -54,18 +54,31 @@ def __init__(self): @property def id(self) -> StringDescriptor: + """ + Identifier of the linked phase. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._id @id.setter - def id(self, value: str): + def id(self, value: str) -> None: self._id.value = value @property def scale(self) -> Parameter: + """ + Scale factor of the linked phase. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._scale @scale.setter - def scale(self, value: float): + def scale(self, value: float) -> None: self._scale.value = value @@ -81,6 +94,6 @@ class LinkedPhases(CategoryCollection): sample_form=frozenset({SampleFormEnum.POWDER}), ) - def __init__(self): + def __init__(self) -> None: """Create an empty collection of linked phases.""" super().__init__(item_type=LinkedPhase) diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_phases/factory.py b/src/easydiffraction/datablocks/experiment/categories/linked_phases/factory.py index 74f16616..56970ee8 100644 --- a/src/easydiffraction/datablocks/experiment/categories/linked_phases/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/linked_phases/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Linked-phases factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/__init__.py b/src/easydiffraction/datablocks/experiment/categories/peak/__init__.py index b335b9d3..667bafb4 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlPseudoVoigt diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/base.py b/src/easydiffraction/datablocks/experiment/categories/peak/base.py index 5f2654a7..050411b1 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/base.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Base class for peak profile categories.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/cwl.py b/src/easydiffraction/datablocks/experiment/categories/peak/cwl.py index aa03c80c..a2b4f63b 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/cwl.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/cwl.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Constant-wavelength peak profile classes.""" @@ -24,7 +24,10 @@ class CwlPseudoVoigt( ): """Constant-wavelength pseudo-Voigt peak shape.""" - type_info = TypeInfo(tag='pseudo-voigt', description='Pseudo-Voigt profile') + type_info = TypeInfo( + tag='pseudo-voigt', + description='Pseudo-Voigt profile', + ) compatibility = Compatibility( scattering_type=frozenset({ScatteringTypeEnum.BRAGG}), beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH}), diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py b/src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py index 2bc9c178..6e4f29c8 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Constant-wavelength (CWL) peak-profile component classes. +""" +Constant-wavelength (CWL) peak-profile component classes. This module provides classes that add broadening and asymmetry parameters. They are composed into concrete peak classes elsewhere via @@ -16,13 +17,12 @@ class CwlBroadeningMixin: """CWL Gaussian and Lorentz broadening parameters.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._broad_gauss_u: Parameter = Parameter( name='broad_gauss_u', - description='Gaussian broadening coefficient (dependent on ' - 'sample size and instrument resolution)', + description='Gaussian broadening from sample size and resolution', units='deg²', value_spec=AttributeSpec( default=0.01, @@ -32,7 +32,7 @@ def __init__(self): ) self._broad_gauss_v: Parameter = Parameter( name='broad_gauss_v', - description='Gaussian broadening coefficient (instrumental broadening contribution)', + description='Gaussian broadening instrumental contribution', units='deg²', value_spec=AttributeSpec( default=-0.01, @@ -42,7 +42,7 @@ def __init__(self): ) self._broad_gauss_w: Parameter = Parameter( name='broad_gauss_w', - description='Gaussian broadening coefficient (instrumental broadening contribution)', + description='Gaussian broadening instrumental contribution', units='deg²', value_spec=AttributeSpec( default=0.02, @@ -52,7 +52,7 @@ def __init__(self): ) self._broad_lorentz_x: Parameter = Parameter( name='broad_lorentz_x', - description='Lorentzian broadening coefficient (dependent on sample strain effects)', + description='Lorentzian broadening from sample strain effects', units='deg', value_spec=AttributeSpec( default=0.0, @@ -62,8 +62,7 @@ def __init__(self): ) self._broad_lorentz_y: Parameter = Parameter( name='broad_lorentz_y', - description='Lorentzian broadening coefficient (dependent on ' - 'microstructural defects and strain)', + description='Lorentzian broadening from microstructural defects', units='deg', value_spec=AttributeSpec( default=0.0, @@ -78,49 +77,79 @@ def __init__(self): @property def broad_gauss_u(self) -> Parameter: + """ + Gaussian broadening from sample size and resolution (deg²). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_gauss_u @broad_gauss_u.setter - def broad_gauss_u(self, value): + def broad_gauss_u(self, value: float) -> None: self._broad_gauss_u.value = value @property def broad_gauss_v(self) -> Parameter: + """ + Gaussian broadening instrumental contribution (deg²). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_gauss_v @broad_gauss_v.setter - def broad_gauss_v(self, value): + def broad_gauss_v(self, value: float) -> None: self._broad_gauss_v.value = value @property def broad_gauss_w(self) -> Parameter: + """ + Gaussian broadening instrumental contribution (deg²). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_gauss_w @broad_gauss_w.setter - def broad_gauss_w(self, value): + def broad_gauss_w(self, value: float) -> None: self._broad_gauss_w.value = value @property def broad_lorentz_x(self) -> Parameter: + """ + Lorentzian broadening (sample strain effects) (deg). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_lorentz_x @broad_lorentz_x.setter - def broad_lorentz_x(self, value): + def broad_lorentz_x(self, value: float) -> None: self._broad_lorentz_x.value = value @property def broad_lorentz_y(self) -> Parameter: + """ + Lorentzian broadening from microstructural defects (deg). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_lorentz_y @broad_lorentz_y.setter - def broad_lorentz_y(self, value): + def broad_lorentz_y(self, value: float) -> None: self._broad_lorentz_y.value = value class EmpiricalAsymmetryMixin: """Empirical CWL peak asymmetry parameters.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._asym_empir_1: Parameter = Parameter( @@ -170,41 +199,65 @@ def __init__(self): @property def asym_empir_1(self) -> Parameter: + """ + Empirical asymmetry coefficient p1. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_empir_1 @asym_empir_1.setter - def asym_empir_1(self, value): + def asym_empir_1(self, value: float) -> None: self._asym_empir_1.value = value @property def asym_empir_2(self) -> Parameter: + """ + Empirical asymmetry coefficient p2. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_empir_2 @asym_empir_2.setter - def asym_empir_2(self, value): + def asym_empir_2(self, value: float) -> None: self._asym_empir_2.value = value @property def asym_empir_3(self) -> Parameter: + """ + Empirical asymmetry coefficient p3. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_empir_3 @asym_empir_3.setter - def asym_empir_3(self, value): + def asym_empir_3(self, value: float) -> None: self._asym_empir_3.value = value @property def asym_empir_4(self) -> Parameter: + """ + Empirical asymmetry coefficient p4. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_empir_4 @asym_empir_4.setter - def asym_empir_4(self, value): + def asym_empir_4(self, value: float) -> None: self._asym_empir_4.value = value class FcjAsymmetryMixin: """Finger–Cox–Jephcoat (FCJ) asymmetry parameters.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._asym_fcj_1: Parameter = Parameter( @@ -233,17 +286,29 @@ def __init__(self): # ------------------------------------------------------------------ @property - def asym_fcj_1(self): + def asym_fcj_1(self) -> Parameter: + """ + Finger-Cox-Jephcoat asymmetry parameter 1. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_fcj_1 @asym_fcj_1.setter - def asym_fcj_1(self, value): + def asym_fcj_1(self, value: float) -> None: self._asym_fcj_1.value = value @property - def asym_fcj_2(self): + def asym_fcj_2(self) -> Parameter: + """ + Finger-Cox-Jephcoat asymmetry parameter 2. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_fcj_2 @asym_fcj_2.setter - def asym_fcj_2(self, value): + def asym_fcj_2(self, value: float) -> None: self._asym_fcj_2.value = value diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/factory.py b/src/easydiffraction/datablocks/experiment/categories/peak/factory.py index 1992b633..ca196748 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Peak profile factory — delegates to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/tof.py b/src/easydiffraction/datablocks/experiment/categories/peak/tof.py index 1c70b65b..59c0b9e3 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/tof.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/tof.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Time-of-flight peak profile classes.""" @@ -23,7 +23,10 @@ class TofPseudoVoigt( ): """Time-of-flight pseudo-Voigt peak shape.""" - type_info = TypeInfo(tag='tof-pseudo-voigt', description='TOF pseudo-Voigt profile') + type_info = TypeInfo( + tag='tof-pseudo-voigt', + description='TOF pseudo-Voigt profile', + ) compatibility = Compatibility( scattering_type=frozenset({ScatteringTypeEnum.BRAGG}), beam_mode=frozenset({BeamModeEnum.TIME_OF_FLIGHT}), diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/tof_mixins.py b/src/easydiffraction/datablocks/experiment/categories/peak/tof_mixins.py index 01a10b26..8093d877 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/tof_mixins.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/tof_mixins.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Time-of-flight (TOF) peak-profile component classes. +""" +Time-of-flight (TOF) peak-profile component classes. Defines classes that add Gaussian/Lorentz broadening, mixing, and Ikeda–Carpenter asymmetry parameters used by TOF peak shapes. This @@ -18,12 +19,12 @@ class TofBroadeningMixin: """TOF Gaussian/Lorentz broadening and mixing parameters.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._broad_gauss_sigma_0 = Parameter( name='gauss_sigma_0', - description='Gaussian broadening coefficient (instrumental resolution)', + description='Gaussian broadening (instrumental resolution)', units='µs²', value_spec=AttributeSpec( default=0.0, @@ -33,7 +34,7 @@ def __init__(self): ) self._broad_gauss_sigma_1 = Parameter( name='gauss_sigma_1', - description='Gaussian broadening coefficient (dependent on d-spacing)', + description='Gaussian broadening (dependent on d-spacing)', units='µs/Å', value_spec=AttributeSpec( default=0.0, @@ -43,7 +44,7 @@ def __init__(self): ) self._broad_gauss_sigma_2 = Parameter( name='gauss_sigma_2', - description='Gaussian broadening coefficient (instrument-dependent term)', + description='Gaussian broadening (instrument-dependent term)', units='µs²/Ų', value_spec=AttributeSpec( default=0.0, @@ -53,7 +54,7 @@ def __init__(self): ) self._broad_lorentz_gamma_0 = Parameter( name='lorentz_gamma_0', - description='Lorentzian broadening coefficient (dependent on microstrain effects)', + description='Lorentzian broadening (microstrain effects)', units='µs', value_spec=AttributeSpec( default=0.0, @@ -63,7 +64,7 @@ def __init__(self): ) self._broad_lorentz_gamma_1 = Parameter( name='lorentz_gamma_1', - description='Lorentzian broadening coefficient (dependent on d-spacing)', + description='Lorentzian broadening (dependent on d-spacing)', units='µs/Å', value_spec=AttributeSpec( default=0.0, @@ -73,7 +74,7 @@ def __init__(self): ) self._broad_lorentz_gamma_2 = Parameter( name='lorentz_gamma_2', - description='Lorentzian broadening coefficient (instrument-dependent term)', + description='Lorentzian broadening (instrument-dependent term)', units='µs²/Ų', value_spec=AttributeSpec( default=0.0, @@ -83,8 +84,7 @@ def __init__(self): ) self._broad_mix_beta_0 = Parameter( name='mix_beta_0', - description='Mixing parameter. Defines the ratio of Gaussian ' - 'to Lorentzian contributions in TOF profiles', + description='Ratio of Gaussian to Lorentzian contributions', units='deg', value_spec=AttributeSpec( default=0.0, @@ -94,8 +94,7 @@ def __init__(self): ) self._broad_mix_beta_1 = Parameter( name='mix_beta_1', - description='Mixing parameter. Defines the ratio of Gaussian ' - 'to Lorentzian contributions in TOF profiles', + description='Ratio of Gaussian to Lorentzian contributions', units='deg', value_spec=AttributeSpec( default=0.0, @@ -109,75 +108,122 @@ def __init__(self): # ------------------------------------------------------------------ @property - def broad_gauss_sigma_0(self): + def broad_gauss_sigma_0(self) -> Parameter: + """ + Gaussian broadening (instrumental resolution) (µs²). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_gauss_sigma_0 @broad_gauss_sigma_0.setter - def broad_gauss_sigma_0(self, value): + def broad_gauss_sigma_0(self, value: float) -> None: self._broad_gauss_sigma_0.value = value @property - def broad_gauss_sigma_1(self): + def broad_gauss_sigma_1(self) -> Parameter: + """ + Gaussian broadening (dependent on d-spacing) (µs/Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_gauss_sigma_1 @broad_gauss_sigma_1.setter - def broad_gauss_sigma_1(self, value): + def broad_gauss_sigma_1(self, value: float) -> None: self._broad_gauss_sigma_1.value = value @property - def broad_gauss_sigma_2(self): + def broad_gauss_sigma_2(self) -> Parameter: + """ + Gaussian broadening (instrument-dependent term) (µs²/Ų). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_gauss_sigma_2 @broad_gauss_sigma_2.setter - def broad_gauss_sigma_2(self, value): - """Set Gaussian sigma_2 parameter.""" + def broad_gauss_sigma_2(self, value: float) -> None: self._broad_gauss_sigma_2.value = value @property - def broad_lorentz_gamma_0(self): + def broad_lorentz_gamma_0(self) -> Parameter: + """ + Lorentzian broadening (microstrain effects) (µs). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_lorentz_gamma_0 @broad_lorentz_gamma_0.setter - def broad_lorentz_gamma_0(self, value): + def broad_lorentz_gamma_0(self, value: float) -> None: self._broad_lorentz_gamma_0.value = value @property - def broad_lorentz_gamma_1(self): + def broad_lorentz_gamma_1(self) -> Parameter: + """ + Lorentzian broadening (dependent on d-spacing) (µs/Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_lorentz_gamma_1 @broad_lorentz_gamma_1.setter - def broad_lorentz_gamma_1(self, value): + def broad_lorentz_gamma_1(self, value: float) -> None: self._broad_lorentz_gamma_1.value = value @property - def broad_lorentz_gamma_2(self): + def broad_lorentz_gamma_2(self) -> Parameter: + """ + Lorentzian broadening (instrument-dependent term) (µs²/Ų). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_lorentz_gamma_2 @broad_lorentz_gamma_2.setter - def broad_lorentz_gamma_2(self, value): + def broad_lorentz_gamma_2(self, value: float) -> None: self._broad_lorentz_gamma_2.value = value @property - def broad_mix_beta_0(self): + def broad_mix_beta_0(self) -> Parameter: + """ + Ratio of Gaussian to Lorentzian contributions (deg). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_mix_beta_0 @broad_mix_beta_0.setter - def broad_mix_beta_0(self, value): + def broad_mix_beta_0(self, value: float) -> None: self._broad_mix_beta_0.value = value @property - def broad_mix_beta_1(self): + def broad_mix_beta_1(self) -> Parameter: + """ + Ratio of Gaussian to Lorentzian contributions (deg). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_mix_beta_1 @broad_mix_beta_1.setter - def broad_mix_beta_1(self, value): + def broad_mix_beta_1(self, value: float) -> None: self._broad_mix_beta_1.value = value class IkedaCarpenterAsymmetryMixin: """Ikeda–Carpenter asymmetry parameters.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._asym_alpha_0 = Parameter( @@ -202,17 +248,29 @@ def __init__(self): ) @property - def asym_alpha_0(self): + def asym_alpha_0(self) -> Parameter: + """ + Ikeda-Carpenter asymmetry parameter α₀. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_alpha_0 @asym_alpha_0.setter - def asym_alpha_0(self, value): + def asym_alpha_0(self, value: float) -> None: self._asym_alpha_0.value = value @property - def asym_alpha_1(self): + def asym_alpha_1(self) -> Parameter: + """ + Ikeda-Carpenter asymmetry parameter α₁. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_alpha_1 @asym_alpha_1.setter - def asym_alpha_1(self, value): + def asym_alpha_1(self, value: float) -> None: self._asym_alpha_1.value = value diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/total.py b/src/easydiffraction/datablocks/experiment/categories/peak/total.py index a1166c1a..61d5e6fd 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/total.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/total.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Total-scattering (PDF) peak profile classes.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/total_mixins.py b/src/easydiffraction/datablocks/experiment/categories/peak/total_mixins.py index 139e37dd..bae2c974 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/total_mixins.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/total_mixins.py @@ -1,7 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Total scattering / pair distribution function (PDF) peak-profile -component classes. +""" +Total scattering / PDF peak-profile component classes. This module provides classes that add broadening and asymmetry parameters. They are composed into concrete peak classes elsewhere via @@ -17,13 +17,12 @@ class TotalBroadeningMixin: """PDF broadening/damping/sharpening parameters.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._damp_q = Parameter( name='damp_q', - description='Instrumental Q-resolution damping factor ' - '(affects high-r PDF peak amplitude)', + description='Q-resolution damping for high-r PDF peak amplitude', units='Å⁻¹', value_spec=AttributeSpec( default=0.05, @@ -33,8 +32,7 @@ def __init__(self): ) self._broad_q = Parameter( name='broad_q', - description='Quadratic PDF peak broadening coefficient ' - '(thermal and model uncertainty contribution)', + description='Quadratic peak broadening from thermal uncertainty', units='Å⁻²', value_spec=AttributeSpec( default=0.0, @@ -44,8 +42,7 @@ def __init__(self): ) self._cutoff_q = Parameter( name='cutoff_q', - description='Q-value cutoff applied to model PDF for Fourier ' - 'transform (controls real-space resolution)', + description='Q-value cutoff for Fourier transform', units='Å⁻¹', value_spec=AttributeSpec( default=25.0, @@ -55,7 +52,7 @@ def __init__(self): ) self._sharp_delta_1 = Parameter( name='sharp_delta_1', - description='PDF peak sharpening coefficient (1/r dependence)', + description='Peak sharpening coefficient (1/r dependence)', units='Å', value_spec=AttributeSpec( default=0.0, @@ -65,7 +62,7 @@ def __init__(self): ) self._sharp_delta_2 = Parameter( name='sharp_delta_2', - description='PDF peak sharpening coefficient (1/r² dependence)', + description='Peak sharpening coefficient (1/r² dependence)', units='Ų', value_spec=AttributeSpec( default=0.0, @@ -75,7 +72,7 @@ def __init__(self): ) self._damp_particle_diameter = Parameter( name='damp_particle_diameter', - description='Particle diameter for spherical envelope damping correction in PDF', + description='Particle diameter for spherical envelope damping correction', units='Å', value_spec=AttributeSpec( default=0.0, @@ -89,49 +86,85 @@ def __init__(self): # ------------------------------------------------------------------ @property - def damp_q(self): + def damp_q(self) -> Parameter: + """ + Q-resolution damping for high-r PDF peak amplitude (Å⁻¹). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._damp_q @damp_q.setter - def damp_q(self, value): + def damp_q(self, value: float) -> None: self._damp_q.value = value @property - def broad_q(self): + def broad_q(self) -> Parameter: + """ + Quadratic peak broadening from thermal uncertainty (Å⁻²). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_q @broad_q.setter - def broad_q(self, value): + def broad_q(self, value: float) -> None: self._broad_q.value = value @property def cutoff_q(self) -> Parameter: + """ + Q-value cutoff for Fourier transform (Å⁻¹). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._cutoff_q @cutoff_q.setter - def cutoff_q(self, value): + def cutoff_q(self, value: float) -> None: self._cutoff_q.value = value @property def sharp_delta_1(self) -> Parameter: + """ + PDF peak sharpening coefficient (1/r dependence) (Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._sharp_delta_1 @sharp_delta_1.setter - def sharp_delta_1(self, value): + def sharp_delta_1(self, value: float) -> None: self._sharp_delta_1.value = value @property - def sharp_delta_2(self): + def sharp_delta_2(self) -> Parameter: + """ + PDF peak sharpening coefficient (1/r² dependence) (Ų). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._sharp_delta_2 @sharp_delta_2.setter - def sharp_delta_2(self, value): + def sharp_delta_2(self, value: float) -> None: self._sharp_delta_2.value = value @property - def damp_particle_diameter(self): + def damp_particle_diameter(self) -> Parameter: + """ + Particle diameter for spherical envelope damping correction (Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._damp_particle_diameter @damp_particle_diameter.setter - def damp_particle_diameter(self, value): + def damp_particle_diameter(self, value: float) -> None: self._damp_particle_diameter.value = value diff --git a/src/easydiffraction/datablocks/experiment/collection.py b/src/easydiffraction/datablocks/experiment/collection.py index 854b77ad..5cdf3f6b 100644 --- a/src/easydiffraction/datablocks/experiment/collection.py +++ b/src/easydiffraction/datablocks/experiment/collection.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Collection of experiment data blocks.""" @@ -11,7 +11,8 @@ class Experiments(DatablockCollection): - """Collection of Experiment data blocks. + """ + Collection of Experiment data blocks. Provides convenience constructors for common creation patterns and helper methods for simple presentation of collection contents. @@ -35,14 +36,21 @@ def create( radiation_probe: str | None = None, scattering_type: str | None = None, ) -> None: - """Add an experiment without associating a data file. - - Args: - name: Experiment identifier. - sample_form: Sample form (e.g. ``'powder'``). - beam_mode: Beam mode (e.g. ``'constant wavelength'``). - radiation_probe: Radiation probe (e.g. ``'neutron'``). - scattering_type: Scattering type (e.g. ``'bragg'``). + """ + Add an experiment without associating a data file. + + Parameters + ---------- + name : str + Experiment identifier. + sample_form : str | None, default=None + Sample form (e.g. ``'powder'``). + beam_mode : str | None, default=None + Beam mode (e.g. ``'constant wavelength'``). + radiation_probe : str | None, default=None + Radiation probe (e.g. ``'neutron'``). + scattering_type : str | None, default=None + Scattering type (e.g. ``'bragg'``). """ experiment = ExperimentFactory.from_scratch( name=name, @@ -59,10 +67,13 @@ def add_from_cif_str( self, cif_str: str, ) -> None: - """Add an experiment from a CIF string. + """ + Add an experiment from a CIF string. - Args: - cif_str: Full CIF document as a string. + Parameters + ---------- + cif_str : str + Full CIF document as a string. """ experiment = ExperimentFactory.from_cif_str(cif_str) self.add(experiment) @@ -73,10 +84,13 @@ def add_from_cif_path( self, cif_path: str, ) -> None: - """Add an experiment from a CIF file path. + """ + Add an experiment from a CIF file path. - Args: - cif_path(str): Path to a CIF document. + Parameters + ---------- + cif_path : str + Path to a CIF document. """ experiment = ExperimentFactory.from_cif_path(cif_path) self.add(experiment) @@ -92,15 +106,23 @@ def add_from_data_path( radiation_probe: str | None = None, scattering_type: str | None = None, ) -> None: - """Add an experiment from a data file path. - - Args: - name: Experiment identifier. - data_path: Path to the measured data file. - sample_form: Sample form (e.g. ``'powder'``). - beam_mode: Beam mode (e.g. ``'constant wavelength'``). - radiation_probe: Radiation probe (e.g. ``'neutron'``). - scattering_type: Scattering type (e.g. ``'bragg'``). + """ + Add an experiment from a data file path. + + Parameters + ---------- + name : str + Experiment identifier. + data_path : str + Path to the measured data file. + sample_form : str | None, default=None + Sample form (e.g. ``'powder'``). + beam_mode : str | None, default=None + Beam mode (e.g. ``'constant wavelength'``). + radiation_probe : str | None, default=None + Radiation probe (e.g. ``'neutron'``). + scattering_type : str | None, default=None + Scattering type (e.g. ``'bragg'``). """ experiment = ExperimentFactory.from_data_path( name=name, diff --git a/src/easydiffraction/datablocks/experiment/item/__init__.py b/src/easydiffraction/datablocks/experiment/item/__init__.py index ffe5775d..35a64fb1 100644 --- a/src/easydiffraction/datablocks/experiment/item/__init__.py +++ b/src/easydiffraction/datablocks/experiment/item/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.item.base import ExperimentBase diff --git a/src/easydiffraction/datablocks/experiment/item/base.py b/src/easydiffraction/datablocks/experiment/item/base.py index 24c6ee6e..2dddee44 100644 --- a/src/easydiffraction/datablocks/experiment/item/base.py +++ b/src/easydiffraction/datablocks/experiment/item/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Base classes for experiment datablock items.""" @@ -34,16 +34,14 @@ class ExperimentBase(DatablockItem): - """Base class for all experiment datablock items with only core - attributes. - """ + """Base class for all experiment datablock items.""" def __init__( self, *, name: str, type: ExperimentType, - ): + ) -> None: super().__init__() self._name = name self._type = type @@ -58,18 +56,19 @@ def name(self) -> str: @name.setter def name(self, new: str) -> None: - """Rename the experiment. + """ + Rename the experiment. - Args: - new: New name for this experiment. + Parameters + ---------- + new : str + New name for this experiment. """ self._name = new @property - def type(self): # TODO: Consider another name - """Experiment type descriptor (sample form, probe, beam - mode). - """ + def type(self) -> object: # TODO: Consider another name + """Experiment type: sample form, probe, beam mode.""" return self._type @property @@ -86,10 +85,18 @@ def show_as_cif(self) -> None: @abstractmethod def _load_ascii_data_to_experiment(self, data_path: str) -> None: - """Load ASCII data from file into the experiment data category. + """ + Load ASCII data from file into the experiment data category. + + Parameters + ---------- + data_path : str + Path to the ASCII file to load. - Args: - data_path: Path to the ASCII file to load. + Raises + ------ + NotImplementedError + Subclasses must implement this method. """ raise NotImplementedError() @@ -98,8 +105,9 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: # ------------------------------------------------------------------ @property - def calculator(self): - """The active calculator instance for this experiment. + def calculator(self) -> object: + """ + The active calculator instance for this experiment. Auto-resolved on first access from the experiment's data category ``calculator_support`` and @@ -118,11 +126,14 @@ def calculator_type(self) -> str: @calculator_type.setter def calculator_type(self, tag: str) -> None: - """Switch to a different calculator backend. + """ + Switch to a different calculator backend. - Args: - tag: Calculator tag (e.g. ``'cryspy'``, ``'crysfml'``, - ``'pdffit'``). + Parameters + ---------- + tag : str + Calculator tag (e.g. ``'cryspy'``, ``'crysfml'``, + ``'pdffit'``). """ from easydiffraction.analysis.calculators.factory import CalculatorFactory @@ -140,9 +151,7 @@ def calculator_type(self, tag: str) -> None: console.print(tag) def show_supported_calculator_types(self) -> None: - """Print a table of calculator backends supported by this - experiment. - """ + """Print a table of supported calculator backends.""" from easydiffraction.analysis.calculators.factory import CalculatorFactory supported_tags = self._supported_calculator_tags() @@ -169,10 +178,7 @@ def show_current_calculator_type(self) -> None: console.print(self.calculator_type) def _resolve_calculator(self) -> None: - """Auto-resolve the default calculator from the data category's - ``calculator_support`` and - ``CalculatorFactory._default_rules``. - """ + """Auto-resolve the default calculator from data category.""" from easydiffraction.analysis.calculators.factory import CalculatorFactory tag = CalculatorFactory.default_tag( @@ -185,7 +191,8 @@ def _resolve_calculator(self) -> None: self._calculator_type = tag def _supported_calculator_tags(self) -> list[str]: - """Return calculator tags supported by this experiment. + """ + Return calculator tags supported by this experiment. Intersects the data category's ``calculator_support`` with calculators whose engines are importable. @@ -231,11 +238,14 @@ def __init__( @abstractmethod def _load_ascii_data_to_experiment(self, data_path: str) -> None: - """Load single crystal data from an ASCII file. + """ + Load single crystal data from an ASCII file. - Args: - data_path: Path to data file with columns compatible with - the beam mode. + Parameters + ---------- + data_path : str + Path to data file with columns compatible with the beam + mode. """ pass @@ -244,7 +254,7 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: # ------------------------------------------------------------------ @property - def extinction(self): + def extinction(self) -> object: """Active extinction correction model.""" return self._extinction @@ -255,10 +265,13 @@ def extinction_type(self) -> str: @extinction_type.setter def extinction_type(self, new_type: str) -> None: - """Switch to a different extinction correction model. + """ + Switch to a different extinction correction model. - Args: - new_type: Extinction tag (e.g. ``'shelx'``). + Parameters + ---------- + new_type : str + Extinction tag (e.g. ``'shelx'``). """ supported_tags = ExtinctionFactory.supported_tags() if new_type not in supported_tags: @@ -288,7 +301,7 @@ def show_current_extinction_type(self) -> None: # ------------------------------------------------------------------ @property - def linked_crystal(self): + def linked_crystal(self) -> object: """Linked crystal model for this experiment.""" return self._linked_crystal @@ -299,10 +312,13 @@ def linked_crystal_type(self) -> str: @linked_crystal_type.setter def linked_crystal_type(self, new_type: str) -> None: - """Switch to a different linked-crystal reference type. + """ + Switch to a different linked-crystal reference type. - Args: - new_type: Linked-crystal tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Linked-crystal tag (e.g. ``'default'``). """ supported_tags = LinkedCrystalFactory.supported_tags() if new_type not in supported_tags: @@ -332,7 +348,7 @@ def show_current_linked_crystal_type(self) -> None: # ------------------------------------------------------------------ @property - def instrument(self): + def instrument(self) -> object: """Active instrument model for this experiment.""" return self._instrument @@ -343,10 +359,13 @@ def instrument_type(self) -> str: @instrument_type.setter def instrument_type(self, new_type: str) -> None: - """Switch to a different instrument type. + """ + Switch to a different instrument type. - Args: - new_type: Instrument tag (e.g. ``'cwl-sc'``). + Parameters + ---------- + new_type : str + Instrument tag (e.g. ``'cwl-sc'``). """ supported = InstrumentFactory.supported_for( scattering_type=self.type.scattering_type.value, @@ -384,7 +403,7 @@ def show_current_instrument_type(self) -> None: # ------------------------------------------------------------------ @property - def data(self): + def data(self) -> object: """Data collection for this experiment.""" return self._data @@ -395,10 +414,13 @@ def data_type(self) -> str: @data_type.setter def data_type(self, new_type: str) -> None: - """Switch to a different data collection type. + """ + Switch to a different data collection type. - Args: - new_type: Data tag (e.g. ``'bragg-sc'``). + Parameters + ---------- + new_type : str + Data tag (e.g. ``'bragg-sc'``). """ supported_tags = DataFactory.supported_tags() if new_type not in supported_tags: @@ -454,12 +476,17 @@ def _get_valid_linked_phases( self, structures: Structures, ) -> List[Any]: - """Get valid linked phases for this experiment. + """ + Get valid linked phases for this experiment. - Args: - structures: Collection of structures. + Parameters + ---------- + structures : Structures + Collection of structures. - Returns: + Returns + ------- + List[Any] A list of valid linked phases. """ if not self.linked_phases: @@ -485,16 +512,19 @@ def _get_valid_linked_phases( @abstractmethod def _load_ascii_data_to_experiment(self, data_path: str) -> None: - """Load powder diffraction data from an ASCII file. + """ + Load powder diffraction data from an ASCII file. - Args: - data_path: Path to data file with columns compatible with - the beam mode (e.g. 2θ/I/σ for CWL, TOF/I/σ for TOF). + Parameters + ---------- + data_path : str + Path to data file with columns compatible with the beam mode + (e.g. 2θ/I/σ for CWL, TOF/I/σ for TOF). """ pass @property - def linked_phases(self): + def linked_phases(self) -> object: """Collection of phases linked to this experiment.""" return self._linked_phases @@ -505,10 +535,13 @@ def linked_phases_type(self) -> str: @linked_phases_type.setter def linked_phases_type(self, new_type: str) -> None: - """Switch to a different linked-phases collection type. + """ + Switch to a different linked-phases collection type. - Args: - new_type: Linked-phases tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Linked-phases tag (e.g. ``'default'``). """ supported_tags = LinkedPhasesFactory.supported_tags() if new_type not in supported_tags: @@ -534,7 +567,7 @@ def show_current_linked_phases_type(self) -> None: console.print(self.linked_phases_type) @property - def excluded_regions(self): + def excluded_regions(self) -> object: """Collection of excluded regions for the x-grid.""" return self._excluded_regions @@ -545,10 +578,13 @@ def excluded_regions_type(self) -> str: @excluded_regions_type.setter def excluded_regions_type(self, new_type: str) -> None: - """Switch to a different excluded-regions collection type. + """ + Switch to a different excluded-regions collection type. - Args: - new_type: Excluded-regions tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Excluded-regions tag (e.g. ``'default'``). """ supported_tags = ExcludedRegionsFactory.supported_tags() if new_type not in supported_tags: @@ -565,9 +601,7 @@ def excluded_regions_type(self, new_type: str) -> None: console.print(new_type) def show_supported_excluded_regions_types(self) -> None: - """Print a table of supported excluded-regions collection - types. - """ + """Print a table of supported excluded-regions types.""" ExcludedRegionsFactory.show_supported() def show_current_excluded_regions_type(self) -> None: @@ -580,7 +614,7 @@ def show_current_excluded_regions_type(self) -> None: # ------------------------------------------------------------------ @property - def data(self): + def data(self) -> object: """Data collection for this experiment.""" return self._data @@ -591,10 +625,13 @@ def data_type(self) -> str: @data_type.setter def data_type(self, new_type: str) -> None: - """Switch to a different data collection type. + """ + Switch to a different data collection type. - Args: - new_type: Data tag (e.g. ``'bragg-pd-cwl'``). + Parameters + ---------- + new_type : str + Data tag (e.g. ``'bragg-pd-cwl'``). """ supported_tags = DataFactory.supported_tags() if new_type not in supported_tags: @@ -619,21 +656,24 @@ def show_current_data_type(self) -> None: console.print(self.data_type) @property - def peak(self): + def peak(self) -> object: """Peak category object with profile parameters and mixins.""" return self._peak @property - def peak_profile_type(self): + def peak_profile_type(self) -> object: """Currently selected peak profile type enum.""" return self._peak_profile_type @peak_profile_type.setter - def peak_profile_type(self, new_type: str): - """Change the active peak profile type, if supported. + def peak_profile_type(self, new_type: str) -> None: + """ + Change the active peak profile type, if supported. - Args: - new_type: New profile type as tag string. + Parameters + ---------- + new_type : str + New profile type as tag string. """ supported = PeakFactory.supported_for( scattering_type=self.type.scattering_type.value, @@ -659,14 +699,14 @@ def peak_profile_type(self, new_type: str): console.paragraph(f"Peak profile type for experiment '{self.name}' changed to") console.print(new_type) - def show_supported_peak_profile_types(self): + def show_supported_peak_profile_types(self) -> None: """Print available peak profile types for this experiment.""" PeakFactory.show_supported( scattering_type=self.type.scattering_type.value, beam_mode=self.type.beam_mode.value, ) - def show_current_peak_profile_type(self): + def show_current_peak_profile_type(self) -> None: """Print the currently selected peak profile type.""" console.paragraph('Current peak profile type') console.print(self.peak_profile_type) diff --git a/src/easydiffraction/datablocks/experiment/item/bragg_pd.py b/src/easydiffraction/datablocks/experiment/item/bragg_pd.py index 258085c5..d5b469cf 100644 --- a/src/easydiffraction/datablocks/experiment/item/bragg_pd.py +++ b/src/easydiffraction/datablocks/experiment/item/bragg_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -25,9 +25,7 @@ @ExperimentFactory.register class BraggPdExperiment(PdExperimentBase): - """Standard (Bragg) Powder Diffraction experiment class with - specific attributes. - """ + """Standard Bragg powder diffraction experiment.""" type_info = TypeInfo( tag='bragg-pd', @@ -57,8 +55,8 @@ def __init__( self._background = BackgroundFactory.create(self._background_type) def _load_ascii_data_to_experiment(self, data_path: str) -> None: - """Load (x, y, sy) data from an ASCII file into the data - category. + """ + Load (x, y, sy) data from an ASCII file into the data category. The file format is space/column separated with 2 or 3 columns: ``x y [sy]``. If ``sy`` is missing, it is approximated as @@ -105,7 +103,7 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: # ------------------------------------------------------------------ @property - def instrument(self): + def instrument(self) -> object: """Active instrument model for this experiment.""" return self._instrument @@ -116,10 +114,13 @@ def instrument_type(self) -> str: @instrument_type.setter def instrument_type(self, new_type: str) -> None: - """Switch to a different instrument type. + """ + Switch to a different instrument type. - Args: - new_type: Instrument tag (e.g. ``'cwl-pd'``). + Parameters + ---------- + new_type : str + Instrument tag (e.g. ``'cwl-pd'``). """ supported = InstrumentFactory.supported_for( scattering_type=self.type.scattering_type.value, @@ -157,12 +158,12 @@ def show_current_instrument_type(self) -> None: # ------------------------------------------------------------------ @property - def background_type(self): + def background_type(self) -> object: """Current background type enum value.""" return self._background_type @background_type.setter - def background_type(self, new_type): + def background_type(self, new_type: str) -> None: """Set a new background type and recreate background object.""" if self._background_type == new_type: console.paragraph(f"Background type for experiment '{self.name}' already set to") @@ -190,14 +191,15 @@ def background_type(self, new_type): console.print(new_type) @property - def background(self): + def background(self) -> object: + """Active background model for this experiment.""" return self._background - def show_supported_background_types(self): + def show_supported_background_types(self) -> None: """Print a table of supported background types.""" BackgroundFactory.show_supported() - def show_current_background_type(self): + def show_current_background_type(self) -> None: """Print the currently used background type.""" console.paragraph('Current background type') console.print(self.background_type) diff --git a/src/easydiffraction/datablocks/experiment/item/bragg_sc.py b/src/easydiffraction/datablocks/experiment/item/bragg_sc.py index e8142e0e..3cb3f8a1 100644 --- a/src/easydiffraction/datablocks/experiment/item/bragg_sc.py +++ b/src/easydiffraction/datablocks/experiment/item/bragg_sc.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -23,9 +23,7 @@ @ExperimentFactory.register class CwlScExperiment(ScExperimentBase): - """Standard (Bragg) constant wavelength single srystal experiment - class with specific attributes. - """ + """Bragg constant-wavelength single-crystal experiment.""" type_info = TypeInfo( tag='bragg-sc-cwl', @@ -46,10 +44,11 @@ def __init__( super().__init__(name=name, type=type) def _load_ascii_data_to_experiment(self, data_path: str) -> None: - """Load measured data from an ASCII file into the data category. + """ + Load measured data from an ASCII file into the data category. - The file format is space/column separated with 5 columns: - ``h k l Iobs sIobs``. + The file format is space/column separated with 5 columns: ``h k + l Iobs sIobs``. """ try: data = np.loadtxt(data_path) @@ -87,9 +86,7 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: @ExperimentFactory.register class TofScExperiment(ScExperimentBase): - """Standard (Bragg) time-of-flight single srystal experiment class - with specific attributes. - """ + """Bragg time-of-flight single-crystal experiment.""" type_info = TypeInfo( tag='bragg-sc-tof', @@ -110,10 +107,11 @@ def __init__( super().__init__(name=name, type=type) def _load_ascii_data_to_experiment(self, data_path: str) -> None: - """Load measured data from an ASCII file into the data category. + """ + Load measured data from an ASCII file into the data category. - The file format is space/column separated with 6 columns: - ``h k l Iobs sIobs wavelength``. + The file format is space/column separated with 6 columns: ``h k + l Iobs sIobs wavelength``. """ try: data = np.loadtxt(data_path) diff --git a/src/easydiffraction/datablocks/experiment/item/enums.py b/src/easydiffraction/datablocks/experiment/item/enums.py index 8d0f153e..2375c38e 100644 --- a/src/easydiffraction/datablocks/experiment/item/enums.py +++ b/src/easydiffraction/datablocks/experiment/item/enums.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Enumerations for experiment configuration (forms, modes, types).""" @@ -13,9 +13,25 @@ class SampleFormEnum(str, Enum): @classmethod def default(cls) -> 'SampleFormEnum': + """ + Return the default sample form (POWDER). + + Returns + ------- + 'SampleFormEnum' + The default enum member. + """ return cls.POWDER def description(self) -> str: + """ + Return a human-readable description of this sample form. + + Returns + ------- + str + Description string for the current enum member. + """ if self is SampleFormEnum.POWDER: return 'Powdered or polycrystalline sample.' elif self is SampleFormEnum.SINGLE_CRYSTAL: @@ -30,9 +46,25 @@ class ScatteringTypeEnum(str, Enum): @classmethod def default(cls) -> 'ScatteringTypeEnum': + """ + Return the default scattering type (BRAGG). + + Returns + ------- + 'ScatteringTypeEnum' + The default enum member. + """ return cls.BRAGG def description(self) -> str: + """ + Return a human-readable description of this scattering type. + + Returns + ------- + str + Description string for the current enum member. + """ if self is ScatteringTypeEnum.BRAGG: return 'Bragg diffraction for conventional structure refinement.' elif self is ScatteringTypeEnum.TOTAL: @@ -47,9 +79,25 @@ class RadiationProbeEnum(str, Enum): @classmethod def default(cls) -> 'RadiationProbeEnum': + """ + Return the default radiation probe (NEUTRON). + + Returns + ------- + 'RadiationProbeEnum' + The default enum member. + """ return cls.NEUTRON def description(self) -> str: + """ + Return a human-readable description of this radiation probe. + + Returns + ------- + str + Description string for the current enum member. + """ if self is RadiationProbeEnum.NEUTRON: return 'Neutron diffraction.' elif self is RadiationProbeEnum.XRAY: @@ -65,9 +113,25 @@ class BeamModeEnum(str, Enum): @classmethod def default(cls) -> 'BeamModeEnum': + """ + Return the default beam mode (CONSTANT_WAVELENGTH). + + Returns + ------- + 'BeamModeEnum' + The default enum member. + """ return cls.CONSTANT_WAVELENGTH def description(self) -> str: + """ + Return a human-readable description of this beam mode. + + Returns + ------- + str + Description string for the current enum member. + """ if self is BeamModeEnum.CONSTANT_WAVELENGTH: return 'Constant wavelength (CW) diffraction.' elif self is BeamModeEnum.TIME_OF_FLIGHT: @@ -104,6 +168,23 @@ def default( scattering_type: ScatteringTypeEnum | None = None, beam_mode: BeamModeEnum | None = None, ) -> 'PeakProfileTypeEnum': + """ + Return the default peak profile type for a given mode. + + Parameters + ---------- + scattering_type : ScatteringTypeEnum | None, default=None + Scattering type; defaults to + ``ScatteringTypeEnum.default()`` when ``None``. + beam_mode : BeamModeEnum | None, default=None + Beam mode; defaults to ``BeamModeEnum.default()`` when + ``None``. + + Returns + ------- + 'PeakProfileTypeEnum' + The default profile type for the given combination. + """ if scattering_type is None: scattering_type = ScatteringTypeEnum.default() if beam_mode is None: @@ -119,6 +200,14 @@ def default( }[(scattering_type, beam_mode)] def description(self) -> str: + """ + Return a human-readable description of this peak profile type. + + Returns + ------- + str + Description string for the current enum member. + """ if self is PeakProfileTypeEnum.PSEUDO_VOIGT: return 'Pseudo-Voigt profile' elif self is PeakProfileTypeEnum.SPLIT_PSEUDO_VOIGT: diff --git a/src/easydiffraction/datablocks/experiment/item/factory.py b/src/easydiffraction/datablocks/experiment/item/factory.py index 3e5aebb5..6406ed30 100644 --- a/src/easydiffraction/datablocks/experiment/item/factory.py +++ b/src/easydiffraction/datablocks/experiment/item/factory.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Factory for creating experiment instances from various inputs. +""" +Factory for creating experiment instances from various inputs. Provides individual class methods for each creation pathway: ``from_cif_path``, ``from_cif_str``, ``from_data_path``, and @@ -55,7 +56,7 @@ class ExperimentFactory(FactoryBase): } # TODO: Add to core/factory.py? - def __init__(self): + def __init__(self) -> None: log.error( 'Experiment objects must be created using class methods such as ' '`ExperimentFactory.from_cif_str(...)`, etc.' @@ -74,9 +75,7 @@ def _create_experiment_type( radiation_probe: str | None = None, scattering_type: str | None = None, ) -> ExperimentType: - """Construct an ExperimentType, using defaults for omitted - values. - """ + """Construct ExperimentType with defaults for omitted values.""" # Note: validation of input values is done via Descriptor setter # methods @@ -95,7 +94,7 @@ def _create_experiment_type( @classmethod @typechecked - def _resolve_class(cls, expt_type: ExperimentType): + def _resolve_class(cls, expt_type: ExperimentType) -> type: """Look up the experiment class from the type enums.""" tag = cls.default_tag( scattering_type=expt_type.scattering_type.value, @@ -140,16 +139,25 @@ def from_scratch( radiation_probe: str | None = None, scattering_type: str | None = None, ) -> ExperimentBase: - """Create an experiment without measured data. - - Args: - name: Experiment identifier. - sample_form: Sample form (e.g. ``'powder'``). - beam_mode: Beam mode (e.g. ``'constant wavelength'``). - radiation_probe: Radiation probe (e.g. ``'neutron'``). - scattering_type: Scattering type (e.g. ``'bragg'``). - - Returns: + """ + Create an experiment without measured data. + + Parameters + ---------- + name : str + Experiment identifier. + sample_form : str | None, default=None + Sample form (e.g. ``'powder'``). + beam_mode : str | None, default=None + Beam mode (e.g. ``'constant wavelength'``). + radiation_probe : str | None, default=None + Radiation probe (e.g. ``'neutron'``). + scattering_type : str | None, default=None + Scattering type (e.g. ``'bragg'``). + + Returns + ------- + ExperimentBase An experiment instance with only metadata. """ expt_type = cls._create_experiment_type( @@ -169,12 +177,17 @@ def from_cif_str( cls, cif_str: str, ) -> ExperimentBase: - """Create an experiment from a CIF string. + """ + Create an experiment from a CIF string. - Args: - cif_str: Full CIF document as a string. + Parameters + ---------- + cif_str : str + Full CIF document as a string. - Returns: + Returns + ------- + ExperimentBase A populated experiment instance. """ doc = document_from_string(cif_str) @@ -188,12 +201,17 @@ def from_cif_path( cls, cif_path: str, ) -> ExperimentBase: - """Create an experiment from a CIF file path. + """ + Create an experiment from a CIF file path. - Args: - cif_path: Path to a CIF file. + Parameters + ---------- + cif_path : str + Path to a CIF file. - Returns: + Returns + ------- + ExperimentBase A populated experiment instance. """ doc = document_from_path(cif_path) @@ -212,17 +230,27 @@ def from_data_path( radiation_probe: str | None = None, scattering_type: str | None = None, ) -> ExperimentBase: - """Create an experiment from a raw data ASCII file. - - Args: - name: Experiment identifier. - data_path: Path to the measured data file. - sample_form: Sample form (e.g. ``'powder'``). - beam_mode: Beam mode (e.g. ``'constant wavelength'``). - radiation_probe: Radiation probe (e.g. ``'neutron'``). - scattering_type: Scattering type (e.g. ``'bragg'``). - - Returns: + """ + Create an experiment from a raw data ASCII file. + + Parameters + ---------- + name : str + Experiment identifier. + data_path : str + Path to the measured data file. + sample_form : str | None, default=None + Sample form (e.g. ``'powder'``). + beam_mode : str | None, default=None + Beam mode (e.g. ``'constant wavelength'``). + radiation_probe : str | None, default=None + Radiation probe (e.g. ``'neutron'``). + scattering_type : str | None, default=None + Scattering type (e.g. ``'bragg'``). + + Returns + ------- + ExperimentBase An experiment instance with measured data attached. """ expt_obj = cls.from_scratch( diff --git a/src/easydiffraction/datablocks/experiment/item/total_pd.py b/src/easydiffraction/datablocks/experiment/item/total_pd.py index 881dd0ce..ade11e6f 100644 --- a/src/easydiffraction/datablocks/experiment/item/total_pd.py +++ b/src/easydiffraction/datablocks/experiment/item/total_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -38,15 +38,14 @@ def __init__( self, name: str, type: ExperimentType, - ): + ) -> None: super().__init__(name=name, type=type) - def _load_ascii_data_to_experiment(self, data_path): - """Loads x, y, sy values from an ASCII data file into the - experiment. + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + """ + Load x, y, sy values from an ASCII file into the experiment. - The file must be structured as: - x y sy + The file must be structured as: x y sy """ try: from diffpy.utils.parsers.loaddata import loadData diff --git a/src/easydiffraction/datablocks/structure/__init__.py b/src/easydiffraction/datablocks/structure/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/datablocks/structure/__init__.py +++ b/src/easydiffraction/datablocks/structure/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/datablocks/structure/categories/__init__.py b/src/easydiffraction/datablocks/structure/categories/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/datablocks/structure/categories/__init__.py +++ b/src/easydiffraction/datablocks/structure/categories/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/datablocks/structure/categories/atom_sites/__init__.py b/src/easydiffraction/datablocks/structure/categories/atom_sites/__init__.py index cb4c1750..7bd7b9ad 100644 --- a/src/easydiffraction/datablocks/structure/categories/atom_sites/__init__.py +++ b/src/easydiffraction/datablocks/structure/categories/atom_sites/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.structure.categories.atom_sites.default import AtomSite diff --git a/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py b/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py index 76d890d5..265c3c62 100644 --- a/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py +++ b/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Atom site category. +""" +Atom site category. Defines :class:`AtomSite` items and :class:`AtomSites` collection used in crystallographic structures. @@ -25,7 +26,8 @@ class AtomSite(CategoryItem): - """Single atom site with fractional coordinates and ADP. + """ + Single atom site with fractional coordinates and ADP. Attributes are represented by descriptors to support validation and CIF serialization. @@ -138,19 +140,25 @@ def __init__(self) -> None: @property def _type_symbol_allowed_values(self) -> list[str]: - """Return chemical symbols accepted by *cryspy*. + """ + Return chemical symbols accepted by *cryspy*. - Returns: - list[str]: Unique element/isotope symbols from the database. + Returns + ------- + list[str] + Unique element/isotope symbols from the database. """ return list({key[1] for key in DATABASE['Isotopes']}) @property def _wyckoff_letter_allowed_values(self) -> list[str]: - """Return allowed Wyckoff-letter symbols. + """ + Return allowed Wyckoff-letter symbols. - Returns: - list[str]: Currently a hard-coded placeholder list. + Returns + ------- + list[str] + Currently a hard-coded placeholder list. """ # TODO: Need to now current space group. How to access it? Via # parent Cell? Then letters = @@ -160,10 +168,13 @@ def _wyckoff_letter_allowed_values(self) -> list[str]: @property def _wyckoff_letter_default_value(self) -> str: - """Return the default Wyckoff letter. + """ + Return the default Wyckoff letter. - Returns: - str: First element of the allowed values list. + Returns + ------- + str + First element of the allowed values list. """ # TODO: What to pass as default? return self._wyckoff_letter_allowed_values[0] @@ -174,165 +185,132 @@ def _wyckoff_letter_default_value(self) -> str: @property def label(self) -> StringDescriptor: - """Unique label for this atom site. + """ + Unique identifier for the atom site. - Returns: - StringDescriptor: Descriptor holding the site label. + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. """ return self._label @label.setter def label(self, value: str) -> None: - """Set the atom-site label. - - Args: - value (str): New label string. - """ self._label.value = value @property def type_symbol(self) -> StringDescriptor: - """Chemical element or isotope symbol. + """ + Chemical symbol of the atom at this site. - Returns: - StringDescriptor: Descriptor holding the type symbol. + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. """ return self._type_symbol @type_symbol.setter def type_symbol(self, value: str) -> None: - """Set the chemical element or isotope symbol. - - Args: - value (str): New type symbol (must be in the *cryspy* - database). - """ self._type_symbol.value = value @property def adp_type(self) -> StringDescriptor: - """Type of atomic displacement parameter (e.g. ``'Biso'``). + """ + ADP type used (e.g., Biso, Uiso, Uani, Bani). - Returns: - StringDescriptor: Descriptor holding the ADP type. + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. """ return self._adp_type @adp_type.setter def adp_type(self, value: str) -> None: - """Set the ADP type. - - Args: - value (str): New ADP type string. - """ self._adp_type.value = value @property def wyckoff_letter(self) -> StringDescriptor: - """Wyckoff letter for the symmetry site. + """ + Wyckoff letter for the atom site symmetry position. - Returns: - StringDescriptor: Descriptor holding the Wyckoff letter. + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. """ return self._wyckoff_letter @wyckoff_letter.setter def wyckoff_letter(self, value: str) -> None: - """Set the Wyckoff letter. - - Args: - value (str): New Wyckoff letter. - """ self._wyckoff_letter.value = value @property def fract_x(self) -> Parameter: - """Fractional *x*-coordinate within the unit cell. + """ + Fractional x-coordinate of the atom site within the unit cell. - Returns: - Parameter: Descriptor for the *x* coordinate. + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._fract_x @fract_x.setter def fract_x(self, value: float) -> None: - """Set the fractional *x*-coordinate. - - Args: - value (float): New *x* coordinate. - """ self._fract_x.value = value @property def fract_y(self) -> Parameter: - """Fractional *y*-coordinate within the unit cell. + """ + Fractional y-coordinate of the atom site within the unit cell. - Returns: - Parameter: Descriptor for the *y* coordinate. + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._fract_y @fract_y.setter def fract_y(self, value: float) -> None: - """Set the fractional *y*-coordinate. - - Args: - value (float): New *y* coordinate. - """ self._fract_y.value = value @property def fract_z(self) -> Parameter: - """Fractional *z*-coordinate within the unit cell. + """ + Fractional z-coordinate of the atom site within the unit cell. - Returns: - Parameter: Descriptor for the *z* coordinate. + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._fract_z @fract_z.setter def fract_z(self, value: float) -> None: - """Set the fractional *z*-coordinate. - - Args: - value (float): New *z* coordinate. - """ self._fract_z.value = value @property def occupancy(self) -> Parameter: - """Site occupancy fraction. + """ + Occupancy fraction of the atom type at this site. - Returns: - Parameter: Descriptor for the occupancy (0–1). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._occupancy @occupancy.setter def occupancy(self, value: float) -> None: - """Set the site occupancy. - - Args: - value (float): New occupancy fraction. - """ self._occupancy.value = value @property def b_iso(self) -> Parameter: - r"""Isotropic atomic displacement parameter (*B*-factor). + """ + Isotropic ADP for the atom site (Ų). - Returns: - Parameter: Descriptor for *B*\_iso (Ų). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._b_iso @b_iso.setter def b_iso(self, value: float) -> None: - r"""Set the isotropic displacement parameter. - - Args: - value (float): New *B*\_iso value in Ų. - """ self._b_iso.value = value @@ -354,7 +332,8 @@ def __init__(self) -> None: # ------------------------------------------------------------------ def _apply_atomic_coordinates_symmetry_constraints(self) -> None: - """Apply symmetry rules to fractional coordinates of every site. + """ + Apply symmetry rules to fractional coordinates of every site. Uses the parent structure's space-group symbol, IT coordinate system code and each atom's Wyckoff letter. Atoms without a @@ -387,11 +366,14 @@ def _update( self, called_by_minimizer: bool = False, ) -> None: - """Recalculate atom sites after a change. + """ + Recalculate atom sites after a change. - Args: - called_by_minimizer (bool): Whether the update was triggered - by the fitting minimizer. Currently unused. + Parameters + ---------- + called_by_minimizer : bool, default=False + Whether the update was triggered by the fitting minimizer. + Currently unused. """ del called_by_minimizer diff --git a/src/easydiffraction/datablocks/structure/categories/atom_sites/factory.py b/src/easydiffraction/datablocks/structure/categories/atom_sites/factory.py index e233d0bd..c91b3dda 100644 --- a/src/easydiffraction/datablocks/structure/categories/atom_sites/factory.py +++ b/src/easydiffraction/datablocks/structure/categories/atom_sites/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Atom-sites factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/structure/categories/cell/__init__.py b/src/easydiffraction/datablocks/structure/categories/cell/__init__.py index 08773b3e..16f6cab2 100644 --- a/src/easydiffraction/datablocks/structure/categories/cell/__init__.py +++ b/src/easydiffraction/datablocks/structure/categories/cell/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.structure.categories.cell.default import Cell diff --git a/src/easydiffraction/datablocks/structure/categories/cell/default.py b/src/easydiffraction/datablocks/structure/categories/cell/default.py index e07e20e0..53cc98ee 100644 --- a/src/easydiffraction/datablocks/structure/categories/cell/default.py +++ b/src/easydiffraction/datablocks/structure/categories/cell/default.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Unit cell parameters category for structures.""" @@ -16,8 +16,8 @@ @CellFactory.register class Cell(CategoryItem): - """Unit cell with lengths *a*, *b*, *c* and angles *alpha*, *beta*, - *gamma*. + """ + Unit cell with lengths a, b, c and angles alpha, beta, gamma. All six lattice parameters are exposed as :class:`Parameter` descriptors supporting validation, fitting and CIF serialization. @@ -34,7 +34,7 @@ def __init__(self) -> None: self._length_a = Parameter( name='length_a', - description='Length of the a axis of the unit cell.', + description='Length of the a axis of the unit cell', units='Å', value_spec=AttributeSpec( default=10.0, @@ -44,7 +44,7 @@ def __init__(self) -> None: ) self._length_b = Parameter( name='length_b', - description='Length of the b axis of the unit cell.', + description='Length of the b axis of the unit cell', units='Å', value_spec=AttributeSpec( default=10.0, @@ -54,7 +54,7 @@ def __init__(self) -> None: ) self._length_c = Parameter( name='length_c', - description='Length of the c axis of the unit cell.', + description='Length of the c axis of the unit cell', units='Å', value_spec=AttributeSpec( default=10.0, @@ -64,7 +64,7 @@ def __init__(self) -> None: ) self._angle_alpha = Parameter( name='angle_alpha', - description='Angle between edges b and c.', + description='Angle between edges b and c', units='deg', value_spec=AttributeSpec( default=90.0, @@ -74,7 +74,7 @@ def __init__(self) -> None: ) self._angle_beta = Parameter( name='angle_beta', - description='Angle between edges a and c.', + description='Angle between edges a and c', units='deg', value_spec=AttributeSpec( default=90.0, @@ -84,7 +84,7 @@ def __init__(self) -> None: ) self._angle_gamma = Parameter( name='angle_gamma', - description='Angle between edges a and b.', + description='Angle between edges a and b', units='deg', value_spec=AttributeSpec( default=90.0, @@ -100,7 +100,8 @@ def __init__(self) -> None: # ------------------------------------------------------------------ def _apply_cell_symmetry_constraints(self) -> None: - """Apply symmetry constraints to cell parameters in place. + """ + Apply symmetry constraints to cell parameters in place. Uses the parent structure's space-group symbol to determine which lattice parameters are dependent and sets them @@ -132,11 +133,14 @@ def _update( self, called_by_minimizer: bool = False, ) -> None: - """Recalculate cell parameters after a change. + """ + Recalculate cell parameters after a change. - Args: - called_by_minimizer (bool): Whether the update was triggered - by the fitting minimizer. Currently unused. + Parameters + ---------- + called_by_minimizer : bool, default=False + Whether the update was triggered by the fitting minimizer. + Currently unused. """ del called_by_minimizer # TODO: ??? @@ -148,108 +152,84 @@ def _update( @property def length_a(self) -> Parameter: - """Length of the *a* axis. + """ + Length of the a axis of the unit cell (Å). - Returns: - Parameter: Descriptor for lattice parameter *a* (Å). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._length_a @length_a.setter def length_a(self, value: float) -> None: - """Set the length of the *a* axis. - - Args: - value (float): New length in ångströms. - """ self._length_a.value = value @property def length_b(self) -> Parameter: - """Length of the *b* axis. + """ + Length of the b axis of the unit cell (Å). - Returns: - Parameter: Descriptor for lattice parameter *b* (Å). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._length_b @length_b.setter def length_b(self, value: float) -> None: - """Set the length of the *b* axis. - - Args: - value (float): New length in ångströms. - """ self._length_b.value = value @property def length_c(self) -> Parameter: - """Length of the *c* axis. + """ + Length of the c axis of the unit cell (Å). - Returns: - Parameter: Descriptor for lattice parameter *c* (Å). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._length_c @length_c.setter def length_c(self, value: float) -> None: - """Set the length of the *c* axis. - - Args: - value (float): New length in ångströms. - """ self._length_c.value = value @property def angle_alpha(self) -> Parameter: - """Angle between edges *b* and *c*. + """ + Angle between edges b and c (deg). - Returns: - Parameter: Descriptor for angle *α* (degrees). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._angle_alpha @angle_alpha.setter def angle_alpha(self, value: float) -> None: - """Set the angle between edges *b* and *c*. - - Args: - value (float): New angle in degrees. - """ self._angle_alpha.value = value @property def angle_beta(self) -> Parameter: - """Angle between edges *a* and *c*. + """ + Angle between edges a and c (deg). - Returns: - Parameter: Descriptor for angle *β* (degrees). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._angle_beta @angle_beta.setter def angle_beta(self, value: float) -> None: - """Set the angle between edges *a* and *c*. - - Args: - value (float): New angle in degrees. - """ self._angle_beta.value = value @property def angle_gamma(self) -> Parameter: - """Angle between edges *a* and *b*. + """ + Angle between edges a and b (deg). - Returns: - Parameter: Descriptor for angle *γ* (degrees). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._angle_gamma @angle_gamma.setter def angle_gamma(self, value: float) -> None: - """Set the angle between edges *a* and *b*. - - Args: - value (float): New angle in degrees. - """ self._angle_gamma.value = value diff --git a/src/easydiffraction/datablocks/structure/categories/cell/factory.py b/src/easydiffraction/datablocks/structure/categories/cell/factory.py index c5fde941..6817b2d7 100644 --- a/src/easydiffraction/datablocks/structure/categories/cell/factory.py +++ b/src/easydiffraction/datablocks/structure/categories/cell/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Cell factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/structure/categories/space_group/__init__.py b/src/easydiffraction/datablocks/structure/categories/space_group/__init__.py index daf02947..a8b33e62 100644 --- a/src/easydiffraction/datablocks/structure/categories/space_group/__init__.py +++ b/src/easydiffraction/datablocks/structure/categories/space_group/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.structure.categories.space_group.default import SpaceGroup diff --git a/src/easydiffraction/datablocks/structure/categories/space_group/default.py b/src/easydiffraction/datablocks/structure/categories/space_group/default.py index 7076d272..a91cc554 100644 --- a/src/easydiffraction/datablocks/structure/categories/space_group/default.py +++ b/src/easydiffraction/datablocks/structure/categories/space_group/default.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Space group category for crystallographic structures.""" @@ -21,8 +21,8 @@ @SpaceGroupFactory.register class SpaceGroup(CategoryItem): - """Space group with Hermann–Mauguin symbol and IT coordinate system - code. + """ + Space group with H-M symbol and IT coordinate system code. Holds the space-group symbol (``name_h_m``) and the International Tables coordinate-system qualifier (``it_coordinate_system_code``). @@ -84,28 +84,30 @@ def __init__(self) -> None: # ------------------------------------------------------------------ def _reset_it_coordinate_system_code(self) -> None: - """Reset the IT coordinate system code to the default for the - current group. - """ + """Reset IT coordinate system code to default for this group.""" self._it_coordinate_system_code.value = self._it_coordinate_system_code_default_value @property def _name_h_m_allowed_values(self) -> list[str]: - """Return the list of recognised Hermann–Mauguin short symbols. + """ + Return the list of recognised Hermann–Mauguin short symbols. - Returns: - list[str]: All short H-M symbols known to *cryspy*. + Returns + ------- + list[str] + All short H-M symbols known to *cryspy*. """ return ACCESIBLE_NAME_HM_SHORT @property def _it_coordinate_system_code_allowed_values(self) -> list[str]: - """Return allowed IT coordinate system codes for the current - group. + """ + Return allowed IT coordinate system codes for the current group. - Returns: - list[str]: Coordinate-system codes, or ``['']`` when none - are defined. + Returns + ------- + list[str] + Coordinate-system codes, or ``['']`` when none are defined. """ name = self.name_h_m.value it_number = get_it_number_by_name_hm_short(name) @@ -115,10 +117,13 @@ def _it_coordinate_system_code_allowed_values(self) -> list[str]: @property def _it_coordinate_system_code_default_value(self) -> str: - """Return the default IT coordinate system code. + """ + Return the default IT coordinate system code. - Returns: - str: First element of the allowed codes list. + Returns + ------- + str + First element of the allowed codes list. """ return self._it_coordinate_system_code_allowed_values[0] @@ -128,40 +133,31 @@ def _it_coordinate_system_code_default_value(self) -> str: @property def name_h_m(self) -> StringDescriptor: - """Hermann–Mauguin symbol of the space group. + """ + Hermann-Mauguin symbol of the space group. - Returns: - StringDescriptor: Descriptor holding the H-M symbol. + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. """ return self._name_h_m @name_h_m.setter def name_h_m(self, value: str) -> None: - """Set the Hermann–Mauguin symbol and reset the coordinate- - system code. - - Args: - value (str): New H-M symbol (must be a recognised short - symbol). - """ self._name_h_m.value = value self._reset_it_coordinate_system_code() @property def it_coordinate_system_code(self) -> StringDescriptor: - """International Tables coordinate-system code. + """ + A qualifier identifying which setting in IT is used. - Returns: - StringDescriptor: Descriptor holding the IT code. + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. """ return self._it_coordinate_system_code @it_coordinate_system_code.setter def it_coordinate_system_code(self, value: str) -> None: - """Set the IT coordinate-system code. - - Args: - value (str): New coordinate-system code (must be allowed for - the current space group). - """ self._it_coordinate_system_code.value = value diff --git a/src/easydiffraction/datablocks/structure/categories/space_group/factory.py b/src/easydiffraction/datablocks/structure/categories/space_group/factory.py index 87807cef..9ef8611d 100644 --- a/src/easydiffraction/datablocks/structure/categories/space_group/factory.py +++ b/src/easydiffraction/datablocks/structure/categories/space_group/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Space-group factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/structure/collection.py b/src/easydiffraction/datablocks/structure/collection.py index ecc8f26e..801b416a 100644 --- a/src/easydiffraction/datablocks/structure/collection.py +++ b/src/easydiffraction/datablocks/structure/collection.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Collection of structure data blocks.""" @@ -11,7 +11,8 @@ class Structures(DatablockCollection): - """Ordered collection of :class:`Structure` instances. + """ + Ordered collection of :class:`Structure` instances. Provides convenience ``add_from_*`` methods that mirror the :class:`StructureFactory` classmethods plus a bare :meth:`add` for @@ -33,10 +34,13 @@ def create( *, name: str, ) -> None: - """Create a minimal structure and add it to the collection. + """ + Create a minimal structure and add it to the collection. - Args: - name (str): Identifier for the new structure. + Parameters + ---------- + name : str + Identifier for the new structure. """ structure = StructureFactory.from_scratch(name=name) self.add(structure) @@ -47,10 +51,13 @@ def add_from_cif_str( self, cif_str: str, ) -> None: - """Create a structure from CIF content and add it. + """ + Create a structure from CIF content and add it. - Args: - cif_str (str): CIF file content as a string. + Parameters + ---------- + cif_str : str + CIF file content as a string. """ structure = StructureFactory.from_cif_str(cif_str) self.add(structure) @@ -61,10 +68,13 @@ def add_from_cif_path( self, cif_path: str, ) -> None: - """Create a structure from a CIF file and add it. + """ + Create a structure from a CIF file and add it. - Args: - cif_path (str): Filesystem path to a CIF file. + Parameters + ---------- + cif_path : str + Filesystem path to a CIF file. """ structure = StructureFactory.from_cif_path(cif_path) self.add(structure) diff --git a/src/easydiffraction/datablocks/structure/item/__init__.py b/src/easydiffraction/datablocks/structure/item/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/datablocks/structure/item/__init__.py +++ b/src/easydiffraction/datablocks/structure/item/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/datablocks/structure/item/base.py b/src/easydiffraction/datablocks/structure/item/base.py index cd517785..80d8f76a 100644 --- a/src/easydiffraction/datablocks/structure/item/base.py +++ b/src/easydiffraction/datablocks/structure/item/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Structure datablock item.""" @@ -40,20 +40,26 @@ def __init__( @property def name(self) -> str: - """Name identifier for this structure. + """ + Name identifier for this structure. - Returns: - str: The structure's name. + Returns + ------- + str + The structure's name. """ return self._name @name.setter @typechecked def name(self, new: str) -> None: - """Set the name identifier for this structure. + """ + Set the name identifier for this structure. - Args: - new (str): New name string. + Parameters + ---------- + new : str + New name string. """ self._name = new @@ -69,10 +75,13 @@ def cell(self) -> Cell: @cell.setter @typechecked def cell(self, new: Cell) -> None: - """Replace the unit-cell category for this structure. + """ + Replace the unit-cell category for this structure. - Args: - new (Cell): New unit-cell instance. + Parameters + ---------- + new : Cell + New unit-cell instance. """ self._cell = new @@ -83,10 +92,13 @@ def cell_type(self) -> str: @cell_type.setter def cell_type(self, new_type: str) -> None: - """Switch to a different unit-cell type. + """ + Switch to a different unit-cell type. - Args: - new_type: Cell tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Cell tag (e.g. ``'default'``). """ supported_tags = CellFactory.supported_tags() if new_type not in supported_tags: @@ -122,10 +134,13 @@ def space_group(self) -> SpaceGroup: @space_group.setter @typechecked def space_group(self, new: SpaceGroup) -> None: - """Replace the space-group category for this structure. + """ + Replace the space-group category for this structure. - Args: - new (SpaceGroup): New space-group instance. + Parameters + ---------- + new : SpaceGroup + New space-group instance. """ self._space_group = new @@ -136,10 +151,13 @@ def space_group_type(self) -> str: @space_group_type.setter def space_group_type(self, new_type: str) -> None: - """Switch to a different space-group type. + """ + Switch to a different space-group type. - Args: - new_type: Space-group tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Space-group tag (e.g. ``'default'``). """ supported_tags = SpaceGroupFactory.supported_tags() if new_type not in supported_tags: @@ -175,10 +193,13 @@ def atom_sites(self) -> AtomSites: @atom_sites.setter @typechecked def atom_sites(self, new: AtomSites) -> None: - """Replace the atom-sites collection for this structure. + """ + Replace the atom-sites collection for this structure. - Args: - new (AtomSites): New atom-sites collection. + Parameters + ---------- + new : AtomSites + New atom-sites collection. """ self._atom_sites = new @@ -189,10 +210,13 @@ def atom_sites_type(self) -> str: @atom_sites_type.setter def atom_sites_type(self, new_type: str) -> None: - """Switch to a different atom-sites collection type. + """ + Switch to a different atom-sites collection type. - Args: - new_type: Atom-sites tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Atom-sites tag (e.g. ``'default'``). """ supported_tags = AtomSitesFactory.supported_tags() if new_type not in supported_tags: @@ -221,9 +245,7 @@ def show_current_atom_sites_type(self) -> None: # ------------------------------------------------------------------ def show(self) -> None: - """Display an ASCII projection of the structure on a 2D - plane. - """ + """Display an ASCII projection of the structure in 2D.""" console.paragraph(f"Structure 🧩 '{self.name}'") console.print('Not implemented yet.') diff --git a/src/easydiffraction/datablocks/structure/item/factory.py b/src/easydiffraction/datablocks/structure/item/factory.py index a2067dff..567d26dc 100644 --- a/src/easydiffraction/datablocks/structure/item/factory.py +++ b/src/easydiffraction/datablocks/structure/item/factory.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Factory for creating structure instances from various inputs. +""" +Factory for creating structure instances from various inputs. Provides individual class methods for each creation pathway: ``from_scratch``, ``from_cif_path``, or ``from_cif_str``. @@ -26,7 +27,7 @@ class StructureFactory: """Create :class:`Structure` instances from supported inputs.""" - def __init__(self): + def __init__(self) -> None: log.error( 'Structure objects must be created using class methods such as ' '`StructureFactory.from_cif_str(...)`, etc.' @@ -42,13 +43,18 @@ def _from_gemmi_block( cls, block: gemmi.cif.Block, ) -> Structure: - """Build a structure from a single *gemmi* CIF block. + """ + Build a structure from a single *gemmi* CIF block. - Args: - block (gemmi.cif.Block): Parsed CIF data block. + Parameters + ---------- + block : gemmi.cif.Block + Parsed CIF data block. - Returns: - Structure: A fully populated structure instance. + Returns + ------- + Structure + A fully populated structure instance. """ name = name_from_block(block) structure = Structure(name=name) @@ -67,13 +73,18 @@ def from_scratch( *, name: str, ) -> Structure: - """Create a minimal default structure. + """ + Create a minimal default structure. - Args: - name (str): Identifier for the new structure. + Parameters + ---------- + name : str + Identifier for the new structure. - Returns: - Structure: An empty structure with default categories. + Returns + ------- + Structure + An empty structure with default categories. """ return Structure(name=name) @@ -84,13 +95,18 @@ def from_cif_str( cls, cif_str: str, ) -> Structure: - """Create a structure by parsing a CIF string. + """ + Create a structure by parsing a CIF string. - Args: - cif_str (str): Raw CIF content. + Parameters + ---------- + cif_str : str + Raw CIF content. - Returns: - Structure: A populated structure instance. + Returns + ------- + Structure + A populated structure instance. """ doc = document_from_string(cif_str) block = pick_sole_block(doc) @@ -103,13 +119,18 @@ def from_cif_path( cls, cif_path: str, ) -> Structure: - """Create a structure by reading and parsing a CIF file. + """ + Create a structure by reading and parsing a CIF file. - Args: - cif_path (str): Filesystem path to a CIF file. + Parameters + ---------- + cif_path : str + Filesystem path to a CIF file. - Returns: - Structure: A populated structure instance. + Returns + ------- + Structure + A populated structure instance. """ doc = document_from_path(cif_path) block = pick_sole_block(doc) diff --git a/src/easydiffraction/display/__init__.py b/src/easydiffraction/display/__init__.py index 265560b2..25bccda4 100644 --- a/src/easydiffraction/display/__init__.py +++ b/src/easydiffraction/display/__init__.py @@ -1,14 +1,15 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Display subsystem for tables and plots. +""" +Display subsystem for tables and plots. -This package contains user-facing facades and backend implementations -to render tabular data and plots in different environments. +This package contains user-facing facades and backend implementations to +render tabular data and plots in different environments. - Tables: see :mod:`easydiffraction.display.tables` and the engines in - :mod:`easydiffraction.display.tablers`. -- Plots: see :mod:`easydiffraction.display.plotting` and the engines in - :mod:`easydiffraction.display.plotters`. +:mod:`easydiffraction.display.tablers`. - Plots: see +:mod:`easydiffraction.display.plotting` and the engines in +:mod:`easydiffraction.display.plotters`. """ # TODO: The following works in Jupyter, but breaks MkDocs builds. diff --git a/src/easydiffraction/display/base.py b/src/easydiffraction/display/base.py index d97f7be9..6ffc0698 100644 --- a/src/easydiffraction/display/base.py +++ b/src/easydiffraction/display/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Common base classes for display components and their factories.""" @@ -6,7 +6,6 @@ from abc import ABC from abc import abstractmethod -from typing import Any from typing import List from typing import Tuple @@ -18,14 +17,15 @@ class RendererBase(SingletonBase, ABC): - """Base class for display components with pluggable engines. + """ + Base class for display components with pluggable engines. Subclasses provide a factory and a default engine. This class manages the active backend instance and exposes helpers to inspect supported engines in a table-friendly format. """ - def __init__(self): + def __init__(self) -> None: self._engine = self._default_engine() self._backend = self._factory().create(self._engine) @@ -43,10 +43,27 @@ def _default_engine(cls) -> str: @property def engine(self) -> str: + """ + Return the name of the currently active rendering engine. + + Returns + ------- + str + Identifier of the active engine. + """ return self._engine @engine.setter def engine(self, new_engine: str) -> None: + """ + Switch to a different rendering engine. + + Parameters + ---------- + new_engine : str + Identifier of the engine to activate. Must be a key + returned by ``_factory()._registry()``. + """ if new_engine == self._engine: log.info(f"Engine is already set to '{new_engine}'. No change made.") return @@ -90,18 +107,25 @@ class RendererFactoryBase(ABC): """Base factory that manages discovery and creation of backends.""" @classmethod - def create(cls, engine_name: str) -> Any: - """Create a backend instance for the given engine. + def create(cls, engine_name: str) -> object: + """ + Create a backend instance for the given engine. - Args: - engine_name: Identifier of the engine to instantiate as - listed in ``_registry()``. + Parameters + ---------- + engine_name : str + Identifier of the engine to instantiate as listed in + ``_registry()``. - Returns: + Returns + ------- + object A new backend instance corresponding to ``engine_name``. - Raises: - ValueError: If the engine name is not supported. + Raises + ------ + ValueError + If the engine name is not supported. """ registry = cls._registry() if engine_name not in registry: @@ -117,16 +141,15 @@ def supported_engines(cls) -> List[str]: @classmethod def descriptions(cls) -> List[Tuple[str, str]]: - """Return pairs of engine name and human-friendly - description. - """ + """Return (name, description) pairs for each engine.""" items = cls._registry().items() return [(name, config.get('description')) for name, config in items] @classmethod @abstractmethod def _registry(cls) -> dict: - """Return engine registry. Implementations must provide this. + """ + Return engine registry. Implementations must provide this. The returned mapping should have keys as engine names and values as a config dict with 'description' and 'class'. Lazy imports diff --git a/src/easydiffraction/display/plotters/__init__.py b/src/easydiffraction/display/plotters/__init__.py index 14dae26a..09931ae8 100644 --- a/src/easydiffraction/display/plotters/__init__.py +++ b/src/easydiffraction/display/plotters/__init__.py @@ -1,10 +1,11 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Plotting backends. +""" +Plotting backends. This subpackage implements plotting engines used by the high-level plotting facade: -- :mod:`.ascii` for terminal-friendly ASCII plots. -- :mod:`.plotly` for interactive plots in notebooks or browsers. +- :mod:`.ascii` for terminal-friendly ASCII plots. - :mod:`.plotly` for +interactive plots in notebooks or browsers. """ diff --git a/src/easydiffraction/display/plotters/ascii.py b/src/easydiffraction/display/plotters/ascii.py index 7b5ff6a8..8735a8a0 100644 --- a/src/easydiffraction/display/plotters/ascii.py +++ b/src/easydiffraction/display/plotters/ascii.py @@ -1,10 +1,11 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""ASCII plotting backend. +""" +ASCII plotting backend. -Renders compact line charts in the terminal using -``asciichartpy``. This backend is well suited for quick feedback in -CLI environments and keeps a consistent API with other plotters. +Renders compact line charts in the terminal using ``asciichartpy``. This +backend is well suited for quick feedback in CLI environments and keeps +a consistent API with other plotters. """ import asciichartpy @@ -25,16 +26,21 @@ class AsciiPlotter(PlotterBase): """Terminal-based plotter using ASCII art.""" - def _get_legend_item(self, label): - """Return a colored legend entry for a given series label. + def _get_legend_item(self, label: str) -> str: + """ + Return a colored legend entry for a given series label. - The legend uses a colored line matching the series color and - the human-readable name from :data:`SERIES_CONFIG`. + The legend uses a colored line matching the series color and the + human-readable name from :data:`SERIES_CONFIG`. - Args: - label: Series identifier (e.g., ``'meas'``). + Parameters + ---------- + label : str + Series identifier (e.g., ``'meas'``). - Returns: + Returns + ------- + str A formatted legend string with color escapes. """ color_start = DEFAULT_COLORS[label] @@ -46,27 +52,34 @@ def _get_legend_item(self, label): def plot_powder( self, - x, - y_series, - labels, - axes_labels, - title, - height=None, - ): - """Render a line plot for powder diffraction data. + x: object, + y_series: object, + labels: object, + axes_labels: object, + title: str, + height: int | None = None, + ) -> None: + """ + Render a line plot for powder diffraction data. Suitable for powder diffraction data where intensity is plotted against an x-axis variable (2θ, TOF, d-spacing). Uses ASCII characters for terminal display. - Args: - x: 1D array-like of x values (only used for range - display). - y_series: Sequence of y arrays to plot. - labels: Series identifiers corresponding to y_series. - axes_labels: Ignored; kept for API compatibility. - title: Figure title printed above the chart. - height: Number of text rows to allocate for the chart. + Parameters + ---------- + x : object + 1D array-like of x values (only used for range display). + y_series : object + Sequence of y arrays to plot. + labels : object + Series identifiers corresponding to y_series. + axes_labels : object + Ignored; kept for API compatibility. + title : str + Figure title printed above the chart. + height : int | None, default=None + Number of text rows to allocate for the chart. """ # Intentionally unused; kept for a consistent display API del axes_labels @@ -92,26 +105,34 @@ def plot_powder( def plot_single_crystal( self, - x_calc, - y_meas, - y_meas_su, - axes_labels, - title, - height=None, - ): - """Render a scatter plot for single crystal diffraction data. + x_calc: object, + y_meas: object, + y_meas_su: object, + axes_labels: object, + title: str, + height: int | None = None, + ) -> None: + """ + Render a scatter plot for single crystal diffraction data. Creates an ASCII scatter plot showing measured vs calculated values with a diagonal reference line. - Args: - x_calc: 1D array-like of calculated values (x-axis). - y_meas: 1D array-like of measured values (y-axis). - y_meas_su: 1D array-like of measurement uncertainties - (ignored in ASCII mode). - axes_labels: Pair of strings for the x and y titles. - title: Figure title. - height: Number of text rows for the chart (default: 15). + Parameters + ---------- + x_calc : object + 1D array-like of calculated values (x-axis). + y_meas : object + 1D array-like of measured values (y-axis). + y_meas_su : object + 1D array-like of measurement uncertainties (ignored in ASCII + mode). + axes_labels : object + Pair of strings for the x and y titles. + title : str + Figure title. + height : int | None, default=None + Number of text rows for the chart (default: 15). """ # Intentionally unused; ASCII scatter doesn't show error bars del y_meas_su diff --git a/src/easydiffraction/display/plotters/base.py b/src/easydiffraction/display/plotters/base.py index 7220dfeb..d7ca594c 100644 --- a/src/easydiffraction/display/plotters/base.py +++ b/src/easydiffraction/display/plotters/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Abstract base and shared constants for plotting backends.""" @@ -18,10 +18,11 @@ class XAxisType(str, Enum): - """X-axis types for diffraction plots. + """ + X-axis types for diffraction plots. - Values match attribute names in data models for direct use - with ``getattr(pattern, x_axis)``. + Values match attribute names in data models for direct use with + ``getattr(pattern, x_axis)``. """ TWO_THETA = 'two_theta' @@ -151,64 +152,80 @@ class XAxisType(str, Enum): class PlotterBase(ABC): - """Abstract base for plotting backends. + """ + Abstract base for plotting backends. Implementations accept x values, multiple y-series, optional labels and render a plot to the chosen medium. - Two main plot types are supported: - - ``plot_powder``: Line plots for powder diffraction patterns - (intensity vs. 2θ/TOF/d-spacing). - - ``plot_single_crystal``: Scatter plots comparing measured vs. - calculated values (e.g., F²meas vs F²calc for single crystal). + Two main plot types are supported: - ``plot_powder``: Line plots for + powder diffraction patterns (intensity vs. 2θ/TOF/d-spacing). - + ``plot_single_crystal``: Scatter plots comparing measured vs. + calculated values (e.g., F²meas vs F²calc for single crystal). """ @abstractmethod def plot_powder( self, - x, - y_series, - labels, - axes_labels, - title, - height, - ): - """Render a line plot for powder diffraction data. + x: object, + y_series: object, + labels: object, + axes_labels: object, + title: str, + height: int | None, + ) -> None: + """ + Render a line plot for powder diffraction data. Suitable for powder diffraction data where intensity is plotted against an x-axis variable (2θ, TOF, d-spacing). - Args: - x: 1D array of x-axis values. - y_series: Sequence of y arrays to plot. - labels: Identifiers corresponding to y_series. - axes_labels: Pair of strings for the x and y titles. - title: Figure title. - height: Backend-specific height (text rows or pixels). + Parameters + ---------- + x : object + 1D array of x-axis values. + y_series : object + Sequence of y arrays to plot. + labels : object + Identifiers corresponding to y_series. + axes_labels : object + Pair of strings for the x and y titles. + title : str + Figure title. + height : int | None + Backend-specific height (text rows or pixels). """ pass @abstractmethod def plot_single_crystal( self, - x_calc, - y_meas, - y_meas_su, - axes_labels, - title, - height, - ): - """Render a scatter plot for single crystal diffraction data. + x_calc: object, + y_meas: object, + y_meas_su: object, + axes_labels: object, + title: str, + height: int | None, + ) -> None: + """ + Render a scatter plot for single crystal diffraction data. Suitable for single crystal diffraction data where measured values are plotted against calculated values with error bars. - Args: - x_calc: 1D array of calculated values (x-axis). - y_meas: 1D array of measured values (y-axis). - y_meas_su: 1D array of measurement uncertainties. - axes_labels: Pair of strings for the x and y titles. - title: Figure title. - height: Backend-specific height (text rows or pixels). + Parameters + ---------- + x_calc : object + 1D array of calculated values (x-axis). + y_meas : object + 1D array of measured values (y-axis). + y_meas_su : object + 1D array of measurement uncertainties. + axes_labels : object + Pair of strings for the x and y titles. + title : str + Figure title. + height : int | None + Backend-specific height (text rows or pixels). """ pass diff --git a/src/easydiffraction/display/plotters/plotly.py b/src/easydiffraction/display/plotters/plotly.py index bf2b4907..4df79cf0 100644 --- a/src/easydiffraction/display/plotters/plotly.py +++ b/src/easydiffraction/display/plotters/plotly.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Plotly plotting backend. +""" +Plotly plotting backend. Provides an interactive plotting implementation using Plotly. In notebooks, figures are displayed inline; in other environments a browser @@ -38,19 +39,25 @@ class PlotlyPlotter(PlotterBase): def _get_powder_trace( self, - x, - y, - label, - ): - """Create a Plotly trace for powder diffraction data. - - Args: - x: 1D array-like of x-axis values. - y: 1D array-like of y-axis values. - label: Series identifier (``'meas'``, ``'calc'``, or - ``'resid'``). - - Returns: + x: object, + y: object, + label: str, + ) -> object: + """ + Create a Plotly trace for powder diffraction data. + + Parameters + ---------- + x : object + 1D array-like of x-axis values. + y : object + 1D array- like of y-axis values. + label : str + Series identifier (``'meas'``, ``'calc'``, or ``'resid'``). + + Returns + ------- + object A configured :class:`plotly.graph_objects.Scatter` trace. """ mode = SERIES_CONFIG[label]['mode'] @@ -70,18 +77,25 @@ def _get_powder_trace( def _get_single_crystal_trace( self, - x_calc, - y_meas, - y_meas_su, - ): - """Create a Plotly trace for single crystal diffraction data. - - Args: - x_calc: 1D array-like of calculated values (x-axis). - y_meas: 1D array-like of measured values (y-axis). - y_meas_su: 1D array-like of measurement uncertainties. - - Returns: + x_calc: object, + y_meas: object, + y_meas_su: object, + ) -> object: + """ + Create a Plotly trace for single crystal diffraction data. + + Parameters + ---------- + x_calc : object + 1D array-like of calculated values (x-axis). + y_meas : object + 1D array-like of measured values (y-axis). + y_meas_su : object + 1D array-like of measurement uncertainties. + + Returns + ------- + object A configured :class:`plotly.graph_objects.Scatter` trace with markers and error bars. """ @@ -105,13 +119,16 @@ def _get_single_crystal_trace( return trace - def _get_diagonal_shape(self): - """Create a diagonal reference line shape. + def _get_diagonal_shape(self) -> dict: + """ + Create a diagonal reference line shape. Returns a y=x diagonal line spanning the plot area using paper coordinates (0,0) to (1,1). - Returns: + Returns + ------- + dict A dict configuring a diagonal line shape. """ return dict( @@ -126,10 +143,13 @@ def _get_diagonal_shape(self): line=dict(width=0.5), ) - def _get_config(self): - """Return the Plotly figure configuration. + def _get_config(self) -> dict: + """ + Return the Plotly figure configuration. - Returns: + Returns + ------- + dict A dict with display and mode bar settings. """ return dict( @@ -145,16 +165,22 @@ def _get_config(self): def _get_figure( self, - data, - layout, - ): - """Create and configure a Plotly figure. - - Args: - data: List of traces to include in the figure. - layout: Layout configuration dict. - - Returns: + data: object, + layout: object, + ) -> object: + """ + Create and configure a Plotly figure. + + Parameters + ---------- + data : object + List of traces to include in the figure. + layout : object + Layout configuration dict. + + Returns + ------- + object A configured :class:`plotly.graph_objects.Figure`. """ fig = go.Figure(data=data, layout=layout) @@ -166,15 +192,18 @@ def _get_figure( def _show_figure( self, - fig, - ): - """Display a Plotly figure. + fig: object, + ) -> None: + """ + Display a Plotly figure. Renders the figure using the appropriate method for the current environment (browser for PyCharm, inline HTML for Jupyter). - Args: - fig: A :class:`plotly.graph_objects.Figure` to display. + Parameters + ---------- + fig : object + A :class:`plotly.graph_objects.Figure` to display. """ config = self._get_config() @@ -191,18 +220,25 @@ def _show_figure( def _get_layout( self, - title, - axes_labels, - **kwargs, - ): - """Create a Plotly layout configuration. - - Args: - title: Figure title. - axes_labels: Pair of strings for the x and y titles. - **kwargs: Additional layout parameters (e.g., shapes). - - Returns: + title: str, + axes_labels: object, + **kwargs: object, + ) -> object: + """ + Create a Plotly layout configuration. + + Parameters + ---------- + title : str + Figure title. + axes_labels : object + Pair of strings for the x and y titles. + **kwargs : object + Additional layout parameters (e.g., shapes). + + Returns + ------- + object A configured :class:`plotly.graph_objects.Layout`. """ return go.Layout( @@ -238,25 +274,33 @@ def _get_layout( def plot_powder( self, - x, - y_series, - labels, - axes_labels, - title, - height=None, - ): - """Render a line plot for powder diffraction data. + x: object, + y_series: object, + labels: object, + axes_labels: object, + title: str, + height: int | None = None, + ) -> None: + """ + Render a line plot for powder diffraction data. Suitable for powder diffraction data where intensity is plotted against an x-axis variable (2θ, TOF, d-spacing). - Args: - x: 1D array-like of x-axis values. - y_series: Sequence of y arrays to plot. - labels: Series identifiers corresponding to y_series. - axes_labels: Pair of strings for the x and y titles. - title: Figure title. - height: Ignored; Plotly auto-sizes based on renderer. + Parameters + ---------- + x : object + 1D array-like of x-axis values. + y_series : object + Sequence of y arrays to plot. + labels : object + Series identifiers corresponding to y_series. + axes_labels : object + Pair of strings for the x and y titles. + title : str + Figure title. + height : int | None, default=None + Ignored; Plotly auto-sizes based on renderer. """ # Intentionally unused; accepted for API compatibility del height @@ -277,26 +321,34 @@ def plot_powder( def plot_single_crystal( self, - x_calc, - y_meas, - y_meas_su, - axes_labels, - title, - height=None, - ): - """Render a scatter plot for single crystal diffraction data. + x_calc: object, + y_meas: object, + y_meas_su: object, + axes_labels: object, + title: str, + height: int | None = None, + ) -> None: + """ + Render a scatter plot for single crystal diffraction data. Suitable for single crystal diffraction data where measured - values are plotted against calculated values with error bars - and a diagonal reference line. - - Args: - x_calc: 1D array-like of calculated values (x-axis). - y_meas: 1D array-like of measured values (y-axis). - y_meas_su: 1D array-like of measurement uncertainties. - axes_labels: Pair of strings for the x and y titles. - title: Figure title. - height: Ignored; Plotly auto-sizes based on renderer. + values are plotted against calculated values with error bars and + a diagonal reference line. + + Parameters + ---------- + x_calc : object + 1D array-like of calculated values (x-axis). + y_meas : object + 1D array-like of measured values (y-axis). + y_meas_su : object + 1D array-like of measurement uncertainties. + axes_labels : object + Pair of strings for the x and y titles. + title : str + Figure title. + height : int | None, default=None + Ignored; Plotly auto-sizes based on renderer. """ # Intentionally unused; accepted for API compatibility del height diff --git a/src/easydiffraction/display/plotting.py b/src/easydiffraction/display/plotting.py index 90d54261..ec8e1d5c 100644 --- a/src/easydiffraction/display/plotting.py +++ b/src/easydiffraction/display/plotting.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Plotting facade for measured and calculated patterns. +""" +Plotting facade for measured and calculated patterns. Uses the common :class:`RendererBase` so plotters and tablers share a consistent configuration surface and engine handling. @@ -28,6 +29,8 @@ class PlotterEngineEnum(str, Enum): + """Available plotting engine backends.""" + ASCII = 'asciichartpy' PLOTLY = 'plotly' @@ -56,7 +59,7 @@ class Plotter(RendererBase): # Private special methods # ------------------------------------------------------------------ - def __init__(self): + def __init__(self) -> None: super().__init__() # X-axis limits self._x_min = DEFAULT_MIN @@ -80,17 +83,30 @@ def _default_engine(cls) -> str: # Private helper methods # ------------------------------------------------------------------ - def _auto_x_range_for_ascii(self, pattern, x_array, x_min, x_max): - """For the ASCII engine, narrow the range around the tallest - peak. - - Args: - pattern: Data pattern object (needs ``intensity_meas``). - x_array: Full x-axis array. - x_min: Current minimum (may be ``None``). - x_max: Current maximum (may be ``None``). - - Returns: + def _auto_x_range_for_ascii( + self, + pattern: object, + x_array: object, + x_min: object, + x_max: object, + ) -> tuple: + """ + For the ASCII engine, narrow the range around the tallest peak. + + Parameters + ---------- + pattern : object + Data pattern object (needs ``intensity_meas``). + x_array : object + Full x-axis array. + x_min : object + Current minimum (may be ``None``). + x_max : object + Current maximum (may be ``None``). + + Returns + ------- + tuple Tuple of ``(x_min, x_max)``, possibly narrowed. """ if self._engine == 'asciichartpy' and (x_min is None or x_max is None): @@ -104,21 +120,28 @@ def _auto_x_range_for_ascii(self, pattern, x_array, x_min, x_max): def _filtered_y_array( self, - y_array, - x_array, - x_min, - x_max, - ): - """Filter an array by the inclusive x-range limits. - - Args: - y_array: 1D array-like of y values. - x_array: 1D array-like of x values (same length as - ``y_array``). - x_min: Minimum x limit (or ``None`` to use default). - x_max: Maximum x limit (or ``None`` to use default). - - Returns: + y_array: object, + x_array: object, + x_min: object, + x_max: object, + ) -> object: + """ + Filter an array by the inclusive x-range limits. + + Parameters + ---------- + y_array : object + 1D array-like of y values. + x_array : object + 1D array-like of x values (same length as ``y_array``). + x_min : object + Minimum x limit (or ``None`` to use default). + x_max : object + Maximum x limit (or ``None`` to use default). + + Returns + ------- + object Filtered ``y_array`` values where ``x_array`` lies within ``[x_min, x_max]``. """ @@ -132,39 +155,55 @@ def _filtered_y_array( return filtered_y_array - def _get_axes_labels(self, sample_form, scattering_type, x_axis): - """Look up axis labels for the given experiment / x-axis - combination. - """ + def _get_axes_labels( + self, + sample_form: object, + scattering_type: object, + x_axis: object, + ) -> list: + """Look up axis labels for the experiment / x-axis.""" return DEFAULT_AXES_LABELS[(sample_form, scattering_type, x_axis)] def _prepare_powder_data( self, - pattern, - expt_name, - expt_type, - x_min, - x_max, - x, - need_meas=False, - need_calc=False, - show_residual=False, - ): - """Validate, resolve axes, auto-range, and filter arrays. - - Args: - pattern: Data pattern object with intensity arrays. - expt_name: Experiment name for error messages. - expt_type: Experiment type with sample_form, scattering, - and beam enums. - x_min: Optional minimum x-axis limit. - x_max: Optional maximum x-axis limit. - x: Explicit x-axis type or ``None``. - need_meas: Whether ``intensity_meas`` is required. - need_calc: Whether ``intensity_calc`` is required. - show_residual: If ``True``, compute meas − calc residual. - - Returns: + pattern: object, + expt_name: str, + expt_type: object, + x_min: object, + x_max: object, + x: object, + need_meas: bool = False, + need_calc: bool = False, + show_residual: bool = False, + ) -> dict | None: + """ + Validate, resolve axes, auto-range, and filter arrays. + + Parameters + ---------- + pattern : object + Data pattern object with intensity arrays. + expt_name : str + Experiment name for error messages. + expt_type : object + Experiment type with sample_form, scattering, and beam + enums. + x_min : object + Optional minimum x-axis limit. + x_max : object + Optional maximum x-axis limit. + x : object + Explicit x-axis type or ``None``. + need_meas : bool, default=False + Whether ``intensity_meas`` is required. + need_calc : bool, default=False + Whether ``intensity_calc`` is required. + show_residual : bool, default=False + If ``True``, compute meas − calc residual. + + Returns + ------- + dict | None A dict with keys ``x_filtered``, ``y_series``, ``y_labels``, ``axes_labels``, and ``x_axis``; or ``None`` when a required array is missing. @@ -222,15 +261,21 @@ def _prepare_powder_data( 'x_axis': x_axis, } - def _resolve_x_axis(self, expt_type, x): - """Determine the x-axis type from experiment metadata. - - Args: - expt_type: Experiment type with sample_form, - scattering_type, and beam_mode enums. - x: Explicit x-axis type or ``None`` to auto-detect. - - Returns: + def _resolve_x_axis(self, expt_type: object, x: object) -> tuple: + """ + Determine the x-axis type from experiment metadata. + + Parameters + ---------- + expt_type : object + Experiment type with sample_form, scattering_type, and + beam_mode enums. + x : object + Explicit x-axis type or ``None`` to auto-detect. + + Returns + ------- + tuple Tuple of ``(x_axis, x_name, sample_form, scattering_type, beam_mode)``. """ @@ -246,16 +291,19 @@ def _resolve_x_axis(self, expt_type, x): # ------------------------------------------------------------------ @property - def x_min(self): + def x_min(self) -> float: """Minimum x-axis limit.""" return self._x_min @x_min.setter - def x_min(self, value): - """Set the minimum x-axis limit. + def x_min(self, value: object) -> None: + """ + Set the minimum x-axis limit. - Args: - value: Minimum limit or ``None`` to reset to default. + Parameters + ---------- + value : object + Minimum limit or ``None`` to reset to default. """ if value is not None: self._x_min = value @@ -263,16 +311,19 @@ def x_min(self, value): self._x_min = DEFAULT_MIN @property - def x_max(self): + def x_max(self) -> float: """Maximum x-axis limit.""" return self._x_max @x_max.setter - def x_max(self, value): - """Set the maximum x-axis limit. + def x_max(self, value: object) -> None: + """ + Set the maximum x-axis limit. - Args: - value: Maximum limit or ``None`` to reset to default. + Parameters + ---------- + value : object + Maximum limit or ``None`` to reset to default. """ if value is not None: self._x_max = value @@ -280,16 +331,19 @@ def x_max(self, value): self._x_max = DEFAULT_MAX @property - def height(self): + def height(self) -> int: """Plot height (rows for ASCII, pixels for Plotly).""" return self._height @height.setter - def height(self, value): - """Set plot height. + def height(self, value: object) -> None: + """ + Set plot height. - Args: - value: Height value or ``None`` to reset to default. + Parameters + ---------- + value : object + Height value or ``None`` to reset to default. """ if value is not None: self._height = value @@ -300,7 +354,7 @@ def height(self, value): # Public methods # ------------------------------------------------------------------ - def show_config(self): + def show_config(self) -> None: """Display the current plotting configuration.""" headers = [ ('Parameter', 'left'), @@ -317,25 +371,32 @@ def show_config(self): def plot_meas( self, - pattern, - expt_name, - expt_type, - x_min=None, - x_max=None, - x=None, - ): - """Plot measured pattern using the current engine. - - Args: - pattern: Object with x-axis arrays (``two_theta``, - ``time_of_flight``, ``d_spacing``) and ``meas`` array. - expt_name: Experiment name for the title. - expt_type: Experiment type with scattering/beam enums. - x_min: Optional minimum x-axis limit. - x_max: Optional maximum x-axis limit. - x: X-axis type (``'two_theta'``, ``'time_of_flight'``, or - ``'d_spacing'``). If ``None``, auto-detected from - beam mode. + pattern: object, + expt_name: str, + expt_type: object, + x_min: object = None, + x_max: object = None, + x: object = None, + ) -> None: + """ + Plot measured pattern using the current engine. + + Parameters + ---------- + pattern : object + Object with x-axis arrays (``two_theta``, + ``time_of_flight``, ``d_spacing``) and ``meas`` array. + expt_name : str + Experiment name for the title. + expt_type : object + Experiment type with scattering/beam enums. + x_min : object, default=None + Optional minimum x-axis limit. + x_max : object, default=None + Optional maximum x-axis limit. + x : object, default=None + X-axis type (``'two_theta'``, ``'time_of_flight'``, or + ``'d_spacing'``). If ``None``, auto-detected from beam mode. """ ctx = self._prepare_powder_data( pattern, @@ -360,25 +421,32 @@ def plot_meas( def plot_calc( self, - pattern, - expt_name, - expt_type, - x_min=None, - x_max=None, - x=None, - ): - """Plot calculated pattern using the current engine. - - Args: - pattern: Object with x-axis arrays (``two_theta``, - ``time_of_flight``, ``d_spacing``) and ``calc`` array. - expt_name: Experiment name for the title. - expt_type: Experiment type with scattering/beam enums. - x_min: Optional minimum x-axis limit. - x_max: Optional maximum x-axis limit. - x: X-axis type (``'two_theta'``, ``'time_of_flight'``, or - ``'d_spacing'``). If ``None``, auto-detected from - beam mode. + pattern: object, + expt_name: str, + expt_type: object, + x_min: object = None, + x_max: object = None, + x: object = None, + ) -> None: + """ + Plot calculated pattern using the current engine. + + Parameters + ---------- + pattern : object + Object with x-axis arrays (``two_theta``, + ``time_of_flight``, ``d_spacing``) and ``calc`` array. + expt_name : str + Experiment name for the title. + expt_type : object + Experiment type with scattering/beam enums. + x_min : object, default=None + Optional minimum x-axis limit. + x_max : object, default=None + Optional maximum x-axis limit. + x : object, default=None + X-axis type (``'two_theta'``, ``'time_of_flight'``, or + ``'d_spacing'``). If ``None``, auto-detected from beam mode. """ ctx = self._prepare_powder_data( pattern, @@ -403,37 +471,44 @@ def plot_calc( def plot_meas_vs_calc( self, - pattern, - expt_name, - expt_type, - x_min=None, - x_max=None, - show_residual=False, - x=None, - ): - """Plot measured and calculated series and optional residual. + pattern: object, + expt_name: str, + expt_type: object, + x_min: object = None, + x_max: object = None, + show_residual: bool = False, + x: object = None, + ) -> None: + """ + Plot measured and calculated series and optional residual. Supports both powder and single crystal data with a unified API. - For powder diffraction: - - x='two_theta', 'time_of_flight', or 'd_spacing' - - Auto-detected from beam mode if not specified - - For single crystal diffraction: - - x='intensity_calc' (default): scatter plot - - x='d_spacing' or 'sin_theta_over_lambda': line plot - - Args: - pattern: Data pattern object with meas/calc arrays. - expt_name: Experiment name for the title. - expt_type: Experiment type with sample_form, - scattering, and beam enums. - x_min: Optional minimum x-axis limit. - x_max: Optional maximum x-axis limit. - show_residual: If ``True``, add residual series - (powder only). - x: X-axis type. If ``None``, auto-detected from sample form - and beam mode. + For powder diffraction: - x='two_theta', 'time_of_flight', or + 'd_spacing' - Auto-detected from beam mode if not specified + + For single crystal diffraction: - x='intensity_calc' (default): + scatter plot - x='d_spacing' or 'sin_theta_over_lambda': line + plot + + Parameters + ---------- + pattern : object + Data pattern object with meas/calc arrays. + expt_name : str + Experiment name for the title. + expt_type : object + Experiment type with sample_form, scattering, and beam + enums. + x_min : object, default=None + Optional minimum x-axis limit. + x_max : object, default=None + Optional maximum x-axis limit. + show_residual : bool, default=False + If ``True``, add residual series (powder only). + x : object, default=None + X-axis type. If ``None``, auto-detected from sample form and + beam mode. """ x_axis, _, sample_form, scattering_type, _ = self._resolve_x_axis(expt_type, x) diff --git a/src/easydiffraction/display/tablers/__init__.py b/src/easydiffraction/display/tablers/__init__.py index 93d84417..6471fbff 100644 --- a/src/easydiffraction/display/tablers/__init__.py +++ b/src/easydiffraction/display/tablers/__init__.py @@ -1,10 +1,11 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Tabular rendering backends. +""" +Tabular rendering backends. -This subpackage provides concrete implementations for rendering -tables in different environments: +This subpackage provides concrete implementations for rendering tables +in different environments: -- :mod:`.rich` for terminal and notebooks using the Rich library. -- :mod:`.pandas` for notebooks using DataFrame Styler. +- :mod:`.rich` for terminal and notebooks using the Rich library. - +:mod:`.pandas` for notebooks using DataFrame Styler. """ diff --git a/src/easydiffraction/display/tablers/base.py b/src/easydiffraction/display/tablers/base.py index 2b710d8f..869c5a17 100644 --- a/src/easydiffraction/display/tablers/base.py +++ b/src/easydiffraction/display/tablers/base.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Low-level backends for rendering tables. +""" +Low-level backends for rendering tables. This module defines the abstract base for tabular renderers and small helpers for consistent styling across terminal and notebook outputs. @@ -10,7 +11,6 @@ from abc import ABC from abc import abstractmethod -from typing import Any from IPython import get_ipython from rich.color import Color @@ -19,11 +19,11 @@ class TableBackendBase(ABC): - """Abstract base class for concrete table backends. + """ + Abstract base class for concrete table backends. - Subclasses implement the ``render`` method which receives an - index-aware pandas DataFrame and the alignment for each column - header. + Subclasses implement the ``render`` method which receives an index- + aware pandas DataFrame and the alignment for each column header. """ FLOAT_PRECISION = 5 @@ -34,20 +34,26 @@ def __init__(self) -> None: super().__init__() self._float_fmt = f'{{:.{self.FLOAT_PRECISION}f}}'.format - def _format_value(self, value: Any) -> Any: - """Format floats with fixed precision and others as strings. + def _format_value(self, value: object) -> object: + """ + Format floats with fixed precision and others as strings. - Args: - value: Cell value to format. + Parameters + ---------- + value : object + Cell value to format. - Returns: + Returns + ------- + object A string representation with fixed precision for floats or ``str(value)`` for other types. """ return self._float_fmt(value) if isinstance(value, float) else str(value) def _is_dark_theme(self) -> bool: - """Return True when a dark theme is detected in Jupyter. + """ + Return True when a dark theme is detected in Jupyter. If not running inside Jupyter, return a sane default (True). """ @@ -62,14 +68,18 @@ def _is_dark_theme(self) -> bool: return is_dark() - def _rich_to_hex(self, color): - """Convert a Rich color name to a CSS-style hex string. + def _rich_to_hex(self, color: str) -> str: + """ + Convert a Rich color name to a CSS-style hex string. - Args: - color: Rich color name or specification parsable by - :mod:`rich`. + Parameters + ---------- + color : str + Rich color name or specification parsable by :mod:`rich`. - Returns: + Returns + ------- + str Hex color string in the form ``#RRGGBB``. """ c = Color.parse(color) @@ -90,21 +100,27 @@ def _pandas_border_color(self) -> str: @abstractmethod def render( self, - alignments, - df, - display_handle: Any | None = None, - ) -> Any: - """Render the provided DataFrame with backend-specific styling. - - Args: - alignments: Iterable of column justifications (e.g., - ``'left'`` or ``'center'``) corresponding to the data - columns. - df: Index-aware DataFrame with data to render. - display_handle: Optional environment-specific handle to - enable in-place updates. - - Returns: + alignments: object, + df: object, + display_handle: object | None = None, + ) -> object: + """ + Render the provided DataFrame with backend-specific styling. + + Parameters + ---------- + alignments : object + Iterable of column justifications (e.g., ``'left'`` or + ``'center'``) corresponding to the data columns. + df : object + Index-aware DataFrame with data to render. + display_handle : object | None, default=None + Optional environment-specific handle to enable in-place + updates. + + Returns + ------- + object Backend-defined return value (commonly ``None``). """ pass diff --git a/src/easydiffraction/display/tablers/pandas.py b/src/easydiffraction/display/tablers/pandas.py index da05b5e8..4efef148 100644 --- a/src/easydiffraction/display/tablers/pandas.py +++ b/src/easydiffraction/display/tablers/pandas.py @@ -1,11 +1,9 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Pandas-based table renderer for notebooks using DataFrame Styler.""" from __future__ import annotations -from typing import Any - try: from IPython.display import HTML from IPython.display import display @@ -22,13 +20,18 @@ class PandasTableBackend(TableBackendBase): """Render tables using the pandas Styler in Jupyter environments.""" def _build_base_styles(self, color: str) -> list[dict]: - """Return base CSS table styles for a given border color. + """ + Return base CSS table styles for a given border color. - Args: - color: CSS color value (e.g., ``#RRGGBB``) to use for - borders and header accents. + Parameters + ---------- + color : str + CSS color value (e.g., ``#RRGGBB``) to use for borders and + header accents. - Returns: + Returns + ------- + list[dict] A list of ``Styler.set_table_styles`` dictionaries. """ return [ @@ -76,15 +79,21 @@ def _build_base_styles(self, color: str) -> list[dict]: }, ] - def _build_header_alignment_styles(self, df, alignments) -> list[dict]: - """Generate header cell alignment styles per column. - - Args: - df: DataFrame whose columns are being rendered. - alignments: Iterable of text alignment values (e.g., - ``'left'``, ``'center'``) matching ``df`` columns. - - Returns: + def _build_header_alignment_styles(self, df: object, alignments: object) -> list[dict]: + """ + Generate header cell alignment styles per column. + + Parameters + ---------- + df : object + DataFrame whose columns are being rendered. + alignments : object + Iterable of text alignment values (e.g., ``'left'``, + ``'center'``) matching ``df`` columns. + + Returns + ------- + list[dict] A list of CSS rules for header cell alignment. """ return [ @@ -95,15 +104,22 @@ def _build_header_alignment_styles(self, df, alignments) -> list[dict]: for column, align in zip(df.columns, alignments, strict=False) ] - def _apply_styling(self, df, alignments, color: str): - """Build a configured Styler with alignments and base styles. - - Args: - df: DataFrame to style. - alignments: Iterable of text alignment values for columns. - color: CSS color value used for borders/header. - - Returns: + def _apply_styling(self, df: object, alignments: object, color: str) -> object: + """ + Build a configured Styler with alignments and base styles. + + Parameters + ---------- + df : object + DataFrame to style. + alignments : object + Iterable of text alignment values for columns. + color : str + CSS color value used for borders/header. + + Returns + ------- + object A configured pandas Styler ready for display. """ table_styles = self._build_base_styles(color) @@ -120,17 +136,20 @@ def _apply_styling(self, df, alignments, color: str): ) return styler - def _update_display(self, styler, display_handle) -> None: - """Single, consistent update path for Jupyter. + def _update_display(self, styler: object, display_handle: object) -> None: + """ + Single, consistent update path for Jupyter. If a handle with ``update()`` is provided and it's a DisplayHandle, update the output area in-place using HTML. Otherwise, display once via IPython ``display()``. - Args: - styler: Configured DataFrame Styler to be rendered. - display_handle: Optional IPython DisplayHandle used for - in-place updates. + Parameters + ---------- + styler : object + Configured DataFrame Styler to be rendered. + display_handle : object + Optional IPython DisplayHandle used for in-place updates. """ # Handle with update() method if display_handle is not None and hasattr(display_handle, 'update'): @@ -152,17 +171,27 @@ def _update_display(self, styler, display_handle) -> None: def render( self, - alignments, - df, - display_handle: Any | None = None, - ) -> Any: - """Render a styled DataFrame. - - Args: - alignments: Iterable of column justifications (e.g. 'left'). - df: DataFrame whose index is displayed as the first column. - display_handle: Optional IPython DisplayHandle to update an - existing output area in place when running in Jupyter. + alignments: object, + df: object, + display_handle: object | None = None, + ) -> object: + """ + Render a styled DataFrame. + + Parameters + ---------- + alignments : object + Iterable of column justifications (e.g. 'left'). + df : object + DataFrame whose index is displayed as the first column. + display_handle : object | None, default=None + Optional IPython DisplayHandle to update an existing output + area in place when running in Jupyter. + + Returns + ------- + object + Backend-defined return value (commonly ``None``). """ color = self._pandas_border_color styler = self._apply_styling(df, alignments, color) diff --git a/src/easydiffraction/display/tablers/rich.py b/src/easydiffraction/display/tablers/rich.py index e7a8afbb..fba2a400 100644 --- a/src/easydiffraction/display/tablers/rich.py +++ b/src/easydiffraction/display/tablers/rich.py @@ -1,11 +1,10 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Rich-based table renderer for terminals and notebooks.""" from __future__ import annotations import io -from typing import Any from rich.box import Box from rich.console import Console @@ -41,16 +40,20 @@ class RichTableBackend(TableBackendBase): """Render tables to terminal or Jupyter using the Rich library.""" def _to_html(self, table: Table) -> str: - """Render a Rich table to HTML using an off-screen console. + """ + Render a Rich table to HTML using an off-screen console. - A fresh ``Console(record=True, file=StringIO())`` avoids - private attribute access and guarantees no visible output - in notebooks. + A fresh ``Console(record=True, file=StringIO())`` avoids private + attribute access and guarantees no visible output in notebooks. - Args: - table: Rich :class:`~rich.table.Table` to export. + Parameters + ---------- + table : Table + Rich :class:`~rich.table.Table` to export. - Returns: + Returns + ------- + str HTML string with inline styles for notebook display. """ tmp = Console(force_jupyter=False, record=True, file=io.StringIO()) @@ -63,15 +66,22 @@ def _to_html(self, table: Table) -> str: ) return html - def _build_table(self, df, alignments, color: str) -> Table: - """Construct a Rich Table with formatted data and alignment. - - Args: - df: DataFrame-like object providing rows to render. - alignments: Iterable of text alignment values for columns. - color: Rich color name used for borders/index style. - - Returns: + def _build_table(self, df: object, alignments: object, color: str) -> Table: + """ + Construct a Rich Table with formatted data and alignment. + + Parameters + ---------- + df : object + DataFrame-like object providing rows to render. + alignments : object + Iterable of text alignment values for columns. + color : str + Rich color name used for borders/index style. + + Returns + ------- + Table A :class:`~rich.table.Table` configured for display. """ table = Table( @@ -96,20 +106,23 @@ def _build_table(self, df, alignments, color: str) -> Table: return table - def _update_display(self, table: Table, display_handle) -> None: - """Single, consistent update path for Jupyter and terminal. - - - With a handle that has ``update()``: - * If it's an IPython DisplayHandle, export to HTML and - update. - * Otherwise, treat it as a terminal/live-like handle and - update with the Rich renderable. - - Without a handle, print once to the shared console. - - Args: - table: Rich :class:`~rich.table.Table` to display. - display_handle: Optional environment-specific handle for - in-place updates (IPython or terminal live). + def _update_display(self, table: Table, display_handle: object) -> None: + """ + Single, consistent update path for Jupyter and terminal. + + - With a handle that has ``update()``: * If it's an IPython + DisplayHandle, export to HTML and update. * Otherwise, treat it + as a terminal/live-like handle and update with the Rich + renderable. - Without a handle, print once to the shared + console. + + Parameters + ---------- + table : Table + Rich :class:`~rich.table.Table` to display. + display_handle : object + Optional environment-specific handle for in- place updates + (IPython or terminal live). """ # Handle with update() method if display_handle is not None and hasattr(display_handle, 'update'): @@ -136,17 +149,26 @@ def _update_display(self, table: Table, display_handle) -> None: def render( self, - alignments, - df, - display_handle=None, - ) -> Any: - """Render a styled table using Rich. - - Args: - alignments: Iterable of text-align values for columns. - df: Index-aware DataFrame to render. - display_handle: Optional environment handle for in-place - updates. + alignments: object, + df: object, + display_handle: object = None, + ) -> object: + """ + Render a styled table using Rich. + + Parameters + ---------- + alignments : object + Iterable of text-align values for columns. + df : object + Index-aware DataFrame to render. + display_handle : object, default=None + Optional environment handle for in-place updates. + + Returns + ------- + object + Backend-defined return value (commonly ``None``). """ color = self._rich_border_color table = self._build_table(df, alignments, color) diff --git a/src/easydiffraction/display/tables.py b/src/easydiffraction/display/tables.py index e149609d..1ae39594 100644 --- a/src/easydiffraction/display/tables.py +++ b/src/easydiffraction/display/tables.py @@ -1,11 +1,10 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Table rendering engines: console (Rich) and Jupyter (pandas).""" from __future__ import annotations from enum import Enum -from typing import Any import pandas as pd @@ -19,12 +18,15 @@ class TableEngineEnum(str, Enum): + """Available table rendering backends.""" + RICH = 'rich' PANDAS = 'pandas' @classmethod def default(cls) -> 'TableEngineEnum': - """Select default engine based on environment. + """ + Select default engine based on environment. Returns Pandas when running in Jupyter, otherwise Rich. """ @@ -35,6 +37,14 @@ def default(cls) -> 'TableEngineEnum': return cls.RICH def description(self) -> str: + """ + Return a human-readable description of this table engine. + + Returns + ------- + str + Description string for the current enum member. + """ if self is TableEngineEnum.RICH: return 'Console rendering with Rich' elif self is TableEngineEnum.PANDAS: @@ -65,17 +75,23 @@ def show_config(self) -> None: console.paragraph('Current tabler configuration') TableRenderer.get().render(df) - def render(self, df, display_handle: Any | None = None) -> Any: - """Render a DataFrame as a table using the active backend. - - Args: - df: DataFrame with a two-level column index where the - second level provides per-column alignment. - display_handle: Optional environment-specific handle used - to update an existing output area in-place (e.g., an - IPython DisplayHandle or a terminal live handle). - - Returns: + def render(self, df: object, display_handle: object | None = None) -> object: + """ + Render a DataFrame as a table using the active backend. + + Parameters + ---------- + df : object + DataFrame with a two-level column index where the second + level provides per-column alignment. + display_handle : object | None, default=None + Optional environment-specific handle used to update an + existing output area in-place (e.g., an IPython + DisplayHandle or a terminal live handle). + + Returns + ------- + object Backend-specific return value (usually ``None``). """ # Work on a copy to avoid mutating the original DataFrame @@ -98,11 +114,11 @@ class TableRendererFactory(RendererFactoryBase): @classmethod def _registry(cls) -> dict: - """Build registry, adapting available engines to the - environment. + """ + Build registry, adapting available engines to the environment. - - In Jupyter: expose both 'rich' and 'pandas'. - - In terminal: expose only 'rich' (pandas is notebook-only). + - In Jupyter: expose both 'rich' and 'pandas'. - In terminal: + expose only 'rich' (pandas is notebook-only). """ base = { TableEngineEnum.RICH.value: { diff --git a/src/easydiffraction/display/utils.py b/src/easydiffraction/display/utils.py index 8c06f384..17c6fa94 100644 --- a/src/easydiffraction/display/utils.py +++ b/src/easydiffraction/display/utils.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -18,9 +18,7 @@ class JupyterScrollManager: - """Ensures that Jupyter output cells are not scrollable (applied - once). - """ + """Ensures Jupyter output cells are not scrollable (once).""" _applied: ClassVar[bool] = False diff --git a/src/easydiffraction/io/__init__.py b/src/easydiffraction/io/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/io/__init__.py +++ b/src/easydiffraction/io/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/io/cif/__init__.py b/src/easydiffraction/io/cif/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/io/cif/__init__.py +++ b/src/easydiffraction/io/cif/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/io/cif/handler.py b/src/easydiffraction/io/cif/handler.py index 16ed4343..0cd3b62b 100644 --- a/src/easydiffraction/io/cif/handler.py +++ b/src/easydiffraction/io/cif/handler.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Minimal CIF tag handler used by descriptors/parameters.""" @@ -6,7 +6,8 @@ class CifHandler: - """Canonical CIF handler used by descriptors/parameters. + """ + Canonical CIF handler used by descriptors/parameters. Holds CIF tags (names) and attaches to an owning descriptor so it can derive a stable uid if needed. @@ -16,7 +17,7 @@ def __init__(self, *, names: list[str]) -> None: self._names = names self._owner = None # set by attach - def attach(self, owner): + def attach(self, owner: object) -> None: """Attach to a descriptor or parameter instance.""" self._owner = owner diff --git a/src/easydiffraction/io/cif/parse.py b/src/easydiffraction/io/cif/parse.py index ad0d736d..a320cf60 100644 --- a/src/easydiffraction/io/cif/parse.py +++ b/src/easydiffraction/io/cif/parse.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import gemmi diff --git a/src/easydiffraction/io/cif/serialize.py b/src/easydiffraction/io/cif/serialize.py index 971b08c4..f885aed7 100644 --- a/src/easydiffraction/io/cif/serialize.py +++ b/src/easydiffraction/io/cif/serialize.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -22,14 +22,13 @@ from easydiffraction.core.variable import GenericDescriptorBase -def format_value(value) -> str: - """Format a single CIF value, quoting strings with whitespace, and - format floats with global precision. +def format_value(value: object) -> str: + """ + Format a single CIF value for output. - .. note:: - The precision must be high enough so that the minimizer's - finite-difference Jacobian probes (typically ~1e-8 relative) - survive the float→string→float round-trip through CIF. + .. note:: The precision must be high enough so that the + minimizer's finite-difference Jacobian probes (typically ~1e-8 + relative) survive the float→string→float round-trip through CIF. """ width = 12 precision = 8 @@ -61,8 +60,9 @@ def format_value(value) -> str: ################## -def param_to_cif(param) -> str: - """Render a single descriptor/parameter to a CIF line. +def param_to_cif(param: object) -> str: + """ + Render a single descriptor/parameter to a CIF line. Expects ``param`` to expose ``_cif_handler.names`` and ``value``. """ @@ -71,8 +71,9 @@ def param_to_cif(param) -> str: return f'{main_key} {format_value(param.value)}' -def category_item_to_cif(item) -> str: - """Render a CategoryItem-like object to CIF text. +def category_item_to_cif(item: object) -> str: + """ + Render a CategoryItem-like object to CIF text. Expects ``item.parameters`` iterable of params with ``_cif_handler.names`` and ``value``. @@ -84,10 +85,11 @@ def category_item_to_cif(item) -> str: def category_collection_to_cif( - collection, + collection: object, max_display: Optional[int] = 20, ) -> str: - """Render a CategoryCollection-like object to CIF text. + """ + Render a CategoryCollection-like object to CIF text. Uses first item to build loop header, then emits rows for each item. """ @@ -125,8 +127,9 @@ def category_collection_to_cif( return '\n'.join(lines) -def datablock_item_to_cif(datablock) -> str: - """Render a DatablockItem-like object to CIF text. +def datablock_item_to_cif(datablock: object) -> str: + """ + Render a DatablockItem-like object to CIF text. Emits a data_ header and then concatenates category CIF sections. """ @@ -150,15 +153,13 @@ def datablock_item_to_cif(datablock) -> str: return '\n\n'.join(parts) -def datablock_collection_to_cif(collection) -> str: +def datablock_collection_to_cif(collection: object) -> str: """Render a collection of datablocks by joining their CIF blocks.""" return '\n\n'.join([block.as_cif for block in collection.values()]) -def project_info_to_cif(info) -> str: - """Render ProjectInfo to CIF text (id, title, description, - dates). - """ +def project_info_to_cif(info: object) -> str: + """Render ProjectInfo to CIF text (id, title, description).""" name = f'{info.name}' title = f'{info.title}' @@ -184,7 +185,7 @@ def project_info_to_cif(info) -> str: ) -def project_to_cif(project) -> str: +def project_to_cif(project: object) -> str: """Render a whole project by concatenating sections when present.""" parts: list[str] = [] if hasattr(project, 'info'): @@ -200,12 +201,12 @@ def project_to_cif(project) -> str: return '\n\n'.join([p for p in parts if p]) -def experiment_to_cif(experiment) -> str: +def experiment_to_cif(experiment: object) -> str: """Render an experiment: datablock part plus measured data.""" return datablock_item_to_cif(experiment) -def analysis_to_cif(analysis) -> str: +def analysis_to_cif(analysis: object) -> str: """Render analysis metadata, aliases, and constraints to CIF.""" cur_min = format_value(analysis.current_minimizer) lines: list[str] = [] @@ -222,7 +223,7 @@ def analysis_to_cif(analysis) -> str: return '\n'.join(lines) -def summary_to_cif(_summary) -> str: +def summary_to_cif(_summary: object) -> str: """Render a summary CIF block (placeholder for now).""" return 'To be added...' @@ -239,6 +240,18 @@ def param_from_cif( block: gemmi.cif.Block, idx: int = 0, ) -> None: + """ + Populate a single descriptor from a CIF block. + + Parameters + ---------- + self : GenericDescriptorBase + The descriptor instance to populate. + block : gemmi.cif.Block + Parsed CIF block to read values from. + idx : int, default=0 + Row index used when the tag belongs to a loop. + """ found_values: list[Any] = [] # Try to find the value(s) from the CIF block iterating over @@ -290,6 +303,21 @@ def category_collection_from_cif( self: CategoryCollection, block: gemmi.cif.Block, ) -> None: + """ + Populate a CategoryCollection from a CIF loop. + + Parameters + ---------- + self : CategoryCollection + The collection instance to populate. + block : gemmi.cif.Block + Parsed CIF block to read the loop from. + + Raises + ------ + ValueError + If the collection has no ``_item_type`` defined. + """ # TODO: Find a better way and then remove TODO in the AtomSite # class # TODO: Rename to _item_cls? @@ -302,7 +330,7 @@ def category_collection_from_cif( # Iterate over category parameters and their possible CIF names # trying to find the whole loop it belongs to inside the CIF block - def _get_loop(block, category_item): + def _get_loop(block: object, category_item: object) -> object | None: for param in category_item.parameters: for name in param._cif_handler.names: loop = block.find_loop(name).get_loop() diff --git a/src/easydiffraction/project/__init__.py b/src/easydiffraction/project/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/project/__init__.py +++ b/src/easydiffraction/project/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/project/project.py b/src/easydiffraction/project/project.py index f7a3f487..af5a5922 100644 --- a/src/easydiffraction/project/project.py +++ b/src/easydiffraction/project/project.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Project facade to orchestrate models, experiments, and analysis.""" @@ -22,7 +22,8 @@ class Project(GuardedBase): - """Central API for managing a diffraction data analysis project. + """ + Central API for managing a diffraction data analysis project. Provides access to structures, experiments, analysis, and summary. """ @@ -74,13 +75,19 @@ def info(self) -> ProjectInfo: @property def name(self) -> str: - """Convenience property to access the project's name - directly. - """ + """Convenience property for the project name.""" return self._info.name @property def full_name(self) -> str: + """ + Return the full project name (alias for :attr:`name`). + + Returns + ------- + str + The project name. + """ return self.name @property @@ -94,42 +101,42 @@ def structures(self, structures: Structures) -> None: self._structures = structures @property - def experiments(self): + def experiments(self) -> Experiments: """Collection of experiments in the project.""" return self._experiments @experiments.setter @typechecked - def experiments(self, experiments: Experiments): + def experiments(self, experiments: Experiments) -> None: self._experiments = experiments @property - def plotter(self): + def plotter(self) -> Plotter: """Plotting facade bound to the project.""" return self._plotter @property - def tabler(self): + def tabler(self) -> TableRenderer: """Tables rendering facade bound to the project.""" return self._tabler @property - def analysis(self): + def analysis(self) -> Analysis: """Analysis entry-point bound to the project.""" return self._analysis @property - def summary(self): + def summary(self) -> Summary: """Summary report builder bound to the project.""" return self._summary @property - def parameters(self): + def parameters(self) -> list: """Return parameters from all structures and experiments.""" return self.structures.parameters + self.experiments.parameters @property - def as_cif(self): + def as_cif(self) -> str: """Export whole project as CIF text.""" # Concatenate sections using centralized CIF serializers return project_to_cif(self) @@ -139,7 +146,8 @@ def as_cif(self): # ------------------------------------------ def load(self, dir_path: str) -> None: - """Load a project from a given directory. + """ + Load a project from a given directory. Loads project info, structures, experiments, etc. """ @@ -216,7 +224,7 @@ def save_as( # Plotting # ------------------------------------------ - def _update_categories(self, expt_name) -> None: + def _update_categories(self, expt_name: str) -> None: for structure in self.structures: structure._update_categories() self.analysis._update_categories() @@ -225,11 +233,25 @@ def _update_categories(self, expt_name) -> None: def plot_meas( self, - expt_name, - x_min=None, - x_max=None, - x=None, - ): + expt_name: str, + x_min: float | None = None, + x_max: float | None = None, + x: object | None = None, + ) -> None: + """ + Plot measured diffraction data for an experiment. + + Parameters + ---------- + expt_name : str + Name of the experiment to plot. + x_min : float | None, default=None + Lower bound for the x-axis range. + x_max : float | None, default=None + Upper bound for the x-axis range. + x : object | None, default=None + Optional explicit x-axis data to override stored values. + """ self._update_categories(expt_name) experiment = self.experiments[expt_name] @@ -244,11 +266,25 @@ def plot_meas( def plot_calc( self, - expt_name, - x_min=None, - x_max=None, - x=None, - ): + expt_name: str, + x_min: float | None = None, + x_max: float | None = None, + x: object | None = None, + ) -> None: + """ + Plot calculated diffraction pattern for an experiment. + + Parameters + ---------- + expt_name : str + Name of the experiment to plot. + x_min : float | None, default=None + Lower bound for the x-axis range. + x_max : float | None, default=None + Upper bound for the x-axis range. + x : object | None, default=None + Optional explicit x-axis data to override stored values. + """ self._update_categories(expt_name) experiment = self.experiments[expt_name] @@ -263,12 +299,28 @@ def plot_calc( def plot_meas_vs_calc( self, - expt_name, - x_min=None, - x_max=None, - show_residual=False, - x=None, - ): + expt_name: str, + x_min: float | None = None, + x_max: float | None = None, + show_residual: bool = False, + x: object | None = None, + ) -> None: + """ + Plot measured vs calculated data for an experiment. + + Parameters + ---------- + expt_name : str + Name of the experiment to plot. + x_min : float | None, default=None + Lower bound for the x-axis range. + x_max : float | None, default=None + Upper bound for the x-axis range. + show_residual : bool, default=False + When ``True``, include the residual (difference) curve. + x : object | None, default=None + Optional explicit x-axis data to override stored values. + """ self._update_categories(expt_name) experiment = self.experiments[expt_name] diff --git a/src/easydiffraction/project/project_info.py b/src/easydiffraction/project/project_info.py index 998ba200..d062d522 100644 --- a/src/easydiffraction/project/project_info.py +++ b/src/easydiffraction/project/project_info.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Project metadata container used by Project.""" @@ -12,9 +12,7 @@ class ProjectInfo(GuardedBase): - """Stores metadata about the project, such as name, title, - description, and file paths. - """ + """Store project metadata: name, title, description, paths.""" def __init__( self, @@ -38,6 +36,14 @@ def name(self) -> str: @name.setter def name(self, value: str) -> None: + """ + Set the project name. + + Parameters + ---------- + value : str + New project name. + """ self._name = value @property @@ -52,6 +58,14 @@ def title(self) -> str: @title.setter def title(self, value: str) -> None: + """ + Set the project title. + + Parameters + ---------- + value : str + New project title. + """ self._title = value @property @@ -61,6 +75,14 @@ def description(self) -> str: @description.setter def description(self, value: str) -> None: + """ + Set the project description (whitespace normalized). + + Parameters + ---------- + value : str + New description text. + """ self._description = ' '.join(value.split()) @property @@ -69,7 +91,15 @@ def path(self) -> pathlib.Path: return self._path @path.setter - def path(self, value) -> None: + def path(self, value: object) -> None: + """ + Set the project directory path. + + Parameters + ---------- + value : object + New path as a :class:`str` or :class:`pathlib.Path`. + """ # Accept str or Path; normalize to Path self._path = pathlib.Path(value) @@ -87,8 +117,8 @@ def update_last_modified(self) -> None: """Update the last modified timestamp.""" self._last_modified = datetime.datetime.now() - def parameters(self): - """Placeholder for parameter listing.""" + def parameters(self) -> None: + """List parameters (not implemented).""" pass # TODO: Consider moving to io.cif.serialize diff --git a/src/easydiffraction/summary/__init__.py b/src/easydiffraction/summary/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/summary/__init__.py +++ b/src/easydiffraction/summary/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/summary/summary.py b/src/easydiffraction/summary/summary.py index 7e72d825..9d715bac 100644 --- a/src/easydiffraction/summary/summary.py +++ b/src/easydiffraction/summary/summary.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from textwrap import wrap @@ -9,17 +9,21 @@ class Summary: - """Generates reports and exports results from the project. + """ + Generates reports and exports results from the project. This class collects and presents all relevant information about the fitted model, experiments, and analysis results. """ - def __init__(self, project) -> None: - """Initialize the summary with a reference to the project. + def __init__(self, project: object) -> None: + """ + Initialize the summary with a reference to the project. - Args: - project: The Project instance this summary belongs to. + Parameters + ---------- + project : object + The Project instance this summary belongs to. """ self.project = project @@ -28,6 +32,7 @@ def __init__(self, project) -> None: # ------------------------------------------ def show_report(self) -> None: + """Print a full project report covering all sections.""" self.show_project_info() self.show_crystallographic_data() self.show_experimental_data() @@ -52,9 +57,7 @@ def show_project_info(self) -> None: print('\n'.join(desc_lines)) def show_crystallographic_data(self) -> None: - """Print crystallographic data including phase datablocks, space - groups, cell parameters, and atom sites. - """ + """Print crystallographic data for all phases.""" console.section('Crystallographic data') for model in self.project.structures.values(): @@ -114,9 +117,7 @@ def show_crystallographic_data(self) -> None: ) def show_experimental_data(self) -> None: - """Print experimental data including experiment datablocks, - types, instrument settings, and peak profile information. - """ + """Print experimental data for all experiments.""" console.section('Experiments') for expt in self.project.experiments.values(): @@ -174,9 +175,7 @@ def show_experimental_data(self) -> None: ) def show_fitting_details(self) -> None: - """Print fitting details including calculation and minimization - engines, and fit quality metrics. - """ + """Print fitting details including engines and metrics.""" console.section('Fitting') console.paragraph('Calculation engine') @@ -206,9 +205,7 @@ def show_fitting_details(self) -> None: # ------------------------------------------ def as_cif(self) -> str: - """Export the final fitted data and analysis results as CIF - format. - """ + """Export fitted data and analysis results as CIF.""" from easydiffraction.io.cif.serialize import summary_to_cif return summary_to_cif(self) diff --git a/src/easydiffraction/utils/__init__.py b/src/easydiffraction/utils/__init__.py index d193bf2f..53fde2c6 100644 --- a/src/easydiffraction/utils/__init__.py +++ b/src/easydiffraction/utils/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.utils.utils import _is_dev_version diff --git a/src/easydiffraction/utils/_vendored/__init__.py b/src/easydiffraction/utils/_vendored/__init__.py index 20506363..c124e1af 100644 --- a/src/easydiffraction/utils/_vendored/__init__.py +++ b/src/easydiffraction/utils/_vendored/__init__.py @@ -1,23 +1,22 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Vendored third-party modules. +""" +Vendored third-party modules. This package contains third-party code that has been vendored into the project to avoid external dependencies not available on conda-forge. -Packages: - jupyter_dark_detect/ - Vendored copy of jupyter_dark_detect (MIT License). - Check jupyter_dark_detect.__version__ for vendored version. +Packages: jupyter_dark_detect/ Vendored copy of +jupyter_dark_detect (MIT License). Check +jupyter_dark_detect.__version__ for vendored version. - To update, replace these files from upstream: - - https://github.com/OpenMined/jupyter-dark-detect/blob/main/jupyter_dark_detect/__init__.py - - https://github.com/OpenMined/jupyter-dark-detect/blob/main/jupyter_dark_detect/detector.py +To update, replace these files from upstream: - +https://github.com/OpenMined/jupyter-dark-detect/blob/main/jupyter_dark_detect/__init__.py +- +https://github.com/OpenMined/jupyter-dark-detect/blob/main/jupyter_dark_detect/detector.py - Then run 'pixi run fix' to format and check for issues. +Then run 'pixi run fix' to format and check for issues. -Modules: - theme_detect: - Custom wrapper around jupyter_dark_detect with optimized - detection order for EasyDiffraction's use case. +Modules: theme_detect: Custom wrapper around jupyter_dark_detect with +optimized detection order for EasyDiffraction's use case. """ diff --git a/src/easydiffraction/utils/_vendored/jupyter_dark_detect/__init__.py b/src/easydiffraction/utils/_vendored/jupyter_dark_detect/__init__.py index 11ef7b95..640883dd 100644 --- a/src/easydiffraction/utils/_vendored/jupyter_dark_detect/__init__.py +++ b/src/easydiffraction/utils/_vendored/jupyter_dark_detect/__init__.py @@ -1,7 +1,7 @@ -"""jupyter-dark-detect: Detect dark mode in Jupyter environments. +"""Jupyter-dark-detect: Detect dark mode in Jupyter environments. -This package provides a simple API to detect whether Jupyter Notebook/Lab -is running in dark mode across different environments. +This package provides a simple API to detect whether Jupyter +Notebook/Lab is running in dark mode across different environments. """ from .detector import is_dark diff --git a/src/easydiffraction/utils/_vendored/jupyter_dark_detect/detector.py b/src/easydiffraction/utils/_vendored/jupyter_dark_detect/detector.py index 8f34b5af..e247ba87 100644 --- a/src/easydiffraction/utils/_vendored/jupyter_dark_detect/detector.py +++ b/src/easydiffraction/utils/_vendored/jupyter_dark_detect/detector.py @@ -14,14 +14,11 @@ def is_dark() -> bool: """Check if Jupyter Notebook/Lab is running in dark mode. - This function attempts multiple detection strategies: - 1. JupyterLab theme settings files - 2. VS Code settings (when running in VS Code) - 3. JavaScript DOM inspection - 4. System preferences (macOS) - - Returns: - bool: True if dark mode is detected, False otherwise + This function attempts multiple detection strategies: 1. JupyterLab + theme settings files 2. VS Code settings (when running in VS Code) + 3. JavaScript DOM inspection 4. System preferences (macOS) + + Returns: bool: True if dark mode is detected, False otherwise """ # Try JupyterLab settings first result = _check_jupyterlab_settings() diff --git a/src/easydiffraction/utils/_vendored/theme_detect.py b/src/easydiffraction/utils/_vendored/theme_detect.py index 1d555f0a..9f75ee72 100644 --- a/src/easydiffraction/utils/_vendored/theme_detect.py +++ b/src/easydiffraction/utils/_vendored/theme_detect.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Jupyter theme detection with custom detection order. +""" +Jupyter theme detection with custom detection order. This module wraps the vendored jupyter_dark_detect package and provides a custom detection order optimized for EasyDiffraction's use case. @@ -12,15 +13,12 @@ 3. JavaScript DOM inspection (for browser-based environments) 4. System preferences (macOS, Windows) - fallback only -Note: - The detection order differs from upstream jupyter_dark_detect. - We prioritize JavaScript DOM inspection over system preferences - because the Jupyter theme may differ from the system theme. +Note: The detection order differs from upstream jupyter_dark_detect. We +prioritize JavaScript DOM inspection over system preferences because the +Jupyter theme may differ from the system theme. -Example: - >>> from easydiffraction.utils._vendored.theme_detect import is_dark - >>> if is_dark(): - ... print('Dark mode detected') +Example: >>> from easydiffraction.utils._vendored.theme_detect import +is_dark >>> if is_dark(): ... print('Dark mode detected') """ from __future__ import annotations @@ -37,19 +35,22 @@ def is_dark() -> bool: - """Check if the Jupyter environment is running in dark mode. + """ + Check if the Jupyter environment is running in dark mode. This function uses a custom detection order that prioritizes Jupyter-specific detection over system preferences. Detection order: - 1. JupyterLab settings files (most reliable for JupyterLab) - 2. VS Code settings (when running in VS Code) - 3. JavaScript DOM inspection (for browser-based Jupyter) - 4. System preferences (fallback - may differ from Jupyter theme) + 1. JupyterLab settings files (most reliable for JupyterLab) 2. VS + Code settings (when running in VS Code) 3. JavaScript DOM inspection + (for browser-based Jupyter) 4. System preferences (fallback - may + differ from Jupyter theme) - Returns: + Returns + ------- + bool True if dark mode is detected, False otherwise. """ # Try Jupyter-specific methods first @@ -77,11 +78,14 @@ def is_dark() -> bool: def get_detection_result() -> dict[str, Optional[bool]]: - """Get results from all detection methods for debugging. + """ + Get results from all detection methods for debugging. - Returns: - Dictionary with detection method names as keys and their - results (True/False/None) as values. + Returns + ------- + dict[str, Optional[bool]] + Dictionary with detection method names as keys and their results + (True/False/None) as values. """ return { 'jupyterlab_settings': _check_jupyterlab_settings(), diff --git a/src/easydiffraction/utils/environment.py b/src/easydiffraction/utils/environment.py index aa7de5a6..2c518092 100644 --- a/src/easydiffraction/utils/environment.py +++ b/src/easydiffraction/utils/environment.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -9,27 +9,50 @@ def in_pytest() -> bool: + """ + Determine whether the code is running inside a pytest session. + + Returns + ------- + bool + True if pytest is loaded, False otherwise. + """ return 'pytest' in sys.modules def in_warp() -> bool: + """ + Determine whether the terminal is the Warp terminal emulator. + + Returns + ------- + bool + True if the TERM_PROGRAM environment variable equals + ``'WarpTerminal'``, False otherwise. + """ return os.getenv('TERM_PROGRAM') == 'WarpTerminal' def in_pycharm() -> bool: - """Determines if the current environment is PyCharm. + """ + Check whether the current environment is PyCharm. - Returns: - bool: True if running inside PyCharm, False otherwise. + Returns + ------- + bool + True if running inside PyCharm, False otherwise. """ return os.environ.get('PYCHARM_HOSTED') == '1' def in_colab() -> bool: - """Determines if the current environment is Google Colab. + """ + Check whether the current environment is Google Colab. - Returns: - bool: True if running in Google Colab, False otherwise. + Returns + ------- + bool + True if running in Google Colab, False otherwise. """ try: return find_spec('google.colab') is not None @@ -38,10 +61,13 @@ def in_colab() -> bool: def in_jupyter() -> bool: - """Return True when running inside a Jupyter Notebook. + """ + Return True when running inside a Jupyter Notebook. - Returns: - bool: True if inside a Jupyter Notebook, False otherwise. + Returns + ------- + bool + True if inside a Jupyter Notebook, False otherwise. """ try: import IPython # type: ignore[import-not-found] @@ -76,11 +102,13 @@ def in_jupyter() -> bool: def in_github_ci() -> bool: - """Return True when running under GitHub Actions CI. + """ + Return True when running under GitHub Actions CI. - Returns: - bool: True if env var ``GITHUB_ACTIONS`` is set, False - otherwise. + Returns + ------- + bool + True if env var ``GITHUB_ACTIONS`` is set, False otherwise. """ return os.environ.get('GITHUB_ACTIONS') is not None @@ -91,12 +119,13 @@ def in_github_ci() -> bool: def is_ipython_display_handle(obj: object) -> bool: - """Return True if ``obj`` is an IPython DisplayHandle instance. + """ + Return True if ``obj`` is an IPython DisplayHandle instance. Tries to import ``IPython.display.DisplayHandle`` and uses - ``isinstance`` when available. Falls back to a conservative - module name heuristic if IPython is missing. Any errors result - in ``False``. + ``isinstance`` when available. Falls back to a conservative module + name heuristic if IPython is missing. Any errors result in + ``False``. """ try: # Fast path when IPython is available from IPython.display import DisplayHandle # type: ignore[import-not-found] @@ -115,7 +144,8 @@ def is_ipython_display_handle(obj: object) -> bool: def can_update_ipython_display() -> bool: - """Return True if IPython HTML display utilities are available. + """ + Return True if IPython HTML display utilities are available. This indicates we can safely construct ``IPython.display.HTML`` and update a display handle. @@ -129,7 +159,8 @@ def can_update_ipython_display() -> bool: def can_use_ipython_display(handle: object) -> bool: - """Return True if we can update the given IPython DisplayHandle. + """ + Return True if we can update the given IPython DisplayHandle. Combines type checking of the handle with availability of IPython HTML utilities. diff --git a/src/easydiffraction/utils/logging.py b/src/easydiffraction/utils/logging.py index 056d650b..876d7956 100644 --- a/src/easydiffraction/utils/logging.py +++ b/src/easydiffraction/utils/logging.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Project-wide logging utilities built on top of Rich. +""" +Project-wide logging utilities built on top of Rich. Provides a shared Rich console, a compact/verbose logger with consistent formatting, Jupyter traceback handling, and a small printing façade @@ -43,9 +44,7 @@ class IconifiedRichHandler(RichHandler): - """RichHandler that uses icons for log levels in compact mode, Rich - default in verbose mode. - """ + """RichHandler using icons (compact) or names (verbose).""" _icons = { logging.CRITICAL: '💀', @@ -55,11 +54,24 @@ class IconifiedRichHandler(RichHandler): logging.INFO: 'ℹ️', } - def __init__(self, *args, mode: str = 'compact', **kwargs): + def __init__(self, *args: object, mode: str = 'compact', **kwargs: object) -> None: super().__init__(*args, **kwargs) self.mode = mode def get_level_text(self, record: logging.LogRecord) -> Text: + """ + Return an icon or level name for the log record. + + Parameters + ---------- + record : logging.LogRecord + The log record being rendered. + + Returns + ------- + Text + A Rich Text object with the level indicator. + """ if self.mode == 'compact': icon = self._icons.get(record.levelno, record.levelname) if in_warp() and not in_jupyter() and icon in ['⚠️', '⚙️', 'ℹ️']: @@ -70,9 +82,21 @@ def get_level_text(self, record: logging.LogRecord) -> Text: return super().get_level_text(record) def render_message(self, record: logging.LogRecord, message: str) -> Text: - # In compact mode, let the icon come from get_level_text and - # keep the message body unadorned. In verbose mode, defer to - # RichHandler. + """ + Render the log message body as a Rich Text object. + + Parameters + ---------- + record : logging.LogRecord + The log record being rendered. + message : str + Pre-formatted log message string. + + Returns + ------- + Text + A Rich Text object with the rendered message. + """ if self.mode == 'compact': try: return Text.from_markup(message) @@ -94,9 +118,12 @@ class ConsoleManager: @staticmethod def _detect_width() -> int: - """Detect a suitable console width for the shared Console. + """ + Detect a suitable console width for the shared Console. - Returns: + Returns + ------- + int The detected terminal width, clamped at ``_MIN_CONSOLE_WIDTH`` to avoid cramped layouts. """ @@ -134,13 +161,19 @@ def setup_handlers( rich_tracebacks: bool, mode: str = 'compact', ) -> None: - """Install Rich handler and optional Jupyter traceback support. - - Args: - logger: Logger instance to attach handlers to. - level: Minimum log level to emit. - rich_tracebacks: Whether to enable Rich tracebacks. - mode: Output mode name ("compact" or "verbose"). + """ + Install Rich handler and optional Jupyter traceback support. + + Parameters + ---------- + logger : logging.Logger + Logger instance to attach handlers to. + level : int + Minimum log level to emit. + rich_tracebacks : bool + Whether to enable Rich tracebacks. + mode : str, default='compact' + Output mode name ("compact" or "verbose"). """ logger.handlers.clear() logger.propagate = False @@ -175,13 +208,19 @@ def configure( level: 'Logger.Level', rich_tracebacks: bool, ) -> None: - """Configure the logger with RichHandler and exception hooks. - - Args: - logger: Logger instance to configure. - mode: Output mode (compact or verbose). - level: Minimum log level to emit. - rich_tracebacks: Whether to enable Rich tracebacks. + """ + Configure the logger with RichHandler and exception hooks. + + Parameters + ---------- + logger : logging.Logger + Logger instance to configure. + mode : 'Logger.Mode' + Output mode (compact or verbose). + level : 'Logger.Level' + Minimum log level to emit. + rich_tracebacks : bool + Whether to enable Rich tracebacks. """ LoggerConfig.setup_handlers( logger, @@ -204,10 +243,13 @@ class ExceptionHookManager: @staticmethod def install_verbose_hook(logger: logging.Logger) -> None: - """Install a verbose exception hook that prints rich tracebacks. + """ + Install a verbose exception hook that prints rich tracebacks. - Args: - logger: Logger used to emit the exception information. + Parameters + ---------- + logger : logging.Logger + Logger used to emit the exception information. """ if not hasattr(Logger, '_orig_excepthook'): Logger._orig_excepthook = sys.excepthook # type: ignore[attr-defined] @@ -217,6 +259,7 @@ def aligned_excepthook( exc: BaseException, tb: 'TracebackType | None', ) -> None: + """Log the exception with full traceback via Rich.""" original_args = getattr(exc, 'args', tuple()) message = str(exc) with suppress(Exception): @@ -233,10 +276,13 @@ def aligned_excepthook( @staticmethod def install_compact_hook(logger: logging.Logger) -> None: - """Install a compact exception hook that logs message-only. + """ + Install a compact exception hook that logs message-only. - Args: - logger: Logger used to emit the error message. + Parameters + ---------- + logger : logging.Logger + Logger used to emit the error message. """ if not hasattr(Logger, '_orig_excepthook'): Logger._orig_excepthook = sys.excepthook # type: ignore[attr-defined] @@ -246,33 +292,39 @@ def compact_excepthook( exc: BaseException, _tb: 'TracebackType | None', ) -> None: + """Log the exception message and exit.""" logger.error(str(exc)) raise SystemExit(1) sys.excepthook = compact_excepthook # type: ignore[assignment] @staticmethod - def restore_original_hook(): + def restore_original_hook() -> None: """Restore the original sys.excepthook if it was overridden.""" if hasattr(Logger, '_orig_excepthook'): sys.excepthook = Logger._orig_excepthook # type: ignore[attr-defined] # Jupyter-specific traceback suppression (inlined here) @staticmethod - def _suppress_traceback(logger): - """Build a Jupyter custom exception callback that logs only the - message. + def _suppress_traceback(logger: object) -> object: + """ + Build a Jupyter exception callback that logs the message only. - Args: - logger: Logger used to emit error messages. + Parameters + ---------- + logger : object + Logger used to emit error messages. - Returns: + Returns + ------- + object A callable suitable for IPython's set_custom_exc that suppresses full tracebacks and logs only the exception message. """ - def suppress_jupyter_traceback(*args, **kwargs): + def suppress_jupyter_traceback(*args: object, **kwargs: object) -> None: + """Log only the exception message.""" try: _evalue = ( args[2] if len(args) > 2 else kwargs.get('_evalue') or kwargs.get('evalue') @@ -286,11 +338,13 @@ def suppress_jupyter_traceback(*args, **kwargs): @staticmethod def install_jupyter_traceback_suppressor(logger: logging.Logger) -> None: - """Install a Jupyter/IPython custom exception handler that - suppresses tracebacks. + """ + Install a Jupyter/IPython exception handler for tracebacks. - Args: - logger: Logger used to emit error messages. + Parameters + ---------- + logger : logging.Logger + Logger used to emit error messages. """ try: from IPython import get_ipython @@ -311,11 +365,11 @@ def install_jupyter_traceback_suppressor(logger: logging.Logger) -> None: class Logger: - """Centralized logging with Rich formatting and two modes. + """ + Centralized logging with Rich formatting and two modes. - Environment variables: - ED_LOG_MODE: set default mode ('verbose' or 'compact') - ED_LOG_LEVEL: set default level ('DEBUG', 'INFO', etc.) + Environment variables: ED_LOG_MODE: set default mode ('verbose' or + 'compact') ED_LOG_LEVEL: set default level ('DEBUG', 'INFO', etc.) """ # --- Enums --- @@ -326,7 +380,8 @@ class Mode(Enum): COMPACT = 'compact' # single line; no traceback @classmethod - def default(cls): + def default(cls) -> Logger.Mode: + """Return the default output mode (compact).""" return cls.COMPACT class Level(IntEnum): @@ -339,7 +394,8 @@ class Level(IntEnum): CRITICAL = logging.CRITICAL @classmethod - def default(cls): + def default(cls) -> Logger.Level: + """Return the default log level (WARNING).""" return cls.WARNING class Reaction(Enum): @@ -349,7 +405,8 @@ class Reaction(Enum): WARN = auto() @classmethod - def default(cls): + def default(cls) -> Logger.Reaction: + """Return the default error reaction (RAISE).""" return cls.RAISE # --- Internal state --- @@ -369,15 +426,15 @@ def configure( reaction: Reaction | None = None, rich_tracebacks: bool | None = None, ) -> None: - """Configure logger. + """ + Configure logger. - mode: default COMPACT in Jupyter else VERBOSE - level: minimum log level - rich_tracebacks: override automatic choice + mode: default COMPACT in Jupyter else VERBOSE level: minimum log + level rich_tracebacks: override automatic choice - Environment variables: - ED_LOG_MODE: set default mode ('verbose' or 'compact') - ED_LOG_LEVEL: set default level ('DEBUG', 'INFO', etc.) + Environment variables: ED_LOG_MODE: set default mode ('verbose' + or 'compact') ED_LOG_LEVEL: set default level ('DEBUG', 'INFO', + etc.) """ env_mode = os.getenv('ED_LOG_MODE') env_level = os.getenv('ED_LOG_LEVEL') @@ -418,22 +475,44 @@ def configure( @classmethod def _install_jupyter_traceback_suppressor(cls) -> None: - """Install traceback suppressor in Jupyter, safely and lint- - clean. - """ + """Install the Jupyter traceback suppressor safely.""" ExceptionHookManager.install_jupyter_traceback_suppressor(cls._logger) # ===== Helpers ===== @classmethod def set_mode(cls, mode: Mode) -> None: + """ + Set the output mode and reconfigure the logger. + + Parameters + ---------- + mode : Mode + The desired output mode (VERBOSE or COMPACT). + """ cls.configure(mode=mode, level=cls.Level(cls._logger.level)) @classmethod def set_level(cls, level: Level) -> None: + """ + Set the minimum log level and reconfigure the logger. + + Parameters + ---------- + level : Level + The desired minimum log level. + """ cls.configure(mode=cls._mode, level=level) @classmethod def mode(cls) -> Mode: + """ + Return the currently active output mode. + + Returns + ------- + Mode + The current Logger.Mode value. + """ return cls._mode @classmethod @@ -478,22 +557,68 @@ def handle( @classmethod def debug(cls, *messages: str) -> None: + """ + Log one or more messages at DEBUG level. + + Parameters + ---------- + *messages : str + Message parts joined with a space before logging. + """ cls.handle(*messages, level=cls.Level.DEBUG, exc_type=None) @classmethod def info(cls, *messages: str) -> None: + """ + Log one or more messages at INFO level. + + Parameters + ---------- + *messages : str + Message parts joined with a space before logging. + """ cls.handle(*messages, level=cls.Level.INFO, exc_type=None) @classmethod def warning(cls, *messages: str, exc_type: type[BaseException] | None = None) -> None: + """ + Log one or more messages at WARNING level. + + Parameters + ---------- + *messages : str + Message parts joined with a space before logging. + exc_type : type[BaseException] | None, default=None + If provided, raise this exception type instead of logging. + """ cls.handle(*messages, level=cls.Level.WARNING, exc_type=exc_type) @classmethod def error(cls, *messages: str, exc_type: type[BaseException] = AttributeError) -> None: + """ + Log one or more messages at ERROR level. + + Parameters + ---------- + *messages : str + Message parts joined with a space before logging. + exc_type : type[BaseException], default=AttributeError + Exception type to raise in VERBOSE/COMPACT mode. + """ cls.handle(*messages, level=cls.Level.ERROR, exc_type=exc_type) @classmethod def critical(cls, *messages: str, exc_type: type[BaseException] = RuntimeError) -> None: + """ + Log one or more messages at CRITICAL level. + + Parameters + ---------- + *messages : str + Message parts joined with a space before logging. + exc_type : type[BaseException], default=RuntimeError + Exception type to raise in VERBOSE/COMPACT mode. + """ cls.handle(*messages, level=cls.Level.CRITICAL, exc_type=exc_type) @@ -503,20 +628,18 @@ def critical(cls, *messages: str, exc_type: type[BaseException] = RuntimeError) class ConsolePrinter: - """Printer utility that prints objects to the shared console with - left padding. - """ + """Printer utility for the shared console with left padding.""" _console = ConsoleManager.get() @classmethod - def print(cls, *objects, **kwargs): - """Print objects to the console with left padding. + def print(cls, *objects: object, **kwargs: object) -> None: + """ + Print objects to the console with left padding. - Renderables (Rich types like Text, Table, Panel, etc.) are - kept as-is. - - Non-renderables (ints, floats, Path, etc.) are converted to - str(). + kept as-is. - Non-renderables (ints, floats, Path, etc.) are + converted to str(). """ safe_objects = [] for obj in objects: @@ -538,6 +661,15 @@ def print(cls, *objects, **kwargs): @classmethod def paragraph(cls, title: str) -> None: + """ + Print a bold blue paragraph heading. + + Parameters + ---------- + title : str + Heading text; substrings enclosed in single quotes are + rendered without the bold-blue style. + """ parts = re.split(r"('.*?')", title) text = Text() for part in parts: @@ -552,7 +684,7 @@ def paragraph(cls, title: str) -> None: @classmethod def section(cls, title: str) -> None: - """Formats a section header with bold green text.""" + """Format a section header with bold green text.""" full_title = f'{title.upper()}' line = '—' * len(full_title) formatted = f'[bold green]{line}\n{full_title}\n{line}[/bold green]' @@ -562,9 +694,7 @@ def section(cls, title: str) -> None: @classmethod def chapter(cls, title: str) -> None: - """Formats a chapter header with bold magenta text, uppercase, - and padding. - """ + """Format a chapter header in bold magenta, uppercase.""" width = ConsoleManager._detect_width() symbol = '—' full_title = f' {title.upper()} ' diff --git a/src/easydiffraction/utils/utils.py b/src/easydiffraction/utils/utils.py index 82118302..3e8c5f1e 100644 --- a/src/easydiffraction/utils/utils.py +++ b/src/easydiffraction/utils/utils.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -30,13 +30,18 @@ def _validate_url(url: str) -> None: - """Validate that a URL uses only safe HTTP/HTTPS schemes. + """ + Validate that a URL uses only safe HTTP/HTTPS schemes. - Args: - url: The URL to validate. + Parameters + ---------- + url : str + The URL to validate. - Raises: - ValueError: If the URL scheme is not HTTP or HTTPS. + Raises + ------ + ValueError + If the URL scheme is not HTTP or HTTPS. """ parsed = urlparse(url) if parsed.scheme not in ('http', 'https'): @@ -44,16 +49,15 @@ def _validate_url(url: str) -> None: def _filename_for_id_from_url(data_id: int | str, url: str) -> str: - """Return local filename like 'ed-12.xye' using extension from the - URL. - """ + """Return local filename using the extension from the URL.""" suffix = pathlib.Path(urlparse(url).path).suffix # includes leading dot ('.cif', '.xye', ...) # If URL has no suffix, fall back to no extension. return f'ed-{data_id}{suffix}' def _normalize_known_hash(value: str | None) -> str | None: - """Return pooch-compatible known_hash or None. + """ + Return pooch-compatible known_hash or None. Treat placeholder values like 'sha256:...' as unset. """ @@ -66,9 +70,7 @@ def _normalize_known_hash(value: str | None) -> str | None: def _fetch_data_index() -> dict: - """Fetch & cache the diffraction data index.json and return it as - dict. - """ + """Fetch and cache the diffraction data index.json.""" index_url = 'https://raw.githubusercontent.com/easyscience/data/refs/heads/master/diffraction/index.json' _validate_url(index_url) @@ -92,18 +94,20 @@ def _fetch_data_index() -> dict: @functools.lru_cache(maxsize=1) def _fetch_tutorials_index() -> dict: - """Fetch & cache the tutorials index.json from gh-pages and return - it as dict. + """ + Fetch and cache the tutorials index.json from gh-pages. The index is fetched from: https://easyscience.github.io/diffraction-lib/{version}/tutorials/index.json - For released versions, {version} is the public version string - (e.g., '0.8.0.post1'). For development versions, 'dev' is used. + For released versions, {version} is the public version string (e.g., + '0.8.0.post1'). For development versions, 'dev' is used. - Returns: - dict: The tutorials index as a dictionary, or empty dict if - fetch fails. + Returns + ------- + dict + The tutorials index as a dictionary, or empty dict if fetch + fails. """ version = _get_version_for_url() index_url = f'https://easyscience.github.io/diffraction-lib/{version}/tutorials/index.json' @@ -125,24 +129,29 @@ def download_data( destination: str = 'data', overwrite: bool = False, ) -> str: - """Download a dataset by numeric ID using the remote diffraction - index. - - Example: - path = download_data(id=12, destination="data") + """ + Download a dataset by numeric ID using the remote diffraction index. - Args: - id: Numeric dataset id (e.g. 12). - destination: Directory to save the file into (created if - missing). - overwrite: Whether to overwrite the file if it already exists. + Example: path = download_data(id=12, destination="data") - Returns: - str: Full path to the downloaded file as string. + Parameters + ---------- + id : int | str + Numeric dataset id (e.g. 12). + destination : str, default='data' + Directory to save the file into (created if missing). + overwrite : bool, default=False + Whether to overwrite the file if it already exists. + + Returns + ------- + str + Full path to the downloaded file as string. - Raises: - KeyError: If the id is not found in the index. - ValueError: If the resolved URL is not HTTP/HTTPS. + Raises + ------ + KeyError + If the id is not found in the index. """ index = _fetch_data_index() key = str(id) @@ -195,14 +204,19 @@ def download_data( def package_version(package_name: str) -> str | None: - """Get the installed version string of the specified package. + """ + Get the installed version string of the specified package. - Args: - package_name (str): The name of the package to query. + Parameters + ---------- + package_name : str + The name of the package to query. - Returns: - str | None: The raw version string (may include local part, - e.g., '1.2.3+abc123'), or None if the package is not installed. + Returns + ------- + str | None + The raw version string (may include local part, e.g., + '1.2.3+abc123'), or None if the package is not installed. """ try: return version(package_name) @@ -211,18 +225,22 @@ def package_version(package_name: str) -> str | None: def stripped_package_version(package_name: str) -> str | None: - """Get the installed version of the specified package, stripped of - any local version part. + """ + Get installed package version, stripped of local version parts. Returns only the public version segment (e.g., '1.2.3' or '1.2.3.post4'), omitting any local segment (e.g., '+d136'). - Args: - package_name (str): The name of the package to query. + Parameters + ---------- + package_name : str + The name of the package to query. - Returns: - str | None: The public version string, or None if the package - is not installed. + Returns + ------- + str | None + The public version string, or None if the package is not + installed. """ v_str = package_version(package_name) if v_str is None: @@ -235,21 +253,22 @@ def stripped_package_version(package_name: str) -> str | None: def _is_dev_version(package_name: str) -> bool: - """Check if the installed package version is a development/local - version. + """ + Check if the installed package version is a dev version. - A version is considered "dev" if: - - The raw version contains '+dev', '+dirty', or '+devdirty' (local - suffixes from versioningit) - - The public version is '999.0.0' (versioningit default-tag - fallback) + A version is considered "dev" if: - The raw version contains '+dev', + '+dirty', or '+devdirty' (local suffixes from versioningit) - The + public version is '999.0.0' (versioningit default-tag fallback) - Args: - package_name (str): The name of the package to query. + Parameters + ---------- + package_name : str + The name of the package to query. - Returns: - bool: True if the version is a development version, False - otherwise. + Returns + ------- + bool + True if the version is a development version, False otherwise. """ raw_version = package_version(package_name) if raw_version is None: @@ -265,26 +284,31 @@ def _is_dev_version(package_name: str) -> bool: def _get_version_for_url(package_name: str = 'easydiffraction') -> str: - """Get the version string to use in URLs for fetching remote - resources. + """ + Get the version string to use in URLs for fetching remote resources. Returns the public version for released versions, or 'dev' for development/local versions. - Args: - package_name (str): The name of the package to query. + Parameters + ---------- + package_name : str, default='easydiffraction' + The name of the package to query. - Returns: - str: The version string to use in URLs ('dev' or a version like - '0.8.0.post1'). + Returns + ------- + str + The version string to use in URLs ('dev' or a version like + '0.8.0.post1'). """ if _is_dev_version(package_name): return 'dev' return stripped_package_version(package_name) or 'dev' -def _safe_urlopen(request_or_url): # type: ignore[no-untyped-def] - """Wrapper for urlopen with prior validation. +def _safe_urlopen(request_or_url: object) -> object: # type: ignore[no-untyped-def] + """ + Open a URL with prior validation. Centralises lint suppression for validated HTTPS requests. """ @@ -301,25 +325,29 @@ def _safe_urlopen(request_or_url): # type: ignore[no-untyped-def] def _resolve_tutorial_url(url_template: str) -> str: - """Replace {version} placeholder in URL template with actual - version. + """ + Replace {version} placeholder in URL template with actual version. - Args: - url_template (str): URL template containing {version} - placeholder. + Parameters + ---------- + url_template : str + URL template containing {version} placeholder. - Returns: - str: URL with {version} replaced by actual version string. + Returns + ------- + str + URL with {version} replaced by actual version string. """ version = _get_version_for_url() return url_template.replace('{version}', version) def list_tutorials() -> None: - """Display a table of available tutorial notebooks. + """ + Display a table of available tutorial notebooks. - Shows tutorial ID, filename, title, and description for all - tutorials available for the current version of easydiffraction. + Shows tutorial ID, filename and title for all tutorials available + for the current version of easydiffraction. """ index = _fetch_tutorials_index() if not index: @@ -327,19 +355,17 @@ def list_tutorials() -> None: return version = _get_version_for_url() - console.print(f'Tutorials available for easydiffraction v{version}:') - console.print('') + console.paragraph(f'Tutorials available for easydiffraction v{version}:') - columns_headers = ['id', 'file', 'title', 'description'] - columns_alignment = ['right', 'left', 'left', 'left'] + columns_headers = ['id', 'file', 'title'] + columns_alignment = ['right', 'left', 'left'] columns_data = [] - for tutorial_id in sorted(index.keys(), key=lambda x: int(x) if x.isdigit() else x): + for tutorial_id in index: record = index[tutorial_id] filename = f'ed-{tutorial_id}.ipynb' title = record.get('title', '') - description = record.get('description', '') - columns_data.append([tutorial_id, filename, title, description]) + columns_data.append([tutorial_id, filename, title]) render_table( columns_headers=columns_headers, @@ -353,23 +379,29 @@ def download_tutorial( destination: str = 'tutorials', overwrite: bool = False, ) -> str: - """Download a tutorial notebook by numeric ID. - - Example: - path = download_tutorial(id=1, destination="tutorials") + """ + Download a tutorial notebook by numeric ID. - Args: - id: Numeric tutorial id (e.g. 1). - destination: Directory to save the file into (created if - missing). - overwrite: Whether to overwrite the file if it already exists. + Example: path = download_tutorial(id=1, destination="tutorials") - Returns: - str: Full path to the downloaded file as string. + Parameters + ---------- + id : int | str + Numeric tutorial id (e.g. 1). + destination : str, default='tutorials' + Directory to save the file into (created if missing). + overwrite : bool, default=False + Whether to overwrite the file if it already exists. + + Returns + ------- + str + Full path to the downloaded file as string. - Raises: - KeyError: If the id is not found in the index. - ValueError: If the resolved URL is not HTTP/HTTPS. + Raises + ------ + KeyError + If the id is not found in the index. """ index = _fetch_tutorials_index() key = str(id) @@ -420,18 +452,22 @@ def download_all_tutorials( destination: str = 'tutorials', overwrite: bool = False, ) -> list[str]: - """Download all available tutorial notebooks. + """ + Download all available tutorial notebooks. - Example: - paths = download_all_tutorials(destination="tutorials") + Example: paths = download_all_tutorials(destination="tutorials") - Args: - destination: Directory to save the files into (created if - missing). - overwrite: Whether to overwrite files if they already exist. + Parameters + ---------- + destination : str, default='tutorials' + Directory to save the files into (created if missing). + overwrite : bool, default=False + Whether to overwrite files if they already exist. - Returns: - list[str]: List of full paths to the downloaded files. + Returns + ------- + list[str] + List of full paths to the downloaded files. """ index = _fetch_tutorials_index() if not index: @@ -458,11 +494,7 @@ def download_all_tutorials( def show_version() -> None: - """Print the installed version of the easydiffraction package. - - Args: - None - """ + """Print the installed version of the easydiffraction package.""" current_ed_version = package_version('easydiffraction') console.print(f'Current easydiffraction v{current_ed_version}') @@ -470,11 +502,27 @@ def show_version() -> None: # TODO: This is a temporary utility function. Complete migration to # TableRenderer (as e.g. in show_all_params) and remove this. def render_table( - columns_data, - columns_alignment, - columns_headers=None, - display_handle=None, -): + columns_data: object, + columns_alignment: object, + columns_headers: object = None, + display_handle: object = None, +) -> None: + """ + Render tabular data to the active display backend. + + Parameters + ---------- + columns_data : object + A list of rows, where each row is a list of cell values. + columns_alignment : object + A list of alignment strings (e.g. ``'left'``, ``'right'``, + ``'center'``) matching the number of columns. + columns_headers : object, default=None + Optional list of column header strings. + display_handle : object, default=None + Optional display handle for in-place updates (e.g. in Jupyter or + a terminal Live context). + """ headers = [ (col, align) for col, align in zip(columns_headers, columns_alignment, strict=False) ] @@ -484,12 +532,14 @@ def render_table( tabler.render(df, display_handle=display_handle) -def render_cif(cif_text) -> None: - """Display the CIF text as a formatted table in Jupyter Notebook or - terminal. +def render_cif(cif_text: str) -> None: + """ + Display CIF text as a formatted table in Jupyter or terminal. - Args: - cif_text: The CIF text to display. + Parameters + ---------- + cif_text : str + The CIF text to display. """ # Split into lines lines: List[str] = [line for line in cif_text.splitlines()] @@ -510,37 +560,42 @@ def tof_to_d( offset: float, linear: float, quad: float, - quad_eps=1e-20, + quad_eps: float = 1e-20, ) -> np.ndarray: - """Convert time-of-flight (TOF) to d-spacing using a quadratic - calibration. - - Model: - TOF = offset + linear * d + quad * d² - - The function: - - Uses a linear fallback when the quadratic term is effectively - zero. - - Solves the quadratic for d and selects the smallest positive, - finite root. - - Returns NaN where no valid solution exists. - - Expects ``tof`` as a NumPy array; output matches its shape. - - Args: - tof (np.ndarray): Time-of-flight values (µs). Must be a NumPy - array. - offset (float): Calibration offset (µs). - linear (float): Linear calibration coefficient (µs/Å). - quad (float): Quadratic calibration coefficient (µs/Ų). - quad_eps (float, optional): Threshold to treat ``quad`` as zero. - Defaults to 1e-20. - - Returns: - np.ndarray: d-spacing values (Å), NaN where invalid. - - Raises: - TypeError: If ``tof`` is not a NumPy array or coefficients are - not real numbers. + """ + Convert time-of-flight to d-spacing using quadratic calibration. + + Model: TOF = offset + linear * d + quad * d² + + The function: - Uses a linear fallback when the quadratic term is + effectively zero. - Solves the quadratic for d and selects the + smallest positive, finite root. - Returns NaN where no valid + solution exists. - Expects ``tof`` as a NumPy array; output matches + its shape. + + Parameters + ---------- + tof : np.ndarray + Time-of-flight values (µs). Must be a NumPy array. + offset : float + Calibration offset (µs). + linear : float + Linear calibration coefficient (µs/Å). + quad : float + Quadratic calibration coefficient (µs/Ų). + quad_eps : float, default=1e-20 + Threshold to treat ``quad`` as zero. + + Returns + ------- + np.ndarray + d-spacing values (Å), NaN where invalid. + + Raises + ------ + TypeError + If ``tof`` is not a NumPy array or coefficients are not real + numbers. """ # Type checks if not isinstance(tof, np.ndarray): @@ -594,15 +649,21 @@ def tof_to_d( return d_out -def twotheta_to_d(twotheta, wavelength): - """Convert 2-theta to d-spacing using Bragg's law. +def twotheta_to_d(twotheta: object, wavelength: float) -> object: + """ + Convert 2-theta to d-spacing using Bragg's law. - Parameters: - twotheta (float or np.ndarray): 2-theta angle in degrees. - wavelength (float): Wavelength in Å. + Parameters + ---------- + twotheta : object + 2-theta angle in degrees (float or np.ndarray). + wavelength : float + Wavelength in Å. - Returns: - d (float or np.ndarray): d-spacing in Å. + Returns + ------- + object + d-spacing in Å (float or np.ndarray). """ # Convert twotheta from degrees to radians theta_rad = np.radians(twotheta / 2) @@ -613,15 +674,19 @@ def twotheta_to_d(twotheta, wavelength): return d -def sin_theta_over_lambda_to_d_spacing(sin_theta_over_lambda): - """Convert sin(theta)/lambda to d-spacing. +def sin_theta_over_lambda_to_d_spacing(sin_theta_over_lambda: object) -> object: + """ + Convert sin(theta)/lambda to d-spacing. - Parameters: - sin_theta_over_lambda (float or np.ndarray): sin(theta)/lambda - in 1/Å. + Parameters + ---------- + sin_theta_over_lambda : object + sin(theta)/lambda in 1/Å (float or np.ndarray). - Returns: - d (float or np.ndarray): d-spacing in Å. + Returns + ------- + object + d-spacing in Å (float or np.ndarray). """ # Avoid division by zero with np.errstate(divide='ignore', invalid='ignore'): @@ -631,19 +696,26 @@ def sin_theta_over_lambda_to_d_spacing(sin_theta_over_lambda): return d -def get_value_from_xye_header(file_path, key): - """Extracts a floating point value from the first line of the file, - corresponding to the given key. +def get_value_from_xye_header(file_path: str, key: str) -> float: + """ + Extract a float from the first line of the file by key. - Parameters: - file_path (str): Path to the input file. - key (str): The key to extract ('DIFC' or 'two_theta'). + Parameters + ---------- + file_path : str + Path to the input file. + key : str + The key to extract ('DIFC' or 'two_theta'). - Returns: - float: The extracted value. + Returns + ------- + float + The extracted value. - Raises: - ValueError: If the key is not found. + Raises + ------ + ValueError + If the key is not found. """ pattern = rf'{key}\s*=\s*([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)' @@ -658,35 +730,30 @@ def get_value_from_xye_header(file_path, key): def str_to_ufloat(s: Optional[str], default: Optional[float] = None) -> UFloat: - """Parse a CIF-style numeric string into a `ufloat` with an optional - uncertainty. - - Examples of supported input: - - "3.566" → ufloat(3.566, nan) - - "3.566(2)" → ufloat(3.566, 0.002) - - None → ufloat(default, nan) - - Behavior: - - If the input string contains a value with parentheses (e.g. - "3.566(2)"), the number in parentheses is interpreted as an - estimated standard deviation (esd) in the last digit(s). - - If the input string has no parentheses, an uncertainty of NaN is - assigned to indicate "no esd provided". - - If parsing fails, the function falls back to the given `default` - value with uncertainty NaN. + """ + Parse a CIF-style numeric string into a ufloat. + + Examples of supported input: - "3.566" → ufloat(3.566, nan) - + "3.566(2)" → ufloat(3.566, 0.002) - None → ufloat(default, nan) + + Behavior: - If the input string contains a value with parentheses + (e.g. "3.566(2)"), the number in parentheses is interpreted as an + estimated standard deviation (esd) in the last digit(s). - If the + input string has no parentheses, an uncertainty of NaN is assigned + to indicate "no esd provided". - If parsing fails, the function + falls back to the given ``default`` value with uncertainty NaN. Parameters ---------- - s : str or None + s : Optional[str] Numeric string in CIF format (e.g. "3.566", "3.566(2)") or None. - default : float or None, optional - Default value to use if `s` is None or parsing fails. - Defaults to None. + default : Optional[float], default=None + Default value to use if ``s`` is None or parsing fails. - Returns: + Returns ------- UFloat - An `uncertainties.UFloat` object with the parsed value and + An ``uncertainties.UFloat`` object with the parsed value and uncertainty. The uncertainty will be NaN if not specified or parsing failed. """ diff --git a/tests/integration/fitting/test_multi.py b/tests/integration/fitting/test_multi.py index 60e78f0a..6b1a0c8f 100644 --- a/tests/integration/fitting/test_multi.py +++ b/tests/integration/fitting/test_multi.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import tempfile diff --git a/tests/integration/fitting/test_pair-distribution-function.py b/tests/integration/fitting/test_pair-distribution-function.py index 823fd420..066e727e 100644 --- a/tests/integration/fitting/test_pair-distribution-function.py +++ b/tests/integration/fitting/test_pair-distribution-function.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import tempfile diff --git a/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py b/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py index 49e7b4b1..5045dd27 100644 --- a/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py +++ b/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import tempfile diff --git a/tests/integration/fitting/test_powder-diffraction_joint-fit.py b/tests/integration/fitting/test_powder-diffraction_joint-fit.py index cc3e600e..fb94a8ff 100644 --- a/tests/integration/fitting/test_powder-diffraction_joint-fit.py +++ b/tests/integration/fitting/test_powder-diffraction_joint-fit.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import tempfile diff --git a/tests/integration/fitting/test_powder-diffraction_time-of-flight.py b/tests/integration/fitting/test_powder-diffraction_time-of-flight.py index ae702b4d..18123254 100644 --- a/tests/integration/fitting/test_powder-diffraction_time-of-flight.py +++ b/tests/integration/fitting/test_powder-diffraction_time-of-flight.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import tempfile diff --git a/tests/integration/fitting/test_single-crystal-diffraction.py b/tests/integration/fitting/test_single-crystal-diffraction.py index af915d92..f5273fdd 100644 --- a/tests/integration/fitting/test_single-crystal-diffraction.py +++ b/tests/integration/fitting/test_single-crystal-diffraction.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import tempfile diff --git a/tests/integration/scipp-analysis/dream/conftest.py b/tests/integration/scipp-analysis/dream/conftest.py index 56aabf1f..b16da0a1 100644 --- a/tests/integration/scipp-analysis/dream/conftest.py +++ b/tests/integration/scipp-analysis/dream/conftest.py @@ -1,5 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026 DMSC + """Shared fixtures for DREAM scipp-analysis integration tests. This module provides pytest fixtures for downloading and parsing diff --git a/tests/integration/scipp-analysis/dream/test_analyze_reduced_data.py b/tests/integration/scipp-analysis/dream/test_analyze_reduced_data.py index cde09c8a..23821ee2 100644 --- a/tests/integration/scipp-analysis/dream/test_analyze_reduced_data.py +++ b/tests/integration/scipp-analysis/dream/test_analyze_reduced_data.py @@ -1,5 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026 DMSC + """Tests for analyzing reduced diffraction data using easydiffraction. These tests verify the complete workflow: diff --git a/tests/integration/scipp-analysis/dream/test_package_import.py b/tests/integration/scipp-analysis/dream/test_package_import.py index 7c10d02b..03125806 100644 --- a/tests/integration/scipp-analysis/dream/test_package_import.py +++ b/tests/integration/scipp-analysis/dream/test_package_import.py @@ -1,5 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026 DMSC + """Tests for verifying package installation and version consistency. These tests check that easydiffraction and essdiffraction packages are diff --git a/tests/integration/scipp-analysis/dream/test_read_reduced_data.py b/tests/integration/scipp-analysis/dream/test_read_reduced_data.py index 616c9876..db589354 100644 --- a/tests/integration/scipp-analysis/dream/test_read_reduced_data.py +++ b/tests/integration/scipp-analysis/dream/test_read_reduced_data.py @@ -1,5 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026 DMSC + """Tests for reading reduced data from CIF files. These tests verify that the CIF file can be fetched, read as text, diff --git a/tests/integration/scipp-analysis/dream/test_validate_meta_data.py b/tests/integration/scipp-analysis/dream/test_validate_meta_data.py index 7712dbc3..6f07845a 100644 --- a/tests/integration/scipp-analysis/dream/test_validate_meta_data.py +++ b/tests/integration/scipp-analysis/dream/test_validate_meta_data.py @@ -1,5 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026 DMSC + """Tests for validating metadata structure in CIF files. These tests verify that the CIF file contains the expected data blocks, diff --git a/tests/integration/scipp-analysis/dream/test_validate_physical_data.py b/tests/integration/scipp-analysis/dream/test_validate_physical_data.py index f1be5eb3..a8e5d4fb 100644 --- a/tests/integration/scipp-analysis/dream/test_validate_physical_data.py +++ b/tests/integration/scipp-analysis/dream/test_validate_physical_data.py @@ -1,5 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026 DMSC + """Tests for validating physical data values in CIF files. These tests verify that numerical data columns contain valid, diff --git a/tests/unit/easydiffraction/analysis/calculators/test_base.py b/tests/unit/easydiffraction/analysis/calculators/test_base.py index 6483af6e..5070b9a3 100644 --- a/tests/unit/easydiffraction/analysis/calculators/test_base.py +++ b/tests/unit/easydiffraction/analysis/calculators/test_base.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.analysis.calculators.base as MUT diff --git a/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py b/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py index e35d1bd5..a6a1371c 100644 --- a/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py +++ b/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest diff --git a/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py b/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py index bccd63ec..8593bfe0 100644 --- a/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py +++ b/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.analysis.calculators.cryspy as MUT diff --git a/tests/unit/easydiffraction/analysis/calculators/test_factory.py b/tests/unit/easydiffraction/analysis/calculators/test_factory.py index 7df08b97..681299fa 100644 --- a/tests/unit/easydiffraction/analysis/calculators/test_factory.py +++ b/tests/unit/easydiffraction/analysis/calculators/test_factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest diff --git a/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py b/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py index 268d0d84..9dce59f5 100644 --- a/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py +++ b/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/analysis/categories/test_aliases.py b/tests/unit/easydiffraction/analysis/categories/test_aliases.py index 4860961d..2545218a 100644 --- a/tests/unit/easydiffraction/analysis/categories/test_aliases.py +++ b/tests/unit/easydiffraction/analysis/categories/test_aliases.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.categories.aliases import Alias @@ -7,8 +7,8 @@ def test_alias_creation_and_collection(): a = Alias() - a.label='x' - a.param_uid='p1' + a.label = 'x' + a.param_uid = 'p1' assert a.label.value == 'x' coll = Aliases() coll.create(label='x', param_uid='p1') diff --git a/tests/unit/easydiffraction/analysis/categories/test_constraints.py b/tests/unit/easydiffraction/analysis/categories/test_constraints.py index 7d4acb0a..443f9b1b 100644 --- a/tests/unit/easydiffraction/analysis/categories/test_constraints.py +++ b/tests/unit/easydiffraction/analysis/categories/test_constraints.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.categories.constraints import Constraint @@ -7,8 +7,8 @@ def test_constraint_creation_and_collection(): c = Constraint() - c.lhs_alias='a' - c.rhs_expr='b + c' + c.lhs_alias = 'a' + c.rhs_expr = 'b + c' assert c.lhs_alias.value == 'a' coll = Constraints() coll.create(lhs_alias='a', rhs_expr='b + c') diff --git a/tests/unit/easydiffraction/analysis/categories/test_joint_fit_experiments.py b/tests/unit/easydiffraction/analysis/categories/test_joint_fit_experiments.py index c4194c35..51777f11 100644 --- a/tests/unit/easydiffraction/analysis/categories/test_joint_fit_experiments.py +++ b/tests/unit/easydiffraction/analysis/categories/test_joint_fit_experiments.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.categories.joint_fit_experiments import JointFitExperiment @@ -7,8 +7,8 @@ def test_joint_fit_experiment_and_collection(): j = JointFitExperiment() - j.id='ex1' - j.weight=0.5 + j.id = 'ex1' + j.weight = 0.5 assert j.id.value == 'ex1' assert j.weight.value == 0.5 coll = JointFitExperiments() diff --git a/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py b/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py index 7c8b75c9..2fc968f8 100644 --- a/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py +++ b/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/analysis/fit_helpers/test_reporting.py b/tests/unit/easydiffraction/analysis/fit_helpers/test_reporting.py index 458519ce..ee8014f2 100644 --- a/tests/unit/easydiffraction/analysis/fit_helpers/test_reporting.py +++ b/tests/unit/easydiffraction/analysis/fit_helpers/test_reporting.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.analysis.fit_helpers.reporting as MUT diff --git a/tests/unit/easydiffraction/analysis/fit_helpers/test_tracking.py b/tests/unit/easydiffraction/analysis/fit_helpers/test_tracking.py index bf3873ab..561eb54c 100644 --- a/tests/unit/easydiffraction/analysis/fit_helpers/test_tracking.py +++ b/tests/unit/easydiffraction/analysis/fit_helpers/test_tracking.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_base.py b/tests/unit/easydiffraction/analysis/minimizers/test_base.py index 5d04a75a..501a2a98 100644 --- a/tests/unit/easydiffraction/analysis/minimizers/test_base.py +++ b/tests/unit/easydiffraction/analysis/minimizers/test_base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py b/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py index 88e22dee..72d01949 100644 --- a/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py +++ b/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_factory.py b/tests/unit/easydiffraction/analysis/minimizers/test_factory.py index 236d1656..961ef782 100644 --- a/tests/unit/easydiffraction/analysis/minimizers/test_factory.py +++ b/tests/unit/easydiffraction/analysis/minimizers/test_factory.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_minimizer_factory_list_and_show(capsys): from easydiffraction.analysis.minimizers.factory import MinimizerFactory diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py b/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py index 77b694ee..69005bd1 100644 --- a/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py +++ b/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import types diff --git a/tests/unit/easydiffraction/analysis/test_analysis.py b/tests/unit/easydiffraction/analysis/test_analysis.py index 56d493aa..19bc3c2a 100644 --- a/tests/unit/easydiffraction/analysis/test_analysis.py +++ b/tests/unit/easydiffraction/analysis/test_analysis.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.analysis.analysis as MUT @@ -36,7 +37,6 @@ def test_show_current_minimizer_prints(capsys): assert 'lmfit' in out - def test_fit_mode_category_and_joint_fit_experiments(monkeypatch, capsys): from easydiffraction.analysis.analysis import Analysis diff --git a/tests/unit/easydiffraction/analysis/test_analysis_access_params.py b/tests/unit/easydiffraction/analysis/test_analysis_access_params.py index 96bf6ca1..bdd5ead0 100644 --- a/tests/unit/easydiffraction/analysis/test_analysis_access_params.py +++ b/tests/unit/easydiffraction/analysis/test_analysis_access_params.py @@ -1,12 +1,13 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_how_to_access_parameters_prints_paths_and_uids(capsys, monkeypatch): + import easydiffraction.analysis.analysis as analysis_mod from easydiffraction.analysis.analysis import Analysis - from easydiffraction.core.variable import Parameter from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import Parameter from easydiffraction.io.cif.handler import CifHandler - import easydiffraction.analysis.analysis as analysis_mod # Build two parameters with identity metadata set directly def make_param(db, cat, entry, name, val): diff --git a/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py b/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py index 921127ba..7f2895b4 100644 --- a/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py +++ b/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_show_params_empty_branches(capsys): from easydiffraction.analysis.analysis import Analysis diff --git a/tests/unit/easydiffraction/analysis/test_fitting.py b/tests/unit/easydiffraction/analysis/test_fitting.py index d3a1b1fb..2f703f77 100644 --- a/tests/unit/easydiffraction/analysis/test_fitting.py +++ b/tests/unit/easydiffraction/analysis/test_fitting.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.analysis.fitting as MUT diff --git a/tests/unit/easydiffraction/core/test_category.py b/tests/unit/easydiffraction/core/test_category.py index c53fd12c..632d96f1 100644 --- a/tests/unit/easydiffraction/core/test_category.py +++ b/tests/unit/easydiffraction/core/test_category.py @@ -1,10 +1,10 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.core.category import CategoryCollection from easydiffraction.core.category import CategoryItem -from easydiffraction.core.variable import StringDescriptor from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.variable import StringDescriptor from easydiffraction.io.cif.handler import CifHandler @@ -97,5 +97,3 @@ def test_category_collection_help(capsys): assert 'Items (2)' in out assert 'n1' in out assert 'n2' in out - - diff --git a/tests/unit/easydiffraction/core/test_collection.py b/tests/unit/easydiffraction/core/test_collection.py index 616b232f..817b76dd 100644 --- a/tests/unit/easydiffraction/core/test_collection.py +++ b/tests/unit/easydiffraction/core/test_collection.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_collection_add_get_delete_and_names(): from easydiffraction.core.collection import CollectionBase from easydiffraction.core.identity import Identity @@ -55,11 +56,11 @@ def as_cif(self) -> str: def test_collection_remove(): + import pytest + from easydiffraction.core.collection import CollectionBase from easydiffraction.core.identity import Identity - import pytest - class Item: def __init__(self, name): self._identity = Identity(owner=self, category_entry=lambda: name) @@ -119,4 +120,3 @@ def as_cif(self) -> str: del c['beta'] assert 'beta' not in c assert len(c) == 1 - diff --git a/tests/unit/easydiffraction/core/test_datablock.py b/tests/unit/easydiffraction/core/test_datablock.py index 6565bbaa..b41f68b7 100644 --- a/tests/unit/easydiffraction/core/test_datablock.py +++ b/tests/unit/easydiffraction/core/test_datablock.py @@ -1,12 +1,13 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_datablock_collection_add_and_filters_with_real_parameters(): from easydiffraction.core.category import CategoryItem from easydiffraction.core.datablock import DatablockCollection from easydiffraction.core.datablock import DatablockItem - from easydiffraction.core.variable import Parameter from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import Parameter from easydiffraction.io.cif.handler import CifHandler class Cat(CategoryItem): @@ -78,8 +79,8 @@ def cat(self): def test_datablock_item_help(capsys): from easydiffraction.core.category import CategoryItem from easydiffraction.core.datablock import DatablockItem - from easydiffraction.core.variable import Parameter from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import Parameter from easydiffraction.io.cif.handler import CifHandler class Cat(CategoryItem): @@ -133,4 +134,3 @@ def __init__(self, name): out = capsys.readouterr().out assert 'Items (1)' in out assert 'A' in out - diff --git a/tests/unit/easydiffraction/core/test_diagnostic.py b/tests/unit/easydiffraction/core/test_diagnostic.py index 98e96320..cda7ce98 100644 --- a/tests/unit/easydiffraction/core/test_diagnostic.py +++ b/tests/unit/easydiffraction/core/test_diagnostic.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest diff --git a/tests/unit/easydiffraction/core/test_factory.py b/tests/unit/easydiffraction/core/test_factory.py index ee85b97c..78150ea5 100644 --- a/tests/unit/easydiffraction/core/test_factory.py +++ b/tests/unit/easydiffraction/core/test_factory.py @@ -1,5 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause - -# core/factory.py was removed — FactoryBase and _validate_args are no -# longer part of the codebase. This test file is intentionally empty. diff --git a/tests/unit/easydiffraction/core/test_guard.py b/tests/unit/easydiffraction/core/test_guard.py index 34407914..64d46680 100644 --- a/tests/unit/easydiffraction/core/test_guard.py +++ b/tests/unit/easydiffraction/core/test_guard.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest @@ -99,5 +99,3 @@ def test_first_sentence_extracts_first_paragraph(): assert GuardedBase._first_sentence('One liner.') == 'One liner.' assert GuardedBase._first_sentence('First.\n\nSecond.') == 'First.' assert GuardedBase._first_sentence('Line one\ncontinued.') == 'Line one continued.' - - diff --git a/tests/unit/easydiffraction/core/test_identity.py b/tests/unit/easydiffraction/core/test_identity.py index 584135d7..61da0723 100644 --- a/tests/unit/easydiffraction/core/test_identity.py +++ b/tests/unit/easydiffraction/core/test_identity.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_identity_direct_and_parent_resolution(): from easydiffraction.core.identity import Identity diff --git a/tests/unit/easydiffraction/core/test_parameters.py b/tests/unit/easydiffraction/core/test_parameters.py index 35c65558..4bf46f0d 100644 --- a/tests/unit/easydiffraction/core/test_parameters.py +++ b/tests/unit/easydiffraction/core/test_parameters.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -13,9 +13,9 @@ def test_module_import(): def test_string_descriptor_type_override_raises_type_error(): # Creating a StringDescriptor with a NUMERIC spec should raise via Diagnostics - from easydiffraction.core.variable import StringDescriptor from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import DataTypes + from easydiffraction.core.variable import StringDescriptor from easydiffraction.io.cif.handler import CifHandler with pytest.raises(TypeError): @@ -28,8 +28,8 @@ def test_string_descriptor_type_override_raises_type_error(): def test_numeric_descriptor_str_includes_units(): - from easydiffraction.core.variable import NumericDescriptor from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import NumericDescriptor from easydiffraction.io.cif.handler import CifHandler d = NumericDescriptor( @@ -43,8 +43,8 @@ def test_numeric_descriptor_str_includes_units(): def test_parameter_string_repr_and_as_cif_and_flags(): - from easydiffraction.core.variable import Parameter from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import Parameter from easydiffraction.io.cif.handler import CifHandler p = Parameter( @@ -69,8 +69,8 @@ def test_parameter_string_repr_and_as_cif_and_flags(): def test_parameter_uncertainty_must_be_non_negative(): - from easydiffraction.core.variable import Parameter from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import Parameter from easydiffraction.io.cif.handler import CifHandler p = Parameter( @@ -83,8 +83,8 @@ def test_parameter_uncertainty_must_be_non_negative(): def test_parameter_fit_bounds_assign_and_read(): - from easydiffraction.core.variable import Parameter from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import Parameter from easydiffraction.io.cif.handler import CifHandler p = Parameter( diff --git a/tests/unit/easydiffraction/core/test_singletons.py b/tests/unit/easydiffraction/core/test_singletons.py index bd2c5aef..ba69f07a 100644 --- a/tests/unit/easydiffraction/core/test_singletons.py +++ b/tests/unit/easydiffraction/core/test_singletons.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest diff --git a/tests/unit/easydiffraction/core/test_validation.py b/tests/unit/easydiffraction/core/test_validation.py index 70a92bb5..3bbd0ac3 100644 --- a/tests/unit/easydiffraction/core/test_validation.py +++ b/tests/unit/easydiffraction/core/test_validation.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.core.validation as MUT diff --git a/tests/unit/easydiffraction/crystallography/test_crystallography.py b/tests/unit/easydiffraction/crystallography/test_crystallography.py index 3c2bf7df..73e1a134 100644 --- a/tests/unit/easydiffraction/crystallography/test_crystallography.py +++ b/tests/unit/easydiffraction/crystallography/test_crystallography.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.crystallography.crystallography as MUT diff --git a/tests/unit/easydiffraction/crystallography/test_space_groups.py b/tests/unit/easydiffraction/crystallography/test_space_groups.py index 5629f633..dd1482cd 100644 --- a/tests/unit/easydiffraction/crystallography/test_space_groups.py +++ b/tests/unit/easydiffraction/crystallography/test_space_groups.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.crystallography.space_groups as MUT diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_base.py b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_base.py index 31c7fd0b..88049e83 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_base.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -7,9 +7,9 @@ def test_background_base_minimal_impl_and_collection_cif(): from easydiffraction.core.category import CategoryItem from easydiffraction.core.collection import CollectionBase - from easydiffraction.core.variable import Parameter from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import DataTypes + from easydiffraction.core.variable import Parameter from easydiffraction.datablocks.experiment.categories.background.base import BackgroundBase from easydiffraction.io.cif.handler import CifHandler diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_chebyshev.py b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_chebyshev.py index b59ea102..d10a3c40 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_chebyshev.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_chebyshev.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_enums.py b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_enums.py index 47e0f5f3..b37a22e6 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_enums.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_enums.py @@ -1,14 +1,14 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause def test_background_type_info(): - from easydiffraction.datablocks.experiment.categories.background.line_segment import ( - LineSegmentBackground, - ) from easydiffraction.datablocks.experiment.categories.background.chebyshev import ( ChebyshevPolynomialBackground, ) + from easydiffraction.datablocks.experiment.categories.background.line_segment import ( + LineSegmentBackground, + ) assert LineSegmentBackground.type_info.tag == 'line-segment' assert LineSegmentBackground.type_info.description == 'Linear interpolation between points' diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_factory.py index 15c40a19..09679489 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_factory.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_factory.py @@ -1,11 +1,13 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest def test_background_factory_default_and_errors(): - from easydiffraction.datablocks.experiment.categories.background.factory import BackgroundFactory + from easydiffraction.datablocks.experiment.categories.background.factory import ( + BackgroundFactory, + ) # Default via default_tag() obj = BackgroundFactory.create(BackgroundFactory.default_tag()) diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_line_segment.py b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_line_segment.py index e4e89605..ff231943 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_line_segment.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_line_segment.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_pd.py b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_pd.py index eaa48808..1a3f233c 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_pd.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -142,4 +142,3 @@ def test_pd_data_intensity_meas_su_zero_replacement(): assert su[0] == 1.0 # replaced assert su[1] == 1.0 # replaced assert su[2] == 5.0 # kept - diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_sc.py b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_sc.py index 06dd634d..534fd192 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_sc.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_sc.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -90,4 +90,3 @@ def test_refln_data_type_info(): assert ReflnData.type_info.tag == 'bragg-sc' assert ReflnData.type_info.description == 'Bragg single-crystal reflection data' - diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_factory.py index 18ecd5d0..6f52cdff 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_factory.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_factory.py @@ -1,16 +1,15 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest def test_data_factory_default_and_errors(): - from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory - # Ensure concrete classes are registered from easydiffraction.datablocks.experiment.categories.data import bragg_pd # noqa: F401 from easydiffraction.datablocks.experiment.categories.data import bragg_sc # noqa: F401 from easydiffraction.datablocks.experiment.categories.data import total_pd # noqa: F401 + from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory # Explicit type by tag obj = DataFactory.create('bragg-pd') @@ -32,15 +31,14 @@ def test_data_factory_default_and_errors(): def test_data_factory_default_tag_resolution(): - from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory - from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum - from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum - from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum - # Ensure concrete classes are registered from easydiffraction.datablocks.experiment.categories.data import bragg_pd # noqa: F401 from easydiffraction.datablocks.experiment.categories.data import bragg_sc # noqa: F401 from easydiffraction.datablocks.experiment.categories.data import total_pd # noqa: F401 + from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory + from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum + from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum + from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum # Context-dependent default: Bragg powder CWL tag = DataFactory.default_tag( @@ -74,16 +72,14 @@ def test_data_factory_default_tag_resolution(): def test_data_factory_supported_tags(): - from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory - # Ensure concrete classes are registered from easydiffraction.datablocks.experiment.categories.data import bragg_pd # noqa: F401 from easydiffraction.datablocks.experiment.categories.data import bragg_sc # noqa: F401 from easydiffraction.datablocks.experiment.categories.data import total_pd # noqa: F401 + from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory tags = DataFactory.supported_tags() assert 'bragg-pd' in tags assert 'bragg-pd-tof' in tags assert 'bragg-sc' in tags assert 'total-pd' in tags - diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_total_pd.py b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_total_pd.py index 90c85e3e..41e638e5 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_total_pd.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_total_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -89,4 +89,3 @@ def test_total_data_type_info(): assert TotalData.type_info.tag == 'total-pd' assert TotalData.type_info.description == 'Total scattering (PDF) data' - diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_base.py b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_base.py index 70d36b8a..38bcb8c7 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_base.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_base.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_instrument_base_sets_category_code(): from easydiffraction.datablocks.experiment.categories.instrument.base import InstrumentBase diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_cwl.py b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_cwl.py index 205abd50..0816f6e7 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_cwl.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_cwl.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.instrument.cwl import CwlPdInstrument diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_factory.py index f122f9be..04117aa9 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_factory.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest @@ -6,7 +6,9 @@ def test_instrument_factory_default_and_errors(): try: - from easydiffraction.datablocks.experiment.categories.instrument.factory import InstrumentFactory + from easydiffraction.datablocks.experiment.categories.instrument.factory import ( + InstrumentFactory, + ) from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum except ImportError as e: # pragma: no cover - environment-specific circular import diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_tof.py b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_tof.py index 91c4f0d0..bfd6cc12 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_tof.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_tof.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_base.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_base.py index ad3708dc..229fc734 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_base.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.peak.base import PeakBase diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl.py index f4505287..d941afe9 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_cwl_peak_classes_expose_expected_parameters_and_category(): from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlPseudoVoigt from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlSplitPseudoVoigt diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl_mixins.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl_mixins.py index 3bdc4466..19026f50 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl_mixins.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl_mixins.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlPseudoVoigt diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_factory.py index 6ff155ac..4b0ccd35 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_factory.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof.py index fde6d062..78e25d52 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.peak.tof import TofPseudoVoigt diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof_mixins.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof_mixins.py index ae39c99c..c2f114a0 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof_mixins.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof_mixins.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -6,10 +6,16 @@ def test_tof_broadening_and_asymmetry_mixins(): from easydiffraction.datablocks.experiment.categories.peak.base import PeakBase - from easydiffraction.datablocks.experiment.categories.peak.tof_mixins import IkedaCarpenterAsymmetryMixin + from easydiffraction.datablocks.experiment.categories.peak.tof_mixins import ( + IkedaCarpenterAsymmetryMixin, + ) from easydiffraction.datablocks.experiment.categories.peak.tof_mixins import TofBroadeningMixin - class TofPeak(PeakBase, TofBroadeningMixin, IkedaCarpenterAsymmetryMixin,): + class TofPeak( + PeakBase, + TofBroadeningMixin, + IkedaCarpenterAsymmetryMixin, + ): def __init__(self): super().__init__() diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total.py index c49d1cf7..46a6497b 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total_mixins.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total_mixins.py index 0979dcb9..c3534847 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total_mixins.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total_mixins.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.peak.total import TotalGaussianDampedSinc diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/test_excluded_regions.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_excluded_regions.py index 8026740b..8f34b8de 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/test_excluded_regions.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_excluded_regions.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -23,7 +23,7 @@ def test_excluded_regions_add_updates_datastore_and_cif(): meas=full_meas.copy(), meas_su=full_meas_su.copy(), ) - + def set_calc_status(status): # _set_calc_status sets excluded to the inverse ds.excluded = ~status @@ -31,7 +31,7 @@ def set_calc_status(status): ds.x = ds.full_x[status] ds.meas = ds.full_meas[status] ds.meas_su = ds.full_meas_su[status] - + ds._set_calc_status = set_calc_status coll = ExcludedRegions() diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/test_experiment_type.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_experiment_type.py index 169510ab..9190071e 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/test_experiment_type.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_experiment_type.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.datablocks.experiment.categories.experiment_type as MUT diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py index 5287b488..dcee6f6e 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause @@ -74,5 +74,3 @@ def test_extinction_factory_default_tag(): ) assert ExtinctionFactory.default_tag() == 'shelx' - - diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_crystal.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_crystal.py index 06035598..69f96e7b 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_crystal.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_crystal.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause @@ -86,5 +86,3 @@ def test_linked_crystal_factory_default_tag(): ) assert LinkedCrystalFactory.default_tag() == 'default' - - diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_phases.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_phases.py index 263586f8..3b4b9fa7 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_phases.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_phases.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_linked_phases_add_and_cif_headers(): from easydiffraction.datablocks.experiment.categories.linked_phases import LinkedPhase from easydiffraction.datablocks.experiment.categories.linked_phases import LinkedPhases diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_base.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_base.py index 6a6766ed..c2a0ab92 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/item/test_base.py +++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_base.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.datablocks.experiment.item.base as MUT diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_pd.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_pd.py index 8b164ed0..89c763dc 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_pd.py +++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -22,8 +22,6 @@ def _mk_type_powder_cwl_bragg(): return et - - def test_background_defaults_and_change(): expt = BraggPdExperiment(name='e1', type=_mk_type_powder_cwl_bragg()) # default background type diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_sc.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_sc.py index c01a5ee2..a69dd1bd 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_sc.py +++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_sc.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest @@ -21,7 +21,6 @@ def _mk_type_sc_bragg(): return et - class _ConcreteCwlSc(CwlScExperiment): def _load_ascii_data_to_experiment(self, data_path: str) -> None: # Not used in this test diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_enums.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_enums.py index dff44c0f..983f991b 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/item/test_enums.py +++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_enums.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.datablocks.experiment.item.enums as MUT diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py index 8dac91b6..c185ed30 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py +++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.datablocks.experiment.item.factory as MUT @@ -25,4 +26,3 @@ def test_experiment_factory_from_scratch(): ) # Instance should be created (BraggPdExperiment) assert hasattr(ex, 'type') and ex.type.sample_form.value == SampleFormEnum.POWDER.value - diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_total_pd.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_total_pd.py index 78d40afc..d021dc34 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/item/test_total_pd.py +++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_total_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/datablocks/experiment/test_collection.py b/tests/unit/easydiffraction/datablocks/experiment/test_collection.py index 5165f141..bcb9f822 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/test_collection.py +++ b/tests/unit/easydiffraction/datablocks/experiment/test_collection.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.datablocks.experiment.collection as MUT @@ -10,8 +11,8 @@ def test_module_import(): def test_experiments_show_and_remove(monkeypatch, capsys): - from easydiffraction.datablocks.experiment.item.base import ExperimentBase from easydiffraction.datablocks.experiment.collection import Experiments + from easydiffraction.datablocks.experiment.item.base import ExperimentBase class DummyType: def __init__(self): diff --git a/tests/unit/easydiffraction/datablocks/structure/categories/test_space_group.py b/tests/unit/easydiffraction/datablocks/structure/categories/test_space_group.py index 89786b9e..4025bd83 100644 --- a/tests/unit/easydiffraction/datablocks/structure/categories/test_space_group.py +++ b/tests/unit/easydiffraction/datablocks/structure/categories/test_space_group.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.structure.categories.space_group import SpaceGroup diff --git a/tests/unit/easydiffraction/datablocks/structure/item/test_base.py b/tests/unit/easydiffraction/datablocks/structure/item/test_base.py index 33bb878b..154d0405 100644 --- a/tests/unit/easydiffraction/datablocks/structure/item/test_base.py +++ b/tests/unit/easydiffraction/datablocks/structure/item/test_base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.structure.item.base import Structure diff --git a/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py b/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py index d1b50776..148e010a 100644 --- a/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py +++ b/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.structure.item.factory import StructureFactory diff --git a/tests/unit/easydiffraction/datablocks/structure/test_collection.py b/tests/unit/easydiffraction/datablocks/structure/test_collection.py index 9955a1e3..4e798e20 100644 --- a/tests/unit/easydiffraction/datablocks/structure/test_collection.py +++ b/tests/unit/easydiffraction/datablocks/structure/test_collection.py @@ -1,6 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause - -import pytest - -from easydiffraction.datablocks.structure.collection import Structures diff --git a/tests/unit/easydiffraction/display/plotters/test_ascii.py b/tests/unit/easydiffraction/display/plotters/test_ascii.py index 616bc60d..e0c04f8b 100644 --- a/tests/unit/easydiffraction/display/plotters/test_ascii.py +++ b/tests/unit/easydiffraction/display/plotters/test_ascii.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/display/plotters/test_base.py b/tests/unit/easydiffraction/display/plotters/test_base.py index d1c2d561..b1029d2c 100644 --- a/tests/unit/easydiffraction/display/plotters/test_base.py +++ b/tests/unit/easydiffraction/display/plotters/test_base.py @@ -1,9 +1,8 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -import importlib -import types import sys +import types def test_module_import(): @@ -36,10 +35,34 @@ def test_default_axes_labels_keys_present(): from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum # Powder Bragg - assert (SampleFormEnum.POWDER, ScatteringTypeEnum.BRAGG, pb.XAxisType.TWO_THETA) in pb.DEFAULT_AXES_LABELS - assert (SampleFormEnum.POWDER, ScatteringTypeEnum.BRAGG, pb.XAxisType.TIME_OF_FLIGHT) in pb.DEFAULT_AXES_LABELS - assert (SampleFormEnum.POWDER, ScatteringTypeEnum.BRAGG, pb.XAxisType.D_SPACING) in pb.DEFAULT_AXES_LABELS + assert ( + SampleFormEnum.POWDER, + ScatteringTypeEnum.BRAGG, + pb.XAxisType.TWO_THETA, + ) in pb.DEFAULT_AXES_LABELS + assert ( + SampleFormEnum.POWDER, + ScatteringTypeEnum.BRAGG, + pb.XAxisType.TIME_OF_FLIGHT, + ) in pb.DEFAULT_AXES_LABELS + assert ( + SampleFormEnum.POWDER, + ScatteringTypeEnum.BRAGG, + pb.XAxisType.D_SPACING, + ) in pb.DEFAULT_AXES_LABELS # Single crystal Bragg - assert (SampleFormEnum.SINGLE_CRYSTAL, ScatteringTypeEnum.BRAGG, pb.XAxisType.INTENSITY_CALC) in pb.DEFAULT_AXES_LABELS - assert (SampleFormEnum.SINGLE_CRYSTAL, ScatteringTypeEnum.BRAGG, pb.XAxisType.D_SPACING) in pb.DEFAULT_AXES_LABELS - assert (SampleFormEnum.SINGLE_CRYSTAL, ScatteringTypeEnum.BRAGG, pb.XAxisType.SIN_THETA_OVER_LAMBDA) in pb.DEFAULT_AXES_LABELS + assert ( + SampleFormEnum.SINGLE_CRYSTAL, + ScatteringTypeEnum.BRAGG, + pb.XAxisType.INTENSITY_CALC, + ) in pb.DEFAULT_AXES_LABELS + assert ( + SampleFormEnum.SINGLE_CRYSTAL, + ScatteringTypeEnum.BRAGG, + pb.XAxisType.D_SPACING, + ) in pb.DEFAULT_AXES_LABELS + assert ( + SampleFormEnum.SINGLE_CRYSTAL, + ScatteringTypeEnum.BRAGG, + pb.XAxisType.SIN_THETA_OVER_LAMBDA, + ) in pb.DEFAULT_AXES_LABELS diff --git a/tests/unit/easydiffraction/display/plotters/test_plotly.py b/tests/unit/easydiffraction/display/plotters/test_plotly.py index 06539f2b..5a1549f0 100644 --- a/tests/unit/easydiffraction/display/plotters/test_plotly.py +++ b/tests/unit/easydiffraction/display/plotters/test_plotly.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.display.plotters.plotly as MUT diff --git a/tests/unit/easydiffraction/display/test_plotting.py b/tests/unit/easydiffraction/display/test_plotting.py index 15caf344..be5b4239 100644 --- a/tests/unit/easydiffraction/display/test_plotting.py +++ b/tests/unit/easydiffraction/display/test_plotting.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.display.plotting as MUT @@ -59,7 +60,9 @@ def test_plotter_error_paths_and_filtering(capsys): from easydiffraction.display.plotting import Plotter class Ptn: - def __init__(self, two_theta=None, intensity_meas=None, intensity_calc=None, d_spacing=None): + def __init__( + self, two_theta=None, intensity_meas=None, intensity_calc=None, d_spacing=None + ): self.two_theta = two_theta self.intensity_meas = intensity_meas self.intensity_calc = intensity_calc @@ -90,13 +93,19 @@ def __init__(self): out = capsys.readouterr().out assert 'No calculated data available for experiment E' in out - p.plot_meas_vs_calc(Ptn(two_theta=None, intensity_meas=None, intensity_calc=None), 'E', ExptType()) + p.plot_meas_vs_calc( + Ptn(two_theta=None, intensity_meas=None, intensity_calc=None), 'E', ExptType() + ) out = capsys.readouterr().out assert 'No measured data available for experiment E' in out - p.plot_meas_vs_calc(Ptn(two_theta=[1], intensity_meas=None, intensity_calc=[1]), 'E', ExptType()) + p.plot_meas_vs_calc( + Ptn(two_theta=[1], intensity_meas=None, intensity_calc=[1]), 'E', ExptType() + ) out = capsys.readouterr().out assert 'No measured data available for experiment E' in out - p.plot_meas_vs_calc(Ptn(two_theta=[1], intensity_meas=[1], intensity_calc=None), 'E', ExptType()) + p.plot_meas_vs_calc( + Ptn(two_theta=[1], intensity_meas=[1], intensity_calc=None), 'E', ExptType() + ) out = capsys.readouterr().out assert 'No calculated data available for experiment E' in out diff --git a/tests/unit/easydiffraction/io/cif/test_handler.py b/tests/unit/easydiffraction/io/cif/test_handler.py index ea31b833..a9c1be85 100644 --- a/tests/unit/easydiffraction/io/cif/test_handler.py +++ b/tests/unit/easydiffraction/io/cif/test_handler.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_cif_handler_names_and_uid(): import easydiffraction.io.cif.handler as H diff --git a/tests/unit/easydiffraction/io/cif/test_serialize.py b/tests/unit/easydiffraction/io/cif/test_serialize.py index 48f044a1..6b92a0e4 100644 --- a/tests/unit/easydiffraction/io/cif/test_serialize.py +++ b/tests/unit/easydiffraction/io/cif/test_serialize.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.io.cif.serialize as MUT diff --git a/tests/unit/easydiffraction/io/cif/test_serialize_more.py b/tests/unit/easydiffraction/io/cif/test_serialize_more.py index 54f345ee..fc8ef714 100644 --- a/tests/unit/easydiffraction/io/cif/test_serialize_more.py +++ b/tests/unit/easydiffraction/io/cif/test_serialize_more.py @@ -1,9 +1,6 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -import numpy as np -import pytest - def test_datablock_item_to_cif_includes_item_and_collection(): import easydiffraction.io.cif.serialize as MUT diff --git a/tests/unit/easydiffraction/project/test_project.py b/tests/unit/easydiffraction/project/test_project.py index 1a949fc5..c6a64055 100644 --- a/tests/unit/easydiffraction/project/test_project.py +++ b/tests/unit/easydiffraction/project/test_project.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.project.project as MUT @@ -19,4 +20,3 @@ def test_project_help(capsys): assert 'experiments' in out assert 'analysis' in out assert 'summary' in out - diff --git a/tests/unit/easydiffraction/project/test_project_info.py b/tests/unit/easydiffraction/project/test_project_info.py index ef8b061f..8c3455ab 100644 --- a/tests/unit/easydiffraction/project/test_project_info.py +++ b/tests/unit/easydiffraction/project/test_project_info.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.project.project_info as MUT diff --git a/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py b/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py index 5cb103b8..cdeafd35 100644 --- a/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py +++ b/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_project_load_prints_and_sets_path(tmp_path, capsys): import pytest diff --git a/tests/unit/easydiffraction/project/test_project_save.py b/tests/unit/easydiffraction/project/test_project_save.py index eb662dfc..421e8928 100644 --- a/tests/unit/easydiffraction/project/test_project_save.py +++ b/tests/unit/easydiffraction/project/test_project_save.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_project_save_uses_cwd_when_no_explicit_path(monkeypatch, tmp_path, capsys): # Default ProjectInfo.path is cwd; ensure save writes into a temp cwd, not repo root from easydiffraction.project.project import Project diff --git a/tests/unit/easydiffraction/summary/test_summary.py b/tests/unit/easydiffraction/summary/test_summary.py index 29da4e28..b57b5598 100644 --- a/tests/unit/easydiffraction/summary/test_summary.py +++ b/tests/unit/easydiffraction/summary/test_summary.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_summary_as_cif_returns_placeholder_string(): from easydiffraction.summary.summary import Summary @@ -46,11 +47,6 @@ class R: assert 'FITTING' in out - - - - - def test_module_import(): import easydiffraction.summary.summary as MUT diff --git a/tests/unit/easydiffraction/summary/test_summary_details.py b/tests/unit/easydiffraction/summary/test_summary_details.py index 0e9fcf04..d0e0c97b 100644 --- a/tests/unit/easydiffraction/summary/test_summary_details.py +++ b/tests/unit/easydiffraction/summary/test_summary_details.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_summary_crystallographic_and_experimental_sections(capsys): from easydiffraction.summary.summary import Summary diff --git a/tests/unit/easydiffraction/test___init__.py b/tests/unit/easydiffraction/test___init__.py index 5eb8c38f..75f273e1 100644 --- a/tests/unit/easydiffraction/test___init__.py +++ b/tests/unit/easydiffraction/test___init__.py @@ -1,7 +1,6 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Focused tests for package __init__: lazy attributes and error path import importlib from pathlib import Path diff --git a/tests/unit/easydiffraction/test___main__.py b/tests/unit/easydiffraction/test___main__.py index 885f73ef..76ba7cec 100644 --- a/tests/unit/easydiffraction/test___main__.py +++ b/tests/unit/easydiffraction/test___main__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from typer.testing import CliRunner diff --git a/tests/unit/easydiffraction/utils/test_logging.py b/tests/unit/easydiffraction/utils/test_logging.py index 8503e178..6dd520c2 100644 --- a/tests/unit/easydiffraction/utils/test_logging.py +++ b/tests/unit/easydiffraction/utils/test_logging.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.utils.logging as MUT diff --git a/tests/unit/easydiffraction/utils/test_theme_detect.py b/tests/unit/easydiffraction/utils/test_theme_detect.py index e9899fb9..c3277a0a 100644 --- a/tests/unit/easydiffraction/utils/test_theme_detect.py +++ b/tests/unit/easydiffraction/utils/test_theme_detect.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Unit tests for theme detection module.""" @@ -23,12 +23,7 @@ def test_dark_theme_detection(self, tmp_path: Path) -> None: ) settings_dir = ( - tmp_path - / '.jupyter' - / 'lab' - / 'user-settings' - / '@jupyterlab' - / 'apputils-extension' + tmp_path / '.jupyter' / 'lab' / 'user-settings' / '@jupyterlab' / 'apputils-extension' ) settings_dir.mkdir(parents=True) @@ -45,12 +40,7 @@ def test_light_theme_detection(self, tmp_path: Path) -> None: ) settings_dir = ( - tmp_path - / '.jupyter' - / 'lab' - / 'user-settings' - / '@jupyterlab' - / 'apputils-extension' + tmp_path / '.jupyter' / 'lab' / 'user-settings' / '@jupyterlab' / 'apputils-extension' ) settings_dir.mkdir(parents=True) @@ -76,12 +66,7 @@ def test_comments_in_settings(self, tmp_path: Path) -> None: ) settings_dir = ( - tmp_path - / '.jupyter' - / 'lab' - / 'user-settings' - / '@jupyterlab' - / 'apputils-extension' + tmp_path / '.jupyter' / 'lab' / 'user-settings' / '@jupyterlab' / 'apputils-extension' ) settings_dir.mkdir(parents=True) @@ -119,9 +104,7 @@ def test_vscode_dark_theme(self, tmp_path: Path) -> None: vscode_dir.mkdir() settings_file = vscode_dir / 'settings.json' - settings_file.write_text( - json.dumps({'workbench.colorTheme': 'One Dark Pro'}) - ) + settings_file.write_text(json.dumps({'workbench.colorTheme': 'One Dark Pro'})) with mock.patch.dict(os.environ, {'VSCODE_PID': '12345'}): with mock.patch.object(Path, 'cwd', return_value=tmp_path): @@ -137,9 +120,7 @@ def test_vscode_light_theme(self, tmp_path: Path) -> None: vscode_dir.mkdir() settings_file = vscode_dir / 'settings.json' - settings_file.write_text( - json.dumps({'workbench.colorTheme': 'Light+ (default light)'}) - ) + settings_file.write_text(json.dumps({'workbench.colorTheme': 'Light+ (default light)'})) with mock.patch.dict(os.environ, {'VSCODE_PID': '12345'}): with mock.patch.object(Path, 'cwd', return_value=tmp_path): @@ -160,9 +141,7 @@ def test_vscode_nls_config_dark(self) -> None: class TestCheckSystemPreferences: """Tests for _check_system_preferences function.""" - @pytest.mark.skipif( - not os.sys.platform.startswith('darwin'), reason='macOS only test' - ) + @pytest.mark.skipif(not os.sys.platform.startswith('darwin'), reason='macOS only test') def test_macos_dark_mode(self) -> None: """Test macOS dark mode detection.""" from easydiffraction.utils._vendored.jupyter_dark_detect.detector import ( @@ -174,9 +153,7 @@ def test_macos_dark_mode(self) -> None: mock_run.return_value.stdout = 'Dark' assert _check_system_preferences() is True - @pytest.mark.skipif( - not os.sys.platform.startswith('darwin'), reason='macOS only test' - ) + @pytest.mark.skipif(not os.sys.platform.startswith('darwin'), reason='macOS only test') def test_macos_light_mode(self) -> None: """Test macOS light mode detection.""" from easydiffraction.utils._vendored.jupyter_dark_detect.detector import ( @@ -195,86 +172,82 @@ def test_default_to_false(self) -> None: """Test that is_dark defaults to False when no detection works.""" from easydiffraction.utils._vendored.theme_detect import is_dark - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_jupyterlab_settings', - return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_vscode_settings', + with ( + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_jupyterlab_settings', + return_value=None, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_vscode_settings', return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_javascript_detection', - return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_system_preferences', - return_value=None, - ): - assert is_dark() is False + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_javascript_detection', + return_value=None, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_system_preferences', + return_value=None, + ), + ): + assert is_dark() is False def test_jupyterlab_priority(self) -> None: """Test that JupyterLab settings take priority.""" from easydiffraction.utils._vendored.theme_detect import is_dark - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_jupyterlab_settings', - return_value=True, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_vscode_settings', + with ( + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_jupyterlab_settings', + return_value=True, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_vscode_settings', return_value=False, - ): - assert is_dark() is True + ), + ): + assert is_dark() is True def test_vscode_second_priority(self) -> None: """Test that VS Code settings are checked after JupyterLab.""" from easydiffraction.utils._vendored.theme_detect import is_dark - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_jupyterlab_settings', - return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_vscode_settings', + with ( + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_jupyterlab_settings', + return_value=None, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_vscode_settings', return_value=True, - ): - assert is_dark() is True + ), + ): + assert is_dark() is True def test_javascript_before_system(self) -> None: """Test that JS detection comes before system preferences.""" from easydiffraction.utils._vendored.theme_detect import is_dark - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_jupyterlab_settings', - return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_vscode_settings', + with ( + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_jupyterlab_settings', + return_value=None, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_vscode_settings', return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_javascript_detection', - return_value=True, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_system_preferences', - return_value=False, - ): - # JS detection should win over system prefs - assert is_dark() is True + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_javascript_detection', + return_value=True, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_system_preferences', + return_value=False, + ), + ): + # JS detection should win over system prefs + assert is_dark() is True class TestGetDetectionResult: @@ -284,37 +257,35 @@ def test_returns_dict_with_all_methods(self) -> None: """Test that get_detection_result returns all detection methods.""" from easydiffraction.utils._vendored.theme_detect import get_detection_result - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_jupyterlab_settings', - return_value=True, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_vscode_settings', + with ( + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_jupyterlab_settings', + return_value=True, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_vscode_settings', + return_value=None, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_javascript_detection', return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_javascript_detection', - return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_system_preferences', - return_value=False, - ): - result = get_detection_result() - - assert 'jupyterlab_settings' in result - assert 'vscode_settings' in result - assert 'javascript_dom' in result - assert 'system_preferences' in result - - assert result['jupyterlab_settings'] is True - assert result['vscode_settings'] is None - assert result['javascript_dom'] is None - assert result['system_preferences'] is False + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_system_preferences', + return_value=False, + ), + ): + result = get_detection_result() + + assert 'jupyterlab_settings' in result + assert 'vscode_settings' in result + assert 'javascript_dom' in result + assert 'system_preferences' in result + + assert result['jupyterlab_settings'] is True + assert result['vscode_settings'] is None + assert result['javascript_dom'] is None + assert result['system_preferences'] is False class TestImports: diff --git a/tests/unit/easydiffraction/utils/test_utils.py b/tests/unit/easydiffraction/utils/test_utils.py index 183178bd..48d5a182 100644 --- a/tests/unit/easydiffraction/utils/test_utils.py +++ b/tests/unit/easydiffraction/utils/test_utils.py @@ -1,11 +1,10 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np import pytest - def test_module_import(): import easydiffraction.utils.utils as MUT @@ -129,6 +128,7 @@ def test_is_pycharm_and_is_colab(monkeypatch): def test_render_table_terminal_branch(capsys, monkeypatch): import easydiffraction.utils.utils as MUT + # Ensure non-notebook rendering; on CI/default env it's terminal anyway. MUT.render_table( columns_data=[[1, 2], [3, 4]], @@ -354,7 +354,6 @@ def __exit__(self, *args): def test_resolve_tutorial_url(): - import easydiffraction.utils.utils as MUT # Test with a specific version url_template = 'https://example.com/{version}/tutorials/ed-1/ed-1.ipynb' @@ -362,4 +361,3 @@ def test_resolve_tutorial_url(): # So we just test that the function exists and replaces {version} result = url_template.replace('{version}', '0.8.0') assert result == 'https://example.com/0.8.0/tutorials/ed-1/ed-1.ipynb' - diff --git a/tmp/show_d401.py b/tmp/show_d401.py new file mode 100644 index 00000000..8ac806fc --- /dev/null +++ b/tmp/show_d401.py @@ -0,0 +1,26 @@ +files_lines = [ + ('src/easydiffraction/analysis/calculators/crysfml.py', [108,160,205]), + ('src/easydiffraction/analysis/calculators/cryspy.py', [357,377]), + ('src/easydiffraction/analysis/calculators/pdffit.py', [61]), + ('src/easydiffraction/analysis/minimizers/dfols.py', [55,77]), + ('src/easydiffraction/analysis/minimizers/lmfit.py', [40,66,96,117,140]), + ('src/easydiffraction/core/singleton.py', [28,45,49,65,107,116,138]), + ('src/easydiffraction/core/variable.py', [386]), + ('src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py', [312,317,329,334,339,344,494,567]), + ('src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py', [268,286,291,301,308,313,318]), + ('src/easydiffraction/datablocks/experiment/categories/data/total_pd.py', [195,200,212,217,340]), + ('src/easydiffraction/project/project_info.py', [119]), + ('src/easydiffraction/utils/environment.py', [35,47]), + ('src/easydiffraction/utils/logging.py', [685]), + ('src/easydiffraction/utils/utils.py', [308]), +] +for fname, lines in files_lines: + with open(fname) as f: + content = f.readlines() + for ln in lines: + lo = max(0, ln-2) + hi = min(len(content), ln+2) + for i, l in enumerate(content[lo:hi]): + print(f'{fname}:{lo+i+1}: {l}', end='') + print('---') + diff --git a/tmp/show_w505.py b/tmp/show_w505.py new file mode 100644 index 00000000..6e606803 --- /dev/null +++ b/tmp/show_w505.py @@ -0,0 +1,27 @@ +files_lines = [ + ('src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py', [196,277,324,527,602]), + ('src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py', [29,298,422]), + ('src/easydiffraction/datablocks/experiment/categories/data/total_pd.py', [207]), + ('src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py', [30]), + ('src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py', [20]), + ('src/easydiffraction/datablocks/experiment/item/base.py', [71,604]), + ('src/easydiffraction/datablocks/experiment/item/factory.py', [78]), + ('src/easydiffraction/datablocks/structure/item/base.py', [248]), + ('src/easydiffraction/display/base.py', [144]), + ('src/easydiffraction/display/utils.py', [21]), + ('src/easydiffraction/io/cif/serialize.py', [162]), + ('src/easydiffraction/project/project.py', [78]), + ('src/easydiffraction/summary/summary.py', [208]), + ('src/easydiffraction/utils/logging.py', [327,478]), + ('src/easydiffraction/utils/utils.py', [52,73]), +] +for fname, lines in files_lines: + with open(fname) as f: + content = f.readlines() + for ln in lines: + lo = max(0, ln-2) + hi = min(len(content), ln+1) + for i, l in enumerate(content[lo:hi]): + print(f'{fname}:{lo+i+1}: {l}', end='') + print('---') + diff --git a/tools/add_assets_to_docs.sh b/tools/add_assets_to_docs.sh deleted file mode 100755 index e13eb40a..00000000 --- a/tools/add_assets_to_docs.sh +++ /dev/null @@ -1,16 +0,0 @@ -echo "📥 Add files from ../assets-docs" -cp -R ../assets-docs/docs/assets/ docs/assets/ -cp -R ../assets-docs/includes/ includes/ -cp -R ../assets-docs/overrides/ overrides/ - -echo "📥 Add files from ../assets-branding" -mkdir -p docs/assets/images/ -cp ../assets-branding/easydiffraction/hero/dark.png docs/assets/images/hero_dark.png -cp ../assets-branding/easydiffraction/hero/light.png docs/assets/images/hero_light.png -cp ../assets-branding/easydiffraction/logos/dark.svg docs/assets/images/logo_dark.svg -cp ../assets-branding/easydiffraction/logos/light.svg docs/assets/images/logo_light.svg -cp ../assets-branding/easydiffraction/icons/color.png docs/assets/images/favicon.png - -mkdir -p overrides/.icons/ -cp ../assets-branding/easydiffraction/icons/bw.svg overrides/.icons/easydiffraction.svg -cp ../assets-branding/easyscience-org/icons/eso-icon_bw.svg overrides/.icons/easyscience.svg diff --git a/tools/cleanup_docs.sh b/tools/cleanup_docs.sh deleted file mode 100755 index 757ec3e4..00000000 --- a/tools/cleanup_docs.sh +++ /dev/null @@ -1,10 +0,0 @@ -echo "🧹 Clean up after building documentation" -rm -rf site/ -rm -rf docs/assets/javascripts -rm -rf docs/assets/stylesheets -rm -rf docs/assets/images/*.png -rm -rf docs/assets/images/*.svg -rm -rf includes/ -rm -rf overrides/ -rm -rf docs/tutorials/*.ipynb -rm mkdocs.yml diff --git a/tools/convert_google_docstrings_to_numpy.py b/tools/convert_google_docstrings_to_numpy.py new file mode 100644 index 00000000..5fcb14e2 --- /dev/null +++ b/tools/convert_google_docstrings_to_numpy.py @@ -0,0 +1,539 @@ +#!/usr/bin/env python3 +"""Convert Google-style Python docstrings to numpydoc style.""" + +from __future__ import annotations + +import ast +import inspect +import re +import sys +import textwrap +from pathlib import Path + +from docstring_parser import DocstringStyle +from docstring_parser import compose +from docstring_parser import parse +from format_docstring.docstring_rewriter import calc_abs_pos +from format_docstring.docstring_rewriter import calc_line_starts +from format_docstring.docstring_rewriter import find_docstring +from format_docstring.docstring_rewriter import rebuild_literal + +SECTION_NAMES = ( + 'Args', + 'Arguments', + 'Returns', + 'Raises', + 'Yields', + 'Attributes', + 'Examples', + 'Notes', +) +GOOGLE_SECTION_RE = re.compile( + r'(?m)^(?P[ \t]*)(?P
' + + '|'.join(SECTION_NAMES) + + r'):\s*(?P\S.*)?$' +) +NUMPY_SECTION_RE = re.compile(r'(?m)^[^\n]+\n-+\n') +SECTION_KINDS_WITH_ITEMS = {'Args', 'Arguments', 'Attributes'} +PRESERVE_BLOCK_SECTIONS = {'Examples', 'Notes'} +GENERIC_ITEM_SECTIONS = {'Raises', 'Returns', 'Yields'} +GENERIC_ITEM_RE = re.compile( + r'(?[A-Za-z_][A-Za-z0-9_\.\[\], \|\(\)]{0,80}?)\s*:' +) + + +def _iter_python_files(paths: list[Path]) -> list[Path]: + files: list[Path] = [] + for path in paths: + if path.is_file() and path.suffix == '.py': + files.append(path) + continue + + if not path.exists(): + continue + + for file_path in sorted(path.rglob('*.py')): + if '_vendored' in file_path.parts: + continue + if '.pixi' in file_path.parts: + continue + files.append(file_path) + + return files + + +def _collect_names(node: ast.AST) -> list[str]: + names: list[str] = [] + + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + args = list(node.args.posonlyargs) + list(node.args.args) + args += list(node.args.kwonlyargs) + names.extend(arg.arg for arg in args) + if node.args.vararg is not None: + names.append(node.args.vararg.arg) + if node.args.kwarg is not None: + names.append(node.args.kwarg.arg) + return [name for name in names if name not in {'self', 'cls'}] + + if isinstance(node, ast.ClassDef): + init_method = next( + ( + stmt + for stmt in node.body + if isinstance(stmt, ast.FunctionDef) and stmt.name == '__init__' + ), + None, + ) + if init_method is not None: + names.extend(_collect_names(init_method)) + + for stmt in node.body: + if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name): + names.append(stmt.target.id) + elif isinstance(stmt, ast.Assign): + for target in stmt.targets: + if isinstance(target, ast.Name): + names.append(target.id) + + return list(dict.fromkeys(names)) + + +def _strip_blank_edges(lines: list[str]) -> list[str]: + start = 0 + end = len(lines) + while start < end and not lines[start].strip(): + start += 1 + while end > start and not lines[end - 1].strip(): + end -= 1 + return lines[start:end] + + +def _join_wrapped_lines(lines: list[str]) -> str: + parts: list[str] = [] + for line in lines: + text = re.sub(r'\s+', ' ', line.strip()) + if not text: + continue + if parts and parts[-1].endswith('-') and not parts[-1].endswith(' -'): + parts[-1] = parts[-1][:-1] + text + else: + parts.append(text) + return ' '.join(parts) + + +def _collapse_whitespace(lines: list[str]) -> str: + return _join_wrapped_lines(lines) + + +def _repair_named_items(block_lines: list[str], names: list[str]) -> list[str] | None: + flat = _collapse_whitespace(block_lines) + if not flat or not names: + return None + + label_pattern = '|'.join(re.escape(name) for name in sorted(set(names), key=len, reverse=True)) + item_re = re.compile( + rf'(?\*{{0,2}}(?:{label_pattern})(?:\s*\([^)]*\))?)\s*:' + ) + matches = list(item_re.finditer(flat)) + if not matches or matches[0].start() != 0: + return None + + repaired: list[str] = [] + for index, match in enumerate(matches): + start = match.end() + end = matches[index + 1].start() if index + 1 < len(matches) else len(flat) + description = flat[start:end].strip() + repaired.append(f' {match.group("label")}: {description}' if description else f' {match.group("label")}:') + return repaired + + +def _repair_generic_items(block_lines: list[str]) -> list[str] | None: + flat = _collapse_whitespace(block_lines) + if not flat: + return None + + matches = list(GENERIC_ITEM_RE.finditer(flat)) + if not matches or matches[0].start() != 0: + return None + + repaired: list[str] = [] + for index, match in enumerate(matches): + start = match.end() + end = matches[index + 1].start() if index + 1 < len(matches) else len(flat) + description = flat[start:end].strip() + repaired.append(f' {match.group("label")}: {description}' if description else f' {match.group("label")}:') + return repaired + + +def _repair_section(section: str, block_lines: list[str], names: list[str]) -> list[str]: + stripped = _strip_blank_edges(block_lines) + if not stripped: + return [] + + if section in SECTION_KINDS_WITH_ITEMS: + flat = _collapse_whitespace(stripped).lower().rstrip('.') + if flat == 'none': + return [] + repaired = _repair_named_items(stripped, names) + if repaired is not None: + return repaired + + if section in GENERIC_ITEM_SECTIONS: + repaired = _repair_generic_items(stripped) + if repaired is not None: + return repaired + + if section in PRESERVE_BLOCK_SECTIONS: + return [f' {line}' if line else '' for line in stripped] + + flat = _collapse_whitespace(stripped) + return [f' {flat}'] if flat else [] + + +def _repair_inline_sections(docstring: str, names: list[str]) -> str: + cleaned = inspect.cleandoc(docstring.replace('\r\n', '\n')) + lines = cleaned.split('\n') + out: list[str] = [] + index = 0 + + while index < len(lines): + raw_line = lines[index] + heading = GOOGLE_SECTION_RE.match(raw_line) + if heading is None: + out.append(raw_line.rstrip()) + index += 1 + continue + + section = heading.group('section') + section_name = 'Args' if section == 'Arguments' else section + out.append(f'{section_name}:') + + block_lines: list[str] = [] + rest = heading.group('rest') + if rest: + block_lines.append(rest) + + index += 1 + while index < len(lines): + next_line = lines[index] + if GOOGLE_SECTION_RE.match(next_line): + break + if ( + section_name not in PRESERVE_BLOCK_SECTIONS + and not next_line.strip() + and index + 1 < len(lines) + and lines[index + 1].strip() + and GOOGLE_SECTION_RE.match(lines[index + 1]) is None + ): + break + block_lines.append(next_line.rstrip()) + index += 1 + + out.extend(_repair_section(section_name, block_lines, names)) + + return '\n'.join(out) + + +def _looks_google(docstring: str) -> bool: + return bool(GOOGLE_SECTION_RE.search(docstring)) + + +def _looks_numpydoc(docstring: str) -> bool: + return bool(NUMPY_SECTION_RE.search(docstring)) + + +def _meta_kinds(parsed) -> set[str]: + kinds: set[str] = set() + for meta in parsed.meta: + args = getattr(meta, 'args', None) or [] + if not args: + continue + kinds.add(str(args[0]).lower()) + return kinds + + +def _contains_unparsed_sections(parsed) -> bool: + for text in (parsed.short_description, parsed.long_description): + if text and GOOGLE_SECTION_RE.search(text): + return True + return False + + +def _has_section_heading(docstring: str, section: str) -> bool: + return re.search(rf'(?m)^[ \t]*{re.escape(section)}:\s*(?:\S.*)?$', docstring) is not None + + +def _is_safe_conversion(docstring: str, parsed) -> bool: + if '::' in docstring: + return False + + kinds = _meta_kinds(parsed) + if _contains_unparsed_sections(parsed): + return False + + expectations = { + 'Args': 'param', + 'Arguments': 'param', + 'Attributes': 'attribute', + 'Returns': 'returns', + 'Raises': 'raises', + 'Yields': 'yields', + 'Examples': 'examples', + } + for section, expected_kind in expectations.items(): + if _has_section_heading(docstring, section) and expected_kind not in kinds: + return False + + return True + + +def _is_section_header(lines: list[str], index: int) -> bool: + return index + 1 < len(lines) and bool(lines[index].strip()) and set(lines[index + 1].strip()) == {'-'} + + +def _wrap_paragraph(lines: list[str], width: int, indent: str = '') -> list[str]: + if not lines: + return [] + + text = _join_wrapped_lines(lines) + if not text: + return [''] if lines else [] + + return textwrap.wrap( + text, + width=width, + initial_indent=indent, + subsequent_indent=indent, + break_long_words=False, + break_on_hyphens=False, + ) + + +def _format_freeform_block(lines: list[str], width: int = 72, indent: str = '') -> list[str]: + stripped = _strip_blank_edges(lines) + if not stripped: + return [] + + formatted: list[str] = [] + paragraph: list[str] = [] + for line in stripped: + if not line.strip(): + if paragraph: + formatted.extend(_wrap_paragraph(paragraph, width=width, indent=indent)) + paragraph = [] + if formatted and formatted[-1] != '': + formatted.append('') + continue + + content = line.strip() + if content.startswith(('>>>', '...')): + if paragraph: + formatted.extend(_wrap_paragraph(paragraph, width=width, indent=indent)) + paragraph = [] + formatted.append(f'{indent}{content}') + continue + + paragraph.append(content) + + if paragraph: + formatted.extend(_wrap_paragraph(paragraph, width=width, indent=indent)) + + return formatted + + +def _format_named_section(block_lines: list[str]) -> list[str]: + lines = _strip_blank_edges(block_lines) + if not lines: + return [] + + formatted: list[str] = [] + index = 0 + while index < len(lines): + if not lines[index].strip(): + index += 1 + continue + + header = lines[index].strip() + formatted.append(header) + index += 1 + + description: list[str] = [] + while index < len(lines): + line = lines[index] + if not line.strip(): + index += 1 + if description: + break + continue + if not line.startswith(' ') and not line.startswith('\t'): + break + description.append(line.strip()) + index += 1 + + if description: + formatted.extend(_wrap_paragraph(description, width=68, indent=' ')) + elif formatted and formatted[-1] != '': + formatted.append('') + + if formatted and formatted[-1] == '': + formatted.pop() + return formatted + + +def _format_return_like_section(block_lines: list[str]) -> list[str]: + lines = _strip_blank_edges(block_lines) + if not lines: + return [] + + first = next((line for line in lines if line.strip()), '') + if first.startswith((' ', '\t')): + return _format_freeform_block(lines, width=68, indent=' ') + + return _format_named_section(lines) + + +def _format_numpydoc_output(docstring: str) -> str: + lines = docstring.strip('\n').splitlines() + formatted: list[str] = [] + index = 0 + + preamble: list[str] = [] + while index < len(lines) and not _is_section_header(lines, index): + preamble.append(lines[index]) + index += 1 + formatted.extend(_format_freeform_block(preamble)) + + while index < len(lines): + if not _is_section_header(lines, index): + index += 1 + continue + + if formatted and formatted[-1] != '': + formatted.append('') + heading = lines[index].strip() + underline = lines[index + 1].strip() + formatted.extend([heading, underline]) + index += 2 + + block: list[str] = [] + while index < len(lines) and not _is_section_header(lines, index): + block.append(lines[index]) + index += 1 + + if heading in {'Parameters', 'Attributes'}: + formatted.extend(_format_named_section(block)) + elif heading in {'Returns', 'Raises', 'Yields'}: + formatted.extend(_format_return_like_section(block)) + else: + formatted.extend(_format_freeform_block(block)) + + return '\n'.join(_strip_blank_edges(formatted)) + + +def _convert_docstring(docstring: str, names: list[str]) -> str | None: + cleaned = inspect.cleandoc(docstring) + if not _looks_google(cleaned): + return None + + repaired = _repair_inline_sections(cleaned, names) + + try: + parsed = parse(repaired, style=DocstringStyle.GOOGLE) + except Exception: + return None + + if not _is_safe_conversion(repaired, parsed): + return None + + converted = _format_numpydoc_output(compose(parsed, style=DocstringStyle.NUMPYDOC)) + return converted if converted != cleaned else None + + +def _reformat_numpydoc_docstring(docstring: str) -> str | None: + cleaned = inspect.cleandoc(docstring) + if not _looks_numpydoc(cleaned): + return None + + formatted = _format_numpydoc_output(cleaned) + return formatted if formatted != cleaned else None + + +def _format_multiline_docstring(content: str, indent: int) -> str: + indent_str = ' ' * indent + lines = content.strip('\n').splitlines() + body = '\n'.join(f'{indent_str}{line}' if line else '' for line in lines) + return f'\n{body}\n{indent_str}' + + +def _convert_file(path: Path) -> bool: + source_code = path.read_text() + tree = ast.parse(source_code, type_comments=True) + line_starts = calc_line_starts(source_code) + replacements: list[tuple[int, int, str]] = [] + + nodes: list[ast.AST] = [tree] + nodes.extend(ast.walk(tree)) + + for node in nodes: + if not isinstance(node, (ast.Module, ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): + continue + + docstring_obj = find_docstring(node) + if docstring_obj is None: + continue + + value = docstring_obj.value + end_lineno = getattr(value, 'end_lineno', None) + end_col_offset = getattr(value, 'end_col_offset', None) + if end_lineno is None or end_col_offset is None: + continue + + docstring = ast.get_docstring(node, clean=False) + if docstring is None: + continue + + converted = _convert_docstring(docstring, _collect_names(node)) + if converted is None: + converted = _reformat_numpydoc_docstring(docstring) + if converted is None: + continue + + start = calc_abs_pos(source_code, line_starts, value.lineno, value.col_offset) + end = calc_abs_pos(source_code, line_starts, end_lineno, end_col_offset) + original_literal = source_code[start:end] + leading_indent = getattr(value, 'col_offset', 0) + formatted = _format_multiline_docstring(converted, leading_indent) + new_literal = rebuild_literal(original_literal, formatted) + if new_literal is None or new_literal == original_literal: + continue + + replacements.append((start, end, new_literal)) + + if not replacements: + return False + + replacements.sort(reverse=True) + new_source = source_code + for start, end, replacement in replacements: + new_source = new_source[:start] + replacement + new_source[end:] + + compile(new_source, str(path), 'exec') + path.write_text(new_source) + return True + + +def main(argv: list[str]) -> int: + input_paths = [Path(arg) for arg in argv] if argv else [Path('src'), Path('tools')] + changed = 0 + + for path in _iter_python_files(input_paths): + if _convert_file(path): + changed += 1 + print(f'Converted {path}') + + print(f'Converted docstrings in {changed} file(s).') + return 0 + + +if __name__ == '__main__': + raise SystemExit(main(sys.argv[1:])) diff --git a/tools/create_mkdocs_yml.py b/tools/create_mkdocs_yml.py deleted file mode 100644 index fb2b8685..00000000 --- a/tools/create_mkdocs_yml.py +++ /dev/null @@ -1,158 +0,0 @@ -import os -import re -from pathlib import Path -from typing import Any -from typing import Dict -from typing import List - -import material.extensions.emoji # side-effect: register emoji tag -import pymdownx.superfences # side-effect: register superfence tag -import yaml - -# Side-effect imports above ensure tagged YAML constructors -# (e.g., !!python/name:...) can be resolved during load. - - -def _activate_yaml_tag_side_effects() -> None: # pragma: no cover - trivial - """Access imported modules' attributes so Ruff sees them as used. - - The primary purpose of importing these packages is to ensure the - tagged constructors are importable during YAML load. Accessing the - attributes makes the side-effect explicit without needing noqa. - """ - _ = material.extensions.emoji.twemoji # type: ignore[attr-defined] - _ = pymdownx.superfences.fence_code_format # type: ignore[attr-defined] - - -_activate_yaml_tag_side_effects() - - -def load_yaml_with_env_variables(file_path: str) -> Dict[str, Any]: - """Load YAML resolving env variables declared as !ENV ${VAR_NAME}. - - Args: - file_path (str): Path to the YAML file. - - Returns: - dict: Parsed YAML content with environment variables replaced. - """ - tag = '!ENV' - pattern = re.compile(r'.*?\${([A-Z0-9_]+)}.*?') - - def constructor_env_variables(loader, node): # type: ignore[all] - """YAML constructor replacing !ENV markers with values.""" - value = loader.construct_scalar(node) # type: ignore[attr-defined] - for var in pattern.findall(value): - value = value.replace(f'${{{var}}}', os.environ.get(var, var)) - return value - - loader = yaml.FullLoader - loader.add_implicit_resolver(tag, pattern, None) - loader.add_constructor(tag, constructor_env_variables) - - with Path(file_path).open('r', encoding='utf-8') as fh: - return yaml.full_load(fh) - - -def merge_yaml(base_config: Dict[str, Any], override_config: Dict[str, Any]) -> Dict[str, Any]: - """Deep merge two YAML dicts; override has priority.""" - if not isinstance(base_config, dict): - return override_config - - merged_config = base_config.copy() - - for key, override_value in override_config.items(): - if key in merged_config: - base_value = merged_config[key] - if isinstance(base_value, dict) and isinstance(override_value, dict): - merged_config[key] = merge_yaml(base_value, override_value) - elif isinstance(base_value, list) and isinstance(override_value, list): - merged_config[key] = merge_lists(base_value, override_value) - else: - merged_config[key] = override_value - else: - merged_config[key] = override_value - - return merged_config - - -def merge_lists(base_list: List[Any], override_list: List[Any]) -> List[Any]: - """Merge two lists with handling of single-key dict items. - - Single-key dicts sharing a key are deep-merged; other items are - appended if not already present. - - Args: - base_list (list): The base list. - override_list (list): The overriding list. - - Returns: - list: The merged list. - """ - merged_list = [] - seen_items = {} - - for item in base_list + override_list: - if isinstance(item, dict) and len(item) == 1: - key = next(iter(item)) # Extract dictionary key (e.g., "mkdocs-jupyter") - if key in seen_items: - seen_items[key] = merge_yaml(seen_items[key], item[key]) # Merge dictionaries - else: - seen_items[key] = item[key] - elif item not in merged_list: - merged_list.append(item) - - # Convert merged dictionary values back into list format - for key, value in seen_items.items(): - merged_list.append({key: value}) - - return merged_list - - -def save_yaml(data: Dict[str, Any], output_file: str) -> None: - """Write YAML preserving !!python/name tags and Unicode.""" - - class CustomDumper(yaml.Dumper): - """Custom dumper avoiding unnecessary anchors & quotes.""" - - def ignore_aliases(self, _): # noqa: D401 - simple override - return True - - out_path = Path(output_file) - with out_path.open('w', encoding='utf-8') as f: - f.write('# WARNING: This file is auto-generated during the build process.\n') - f.write('# DO NOT EDIT THIS FILE MANUALLY.\n') - f.write('# It is created by merging:\n') - f.write('# - Generic YAML file: ../assets-docs/mkdocs.yml\n') - f.write('# - Project specific YAML file: docs/mkdocs.yml\n\n') - - with out_path.open('a', encoding='utf-8') as f: - yaml.dump( - data, - f, - Dumper=CustomDumper, # Use custom dumper - allow_unicode=True, # Ensure Unicode characters like © are preserved - default_flow_style=False, # - sort_keys=False, # Preserve the order of keys - ) - - -def main() -> None: - """Main function to read, merge, and save YAML configurations.""" - generic_config_path = '../assets-docs/mkdocs.yml' - specific_config_path = 'docs/mkdocs.yml' - output_path = 'mkdocs.yml' - - print(f'Reading generic config: {generic_config_path}') - base_config = load_yaml_with_env_variables(generic_config_path) - - print(f'Reading project specific config: {specific_config_path}') - override_config = load_yaml_with_env_variables(specific_config_path) - - print(f'Saving merged config: {output_path}') - merged_config = merge_yaml(base_config, override_config) - save_yaml(merged_config, output_path) - - -if __name__ == '__main__': - main() diff --git a/tools/license_headers.py b/tools/license_headers.py new file mode 100644 index 00000000..47d23524 --- /dev/null +++ b/tools/license_headers.py @@ -0,0 +1,315 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Add, remove, or check SPDX headers in Python files.""" + +from __future__ import annotations + +import argparse +import fnmatch +import tomllib +from datetime import datetime +from pathlib import Path +from typing import Any +from typing import Optional +from typing import Union + +from git import Repo +from spdx_headers.core import find_repository_root +from spdx_headers.core import get_copyright_info +from spdx_headers.core import has_spdx_header +from spdx_headers.data import load_license_data +from spdx_headers.operations import add_header_to_single_file +from spdx_headers.operations import remove_header_from_single_file + +LICENSE_DATABASE = load_license_data() + + +def load_pyproject(repo_path: Union[str, Path]) -> dict[str, Any]: + """Load and return parsed ``pyproject.toml`` data for the repository.""" + repo_root = find_repository_root(repo_path) + pyproject_path = repo_root / 'pyproject.toml' + + with pyproject_path.open('rb') as file_handle: + return tomllib.load(file_handle) + + +def get_pyproject_value(pyproject_data: dict[str, Any], dotted_key: str) -> Any: + """Return a nested ``pyproject.toml`` value from a dotted key.""" + value: Any = pyproject_data + for part in dotted_key.split('.'): + if not isinstance(value, dict) or part not in value: + raise KeyError(dotted_key) + value = value[part] + return value + + +def normalize_pattern(pattern: str) -> str: + """Normalize an exclude pattern to a POSIX-style relative path.""" + normalized = Path(pattern).as_posix() + if normalized.startswith('./'): + normalized = normalized[2:] + return normalized.rstrip('/') + + +def get_exclude_patterns( + repo_path: Union[str, Path], + exclude_values: list[str], + exclude_from_pyproject_toml: Optional[str], +) -> list[str]: + """Return normalized exclude patterns from CLI and ``pyproject.toml``.""" + pyproject_data = load_pyproject(repo_path) + patterns: list[str] = [] + + if exclude_from_pyproject_toml: + value = get_pyproject_value(pyproject_data, exclude_from_pyproject_toml) + if not isinstance(value, list) or not all(isinstance(item, str) for item in value): + raise ValueError( + f'{exclude_from_pyproject_toml} in pyproject.toml must be a list of strings.', + ) + patterns.extend(value) + + for item in exclude_values: + try: + value = get_pyproject_value(pyproject_data, item) + except KeyError: + patterns.append(item) + continue + + if not isinstance(value, list) or not all(isinstance(entry, str) for entry in value): + raise ValueError(f'{item} in pyproject.toml must be a list of strings.') + patterns.extend(value) + + normalized_patterns: list[str] = [] + seen: set[str] = set() + for pattern in patterns: + normalized = normalize_pattern(pattern) + if normalized and normalized not in seen: + normalized_patterns.append(normalized) + seen.add(normalized) + + return normalized_patterns + + +def get_file_creation_year(file_path: Union[str, Path]) -> str: + """Return the year the file was first added to Git history. + + If the year cannot be determined, fall back to the current year. + """ + file_path = Path(file_path) + + repo = Repo(file_path, search_parent_directories=True) + root = Path(repo.working_tree_dir).resolve() + rel_path = file_path.resolve().relative_to(root) + + rel_path_git = rel_path.as_posix() + + log_output = repo.git.log( + '--follow', + '--diff-filter=A', + '--reverse', + '--format=%ad', + '--date=format:%Y', + '--', + rel_path_git, + ).strip() + + year = log_output.splitlines()[0].strip() if log_output else '' + + return year or str(datetime.now().year) + + +def get_org_url(repo_path: Union[str, Path]) -> str: + """Return the organization URL derived from the repository source URL.""" + pyproject_data = load_pyproject(repo_path) + repo_url = pyproject_data['project']['urls']['Source Code'] + return repo_url.rsplit('/', 1)[0] + + +def get_project_license(repo_path: Union[str, Path]) -> str: + """Return the project license value from ``pyproject.toml``.""" + pyproject_data = load_pyproject(repo_path) + return pyproject_data['project']['license'] + + +def get_copyright_holder(repo_path: Union[str, Path]) -> str: + """Return the repository copyright holder name.""" + _, name, _ = get_copyright_info(repo_path) + return name + + +def add_spdx_header( + target_file: Union[str, Path], + *, + license_key: str, + copyright_holder: str, + org_url: str, +) -> None: + """Add SPDX headers to one file.""" + year = get_file_creation_year(target_file) + + add_header_to_single_file( + filepath=target_file, + license_key=license_key, + license_data=LICENSE_DATABASE, + year=year, + name=copyright_holder, + email=org_url, + ) + + +def is_excluded(relative_path: str, exclude_patterns: list[str]) -> bool: + """Return whether a relative path should be excluded.""" + for pattern in exclude_patterns: + if fnmatch.fnmatch(relative_path, pattern): + return True + if relative_path == pattern: + return True + if relative_path.startswith(f'{pattern}/'): + return True + return False + + +def iter_python_files( + paths: list[str], + *, + repo_root: Path, + exclude_patterns: list[str], + parser: argparse.ArgumentParser, +) -> list[Path]: + """Collect Python files under the given paths after exclusions.""" + files: list[Path] = [] + seen: set[Path] = set() + + for base_dir in paths: + base_path = Path(base_dir) + if not base_path.exists(): + parser.error(f'Path does not exist: {base_dir}') + + if base_path.is_file(): + candidates = [base_path] if base_path.suffix == '.py' else [] + else: + candidates = sorted(base_path.rglob('*.py')) + + for py_file in candidates: + resolved = py_file.resolve() + try: + relative_path = resolved.relative_to(repo_root).as_posix() + except ValueError: + relative_path = py_file.as_posix() + + if is_excluded(relative_path, exclude_patterns): + continue + + if resolved not in seen: + files.append(py_file) + seen.add(resolved) + + return files + + +def run_add( + files: list[Path], + *, + license_key: str, + copyright_holder: str, + org_url: str, +) -> int: + """Add SPDX headers to all selected files.""" + for py_file in files: + add_spdx_header( + py_file, + license_key=license_key, + copyright_holder=copyright_holder, + org_url=org_url, + ) + return 0 + + +def run_remove(files: list[Path]) -> int: + """Remove SPDX headers from all selected files.""" + for py_file in files: + remove_header_from_single_file(py_file) + return 0 + + +def run_check(files: list[Path]) -> int: + """Check SPDX headers in all selected files.""" + missing_files = [py_file for py_file in files if not has_spdx_header(py_file)] + + if not missing_files: + print('✓ All Python files have valid SPDX headers.') + return 0 + + print('✗ The following files are missing SPDX headers:') + for py_file in missing_files: + print(f' - {py_file.as_posix()}') + print(f'\nFound {len(missing_files)} files without SPDX headers.') + return 1 + + +def build_parser() -> argparse.ArgumentParser: + """Build the CLI argument parser.""" + parser = argparse.ArgumentParser( + description='Add, remove, or check SPDX headers in Python files.', + ) + subparsers = parser.add_subparsers(dest='command', required=True) + + for command_name in ('check', 'remove', 'add'): + command_parser = subparsers.add_parser(command_name) + command_parser.add_argument( + 'paths', + nargs='+', + help='Relative paths to scan (e.g. src tests)', + ) + command_parser.add_argument( + '--exclude', + nargs='*', + default=[], + help='Exclude paths, glob patterns, or pyproject dotted keys.', + ) + command_parser.add_argument( + '--exclude-from-pyproject-toml', + help='Read exclude patterns from a dotted key in pyproject.toml.', + ) + + return parser + + +def main(argv: Optional[list[str]] = None) -> int: + """Run the SPDX header CLI.""" + parser = build_parser() + args = parser.parse_args(argv) + + repo_path = Path('.').resolve() + repo_root = find_repository_root(repo_path).resolve() + exclude_patterns = get_exclude_patterns( + repo_path, + args.exclude, + args.exclude_from_pyproject_toml, + ) + files = iter_python_files( + args.paths, + repo_root=repo_root, + exclude_patterns=exclude_patterns, + parser=parser, + ) + + if args.command == 'check': + return run_check(files) + + if args.command == 'remove': + return run_remove(files) + + license_key = get_project_license(repo_path) + copyright_holder = get_copyright_holder(repo_path) + org_url = get_org_url(repo_path) + return run_add( + files, + license_key=license_key, + copyright_holder=copyright_holder, + org_url=org_url, + ) + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/tools/param_consistency.py b/tools/param_consistency.py new file mode 100644 index 00000000..cd63989c --- /dev/null +++ b/tools/param_consistency.py @@ -0,0 +1,677 @@ +"""Check and fix consistency between Parameter/Descriptor +definitions and their public property docstrings and type +annotations. + +Usage:: + + python param_consistency.py --check + python param_consistency.py --fix + python param_consistency.py src/mypackage/ --check + +Template (see architecture.md §9.8 for the full spec) +------------------------------------------------------ +Given ``description='Length of the a axis of the unit +cell.'``, ``units='Å'``, and type ``Parameter``: + +Writable:: + + @property + def length_a(self) -> Parameter: + \"""Length of the a axis of the unit cell (Å). + + Reading this property returns the underlying + ``Parameter`` object. Assigning to it updates + the parameter value. + \""" + return self._length_a + + @length_a.setter + def length_a(self, value: float) -> None: + self._length_a.value = value + +Read-only:: + + @property + def length_a(self) -> Parameter: + \"""Length of the a axis of the unit cell (Å). + + Reading this property returns the underlying + ``Parameter`` object. + \""" + return self._length_a + +Rules: + +- ``{desc}`` = description without trailing period + (single source of truth). +- ``{units}`` = units string; omit ``({units})`` when + absent or empty. +- Getter summary: ``{desc} ({units}).`` or ``{desc}.`` +- Getter body mentions the descriptor class and, for + writable properties, notes that assignment updates + the value. +- Setter has **no** docstring. +- Getter return annotation: the descriptor class name. +- Setter value annotation: ``float`` for numeric, + ``str`` for string. +- Setter return annotation: ``None``. + +Exit code 0 when all checks pass (or fix succeeds), +1 otherwise. +""" + +from __future__ import annotations + +import argparse +import ast +import sys +from dataclasses import dataclass +from dataclasses import field +from pathlib import Path + +# --------------------------------------------------------- +# Constants +# --------------------------------------------------------- + +_SRC_ROOT = ( + Path(__file__).resolve().parents[1] + / 'src' + / 'easydiffraction' +) + +_DESCRIPTOR_TYPES = frozenset( + {'Parameter', 'NumericDescriptor', 'StringDescriptor'} +) + +# Canonical setter value annotation per descriptor family. +_SETTER_ANN: dict[str, str] = { + 'Parameter': 'float', + 'NumericDescriptor': 'float', + 'StringDescriptor': 'str', +} + + +# --------------------------------------------------------- +# Data structures +# --------------------------------------------------------- + + +@dataclass +class DescriptorInfo: + """Descriptor definition from ``__init__``.""" + + attr_name: str # e.g. '_length_a' + prop_name: str # e.g. 'length_a' + type_name: str # 'Parameter' | … + description: str # e.g. 'Length of …' + units: str | None # e.g. 'Å', or None + + +@dataclass +class PropertyInfo: + """Property getter / setter AST nodes.""" + + name: str + getter: ast.FunctionDef + setter: ast.FunctionDef | None = None + + +@dataclass +class Edit: + """A source-level edit. + + Replace ``lines[start:end]`` with *new_text*. + When ``start == end`` the edit is an insertion + before that line. + """ + + start: int # 0-based inclusive + end: int # 0-based exclusive + new_text: str + + +@dataclass +class FileResult: + """Analysis result for one source file.""" + + path: Path + issues: list[str] = field(default_factory=list) + edits: list[Edit] = field(default_factory=list) + + +# --------------------------------------------------------- +# Template helpers +# --------------------------------------------------------- + + +def _strip_dot(s: str) -> str: + """Remove trailing period and whitespace.""" + s = s.rstrip() + if s.endswith('.'): + s = s[:-1].rstrip() + return s + + +def _getter_docstring( + desc: str, + units: str | None, + type_name: str, + has_setter: bool, + indent: str, +) -> str: + """Build the expected getter docstring.""" + d = _strip_dot(desc) + summary = f'{d} ({units}).' if units else f'{d}.' + + if has_setter: + body = ( + f'Reading this property returns the underlying ' + f'``{type_name}`` object. ' + f'Assigning to it updates the parameter value.' + ) + else: + body = ( + f'Reading this property returns the underlying ' + f'``{type_name}`` object.' + ) + + return ( + f'{indent}"""{summary}\n' + f'\n' + f'{indent}{body}\n' + f'{indent}"""\n' + ) + + +def _normalize(text: str) -> str: + """Collapse whitespace for comparison.""" + return ' '.join(text.split()).lower() + + +# --------------------------------------------------------- +# AST helpers +# --------------------------------------------------------- + + +def _call_name(node: ast.Call) -> str | None: + """Return the simple name of a Call's func.""" + if isinstance(node.func, ast.Name): + return node.func.id + if isinstance(node.func, ast.Attribute): + return node.func.attr + return None + + +def _kwarg_str( + call: ast.Call, + name: str, +) -> str | None: + """Extract a string keyword argument.""" + for kw in call.keywords: + if ( + kw.arg == name + and isinstance(kw.value, ast.Constant) + and isinstance(kw.value.value, str) + ): + return kw.value.value + return None + + +def _ann_str(ann: ast.expr | None) -> str | None: + """Return annotation as a source string.""" + if ann is None: + return None + if isinstance(ann, ast.Name): + return ann.id + if isinstance(ann, ast.Constant) and isinstance( + ann.value, str + ): + return ann.value # forward reference + return ast.unparse(ann) + + +def _body_indent( + func: ast.FunctionDef, + lines: list[str], +) -> str: + """Compute the indentation for the body.""" + def_line = lines[func.lineno - 1] + return ' ' * ( + len(def_line) - len(def_line.lstrip()) + 4 + ) + + +def _def_line_range( + func: ast.FunctionDef, + lines: list[str], +) -> tuple[int, int]: + """Return 0-based [start, end) of the def.""" + start = func.lineno - 1 + for i in range(start, min(start + 10, len(lines))): + if lines[i].rstrip().endswith(':'): + return start, i + 1 + if func.body and i + 1 >= func.body[0].lineno: + break + return start, start + 1 + + +def _docstring_range( + func: ast.FunctionDef, +) -> tuple[str | None, int, int]: + """Return (text, start_0, end_exclusive_0).""" + if not func.body: + return None, -1, -1 + first = func.body[0] + if ( + isinstance(first, ast.Expr) + and isinstance(first.value, ast.Constant) + and isinstance(first.value.value, str) + ): + # end_lineno is 1-based inclusive + return ( + first.value.value, + first.lineno - 1, + first.end_lineno, + ) + return None, -1, -1 + + +# --------------------------------------------------------- +# Extraction +# --------------------------------------------------------- + + +def _extract_descriptors( + cls: ast.ClassDef, +) -> dict[str, DescriptorInfo]: + """Find self._xxx = DescriptorType(...) in init.""" + result: dict[str, DescriptorInfo] = {} + + init = next( + ( + n + for n in cls.body + if isinstance(n, ast.FunctionDef) + and n.name == '__init__' + ), + None, + ) + if init is None: + return result + + for stmt in ast.walk(init): + if ( + isinstance(stmt, ast.Assign) + and len(stmt.targets) == 1 + ): + target = stmt.targets[0] + value = stmt.value + elif ( + isinstance(stmt, ast.AnnAssign) + and stmt.value is not None + ): + target = stmt.target + value = stmt.value + else: + continue + + # Target must be self._xxx + if not ( + isinstance(target, ast.Attribute) + and isinstance(target.value, ast.Name) + and target.value.id == 'self' + and target.attr.startswith('_') + ): + continue + + if not isinstance(value, ast.Call): + continue + + name = _call_name(value) + if name not in _DESCRIPTOR_TYPES: + continue + + desc_str = _kwarg_str(value, 'description') + if not desc_str or not _strip_dot(desc_str): + continue + + units = _kwarg_str(value, 'units') or None + prop = target.attr.lstrip('_') + result[prop] = DescriptorInfo( + target.attr, prop, name, desc_str, units + ) + + return result + + +def _extract_properties( + cls: ast.ClassDef, +) -> dict[str, PropertyInfo]: + """Find property getters and setters.""" + result: dict[str, PropertyInfo] = {} + + for item in cls.body: + if not isinstance(item, ast.FunctionDef): + continue + for dec in item.decorator_list: + # @property + if ( + isinstance(dec, ast.Name) + and dec.id == 'property' + ): + result[item.name] = PropertyInfo( + item.name, item + ) + break + # @xxx.setter + if ( + isinstance(dec, ast.Attribute) + and dec.attr == 'setter' + and isinstance(dec.value, ast.Name) + and dec.value.id in result + ): + result[dec.value.id].setter = item + break + + return result + + +# --------------------------------------------------------- +# Analysis (shared by --check and --fix) +# --------------------------------------------------------- + + +def _analyze_file(path: Path) -> FileResult: + """Analyze one file, return issues and edits.""" + result = FileResult(path) + try: + source = path.read_text(encoding='utf-8') + except Exception: # noqa: BLE001 + return result + + lines = source.splitlines(keepends=True) + + try: + tree = ast.parse(source, filename=str(path)) + except SyntaxError: + return result + + for node in tree.body: + if not isinstance(node, ast.ClassDef): + continue + + descriptors = _extract_descriptors(node) + properties = _extract_properties(node) + + for prop_name, prop in properties.items(): + if prop_name not in descriptors: + continue + desc = descriptors[prop_name] + _analyze_property( + node.name, prop, desc, lines, result + ) + + return result + + +def _analyze_property( + cls_name: str, + prop: PropertyInfo, + desc: DescriptorInfo, + lines: list[str], + result: FileResult, +) -> None: + """Check one property against the template.""" + loc = f'{cls_name}.{prop.name}' + indent = _body_indent(prop.getter, lines) + has_setter = prop.setter is not None + + # --- Getter return annotation --- + actual_ret = _ann_str(prop.getter.returns) + expected_ret = desc.type_name + if actual_ret != expected_ret: + result.issues.append( + f'{loc}: getter annotation ' + f'-> {actual_ret} (expected {expected_ret})' + ) + ds, de = _def_line_range(prop.getter, lines) + def_indent = lines[ds][ + : len(lines[ds]) - len(lines[ds].lstrip()) + ] + new_def = ( + f'{def_indent}def {prop.name}' + f'(self) -> {expected_ret}:\n' + ) + result.edits.append(Edit(ds, de, new_def)) + + # --- Getter docstring --- + expected_doc = _getter_docstring( + desc.description, + desc.units, + desc.type_name, + has_setter, + indent, + ) + actual_doc_text, doc_s, doc_e = _docstring_range( + prop.getter + ) + + if actual_doc_text is None: + result.issues.append( + f'{loc}: getter missing docstring' + ) + _, def_end = _def_line_range(prop.getter, lines) + result.edits.append( + Edit(def_end, def_end, expected_doc) + ) + else: + actual_src = ''.join(lines[doc_s:doc_e]) + if _normalize(actual_src) != _normalize( + expected_doc + ): + result.issues.append( + f'{loc}: getter docstring ' + 'does not match template' + ) + result.edits.append( + Edit(doc_s, doc_e, expected_doc) + ) + + # --- Setter --- + if prop.setter is None: + return + + # Setter def-line annotations + setter_args = prop.setter.args.args + setter_param = ( + setter_args[1].arg + if len(setter_args) >= 2 + else 'value' + ) + expected_ann = _SETTER_ANN[desc.type_name] + + actual_val_ann = None + if ( + len(setter_args) >= 2 + and setter_args[1].annotation + ): + actual_val_ann = _ann_str( + setter_args[1].annotation + ) + + actual_ret_ann = _ann_str(prop.setter.returns) + + if ( + actual_val_ann != expected_ann + or actual_ret_ann != 'None' + ): + parts: list[str] = [] + if actual_val_ann != expected_ann: + parts.append( + f'value: {actual_val_ann} ' + f'(expected {expected_ann})' + ) + if actual_ret_ann != 'None': + parts.append( + f'return: {actual_ret_ann} ' + '(expected None)' + ) + result.issues.append( + f'{loc}: setter annotation ' + f'— {", ".join(parts)}' + ) + + ds, de = _def_line_range(prop.setter, lines) + def_indent = lines[ds][ + : len(lines[ds]) - len(lines[ds].lstrip()) + ] + new_def = ( + f'{def_indent}def {prop.name}' + f'(self, {setter_param}: ' + f'{expected_ann}) -> None:\n' + ) + result.edits.append(Edit(ds, de, new_def)) + + # Setter docstring — should not exist + setter_doc_text, sd_s, sd_e = _docstring_range( + prop.setter + ) + if setter_doc_text is not None: + result.issues.append( + f'{loc}: setter has docstring ' + '(should have none)' + ) + result.edits.append(Edit(sd_s, sd_e, '')) + + +# --------------------------------------------------------- +# Apply edits +# --------------------------------------------------------- + + +def _apply_edits( + lines: list[str], + edits: list[Edit], +) -> list[str]: + """Apply edits bottom-up to preserve line numbers.""" + sorted_edits = sorted( + edits, key=lambda e: e.start, reverse=True + ) + result = list(lines) + for edit in sorted_edits: + new_lines = edit.new_text.splitlines(keepends=True) + result[edit.start : edit.end] = new_lines + return result + + +# --------------------------------------------------------- +# Entry point +# --------------------------------------------------------- + + +def _collect_py_files(paths: list[str]) -> list[Path]: + """Resolve paths to a sorted list of .py files. + + Each entry can be a directory (recursively globbed) + or a single .py file. When *paths* is empty, + defaults to ``_SRC_ROOT``. + """ + if not paths: + return sorted(_SRC_ROOT.rglob('*.py')) + + result: list[Path] = [] + for raw in paths: + p = Path(raw).resolve() + if p.is_dir(): + result.extend(p.rglob('*.py')) + elif p.is_file() and p.suffix == '.py': + result.append(p) + return sorted(set(result)) + + +def main() -> int: + """Run param-consistency check or fix.""" + parser = argparse.ArgumentParser( + description=( + 'Parameter / property consistency: ' + 'docstrings and type hints.' + ), + ) + parser.add_argument( + 'paths', + nargs='*', + help=( + 'Directories or .py files to scan ' + '(default: src/easydiffraction/)' + ), + ) + group = parser.add_mutually_exclusive_group() + group.add_argument( + '--check', + action='store_true', + help='Validate consistency (default)', + ) + group.add_argument( + '--fix', + action='store_true', + help='Auto-fix docstrings and type hints', + ) + args = parser.parse_args() + + py_files = _collect_py_files(args.paths) + repo_root = Path(__file__).resolve().parents[1] + total_issues = 0 + total_fixed = 0 + files_touched = 0 + + for path in py_files: + result = _analyze_file(path) + if not result.issues: + continue + + try: + rel = path.relative_to(repo_root) + except ValueError: + rel = path + + if args.fix: + source_lines = path.read_text( + encoding='utf-8' + ).splitlines(keepends=True) + fixed_lines = _apply_edits( + source_lines, result.edits + ) + path.write_text( + ''.join(fixed_lines), encoding='utf-8' + ) + count = len(result.issues) + total_fixed += count + files_touched += 1 + print( + f'📝 {rel}: fixed {count} issue(s)' + ) + else: + for issue in result.issues: + print(f' ❌ {rel}: {issue}') + total_issues += len(result.issues) + + # Summary + print() + if args.fix: + print( + f'✅ Fixed {total_fixed} issue(s) ' + f'in {files_touched} file(s).' + ) + return 0 + if total_issues: + print( + f'❌ {total_issues} consistency ' + 'issue(s) found.' + ) + return 1 + print('✅ All properties match the template.') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/test_scripts.py b/tools/test_scripts.py index 327bafd2..e24b28e2 100644 --- a/tools/test_scripts.py +++ b/tools/test_scripts.py @@ -1,3 +1,5 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause """Test runner for tutorial scripts in the 'tutorials' directory. This test discovers and executes all Python scripts located under the @@ -16,15 +18,13 @@ import pytest -# Mark this module as 'integration' so it's excluded by default -# (see pytest.ini) -pytestmark = pytest.mark.integration - _repo_root = Path(__file__).resolve().parents[1] _src_root = _repo_root / 'src' # Discover tutorial scripts, excluding temporary checkpoint files -TUTORIALS = [p for p in Path('tutorials').rglob('*.py') if '.ipynb_checkpoints' not in p.parts] +TUTORIALS = [ + p for p in Path('docs/docs/tutorials').rglob('*.py') if '.ipynb_checkpoints' not in p.parts +] @pytest.mark.parametrize('script_path', TUTORIALS) diff --git a/tools/update_docs_assets.py b/tools/update_docs_assets.py new file mode 100644 index 00000000..b274e038 --- /dev/null +++ b/tools/update_docs_assets.py @@ -0,0 +1,91 @@ +""" +Update documentation assets from the assets-branding repository. + +This script fetches branding assets (logos, icons, images) from the +easyscience/assets-branding GitHub repository and copies them to the +appropriate locations in the documentation directory. +""" + +import shutil +from pathlib import Path + +import pooch + +# Configuration: Define what to fetch and where to copy +GITHUB_REPO = 'easyscience/assets-branding' +GITHUB_BRANCH = 'master' +BASE_URL = f'https://raw.githubusercontent.com/{GITHUB_REPO}/refs/heads/{GITHUB_BRANCH}' +PROJECT_NAME = 'easydiffraction' + +# Mapping of source files to destination paths +# Format: "source_path_in_repo": "destination_path_in_project" +ASSETS_MAP = { + # Logos + f'{PROJECT_NAME}/logos/dark.svg': 'docs/docs/assets/images/logo_dark.svg', + f'{PROJECT_NAME}/logos/light.svg': 'docs/docs/assets/images/logo_light.svg', + # Favicon + f'{PROJECT_NAME}/icons/color.png': 'docs/docs/assets/images/favicon.png', + # Icon overrides + f'{PROJECT_NAME}/icons/bw.svg': f'docs/overrides/.icons/{PROJECT_NAME}.svg', + 'easyscience-org/icons/eso-icon_bw.svg': 'docs/overrides/.icons/easyscience.svg', +} + + +def fetch_and_copy_asset( + source_path: str, + dest_path: str, + cache_dir: Path, +) -> None: + """ + Fetch an asset from GitHub and copy it to the destination. + + Args: + source_path: Path to the file in the GitHub repository + dest_path: Destination path in the project + cache_dir: Directory to cache downloaded files + """ + url = f'{BASE_URL}/{source_path}' + + # Create a unique cache filename based on source path + cache_filename = source_path.replace('/', '_') + + # Download file using pooch + file_path = pooch.retrieve( + url=url, + known_hash=None, # Skip hash verification + path=cache_dir, + fname=cache_filename, + ) + + # Create destination directory if it doesn't exist + dest = Path(dest_path) + dest.parent.mkdir(parents=True, exist_ok=True) + + # Copy the file to destination + shutil.copy2(file_path, dest) + print(f'Copied {file_path} -> {dest_path}') + + +def main(): + """Main function to update all documentation assets.""" + print('📥 Updating documentation assets...') + print(f' Repository: {GITHUB_REPO}') + print(f' Branch: {GITHUB_BRANCH}\n') + + # Use a temporary cache directory + cache_dir = Path.home() / '.cache' / GITHUB_REPO + cache_dir.mkdir(parents=True, exist_ok=True) + + # Fetch and copy each asset + for source_path, dest_path in ASSETS_MAP.items(): + try: + fetch_and_copy_asset(source_path, dest_path, cache_dir) + print() + except Exception as e: + print(f'❌ Failed to fetch {source_path}: {e}') + + print('\n✅ Documentation assets updated successfully!') + + +if __name__ == '__main__': + main() diff --git a/tools/update_github_labels.py b/tools/update_github_labels.py new file mode 100644 index 00000000..84de575e --- /dev/null +++ b/tools/update_github_labels.py @@ -0,0 +1,341 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Set/update GitHub labels for current or specified easyscience +repository. + +Requires: + - gh CLI installed + - gh auth login completed + +Usage: + python update_github_labels.py + python update_github_labels.py --dry-run + python update_github_labels.py --repo easyscience/my-repo + python update_github_labels.py --repo easyscience/my-repo --dry-run +""" + +from __future__ import annotations + +import argparse +import json +import shlex +import subprocess # noqa: S404 +import sys +from dataclasses import dataclass + +EASYSCIENCE_ORG = 'easyscience' + + +# Data structures + + +@dataclass(frozen=True) +class Label: + """A GitHub label with name, color, and description.""" + + name: str + color: str + description: str = '' + + +@dataclass(frozen=True) +class LabelRename: + """Mapping from old label name to new label name.""" + + old: str + new: str + + +class Colors: + """Hex color codes for label groups.""" + + SCOPE = 'd73a4a' + MAINTAINER = '0e8a16' + PRIORITY = 'fbca04' + BOT = '5319e7' + + +LABEL_RENAMES = [ + # Default GitHub labels to rename (if they exist) + LabelRename('bug', '[scope] bug'), + LabelRename('documentation', '[scope] documentation'), + LabelRename('duplicate', '[maintainer] duplicate'), + LabelRename('enhancement', '[scope] enhancement'), + LabelRename('good first issue', '[maintainer] good first issue'), + LabelRename('help wanted', '[maintainer] help wanted'), + LabelRename('invalid', '[maintainer] invalid'), + LabelRename('question', '[maintainer] question'), + LabelRename('wontfix', '[maintainer] wontfix'), + # Custom label renames (if they exist) + LabelRename('[bot] pull request', '[bot] release'), +] + +LABELS = [ + # Scope labels + Label( + '[scope] bug', + Colors.SCOPE, + 'Bug report or fix (major.minor.PATCH)', + ), + Label( + '[scope] documentation', + Colors.SCOPE, + 'Documentation only changes (major.minor.patch.POST)', + ), + Label( + '[scope] enhancement', + Colors.SCOPE, + 'Adds/improves features (major.MINOR.patch)', + ), + Label( + '[scope] maintenance', + Colors.SCOPE, + 'Code/tooling cleanup, no feature or bugfix (major.minor.PATCH)', + ), + Label( + '[scope] significant', + Colors.SCOPE, + 'Breaking or major changes (MAJOR.minor.patch)', + ), + Label( + '[scope] ⚠️ label needed', + Colors.SCOPE, + 'Automatically added to issues and PRs without a [scope] label', + ), + # Maintainer labels + Label( + '[maintainer] duplicate', + Colors.MAINTAINER, + 'Already reported or submitted', + ), + Label( + '[maintainer] good first issue', + Colors.MAINTAINER, + 'Good entry-level issue for newcomers', + ), + Label( + '[maintainer] help wanted', + Colors.MAINTAINER, + 'Needs additional help to resolve or implement', + ), + Label( + '[maintainer] invalid', + Colors.MAINTAINER, + 'Invalid, incorrect or outdated', + ), + Label( + '[maintainer] question', + Colors.MAINTAINER, + 'Needs clarification, discussion, or more information', + ), + Label( + '[maintainer] wontfix', + Colors.MAINTAINER, + 'Will not be fixed or continued', + ), + # Priority labels + Label( + '[priority] lowest', + Colors.PRIORITY, + 'Very low urgency', + ), + Label( + '[priority] low', + Colors.PRIORITY, + 'Low importance', + ), + Label( + '[priority] medium', + Colors.PRIORITY, + 'Normal/default priority', + ), + Label( + '[priority] high', + Colors.PRIORITY, + 'Should be prioritized soon', + ), + Label( + '[priority] highest', + Colors.PRIORITY, + 'Urgent. Needs attention ASAP', + ), + Label( + '[priority] ⚠️ label needed', + Colors.PRIORITY, + 'Automatically added to issues without a [priority] label', + ), + # Bot label + Label( + '[bot] release', + Colors.BOT, + 'Automated release PR. Excluded from changelog/versioning', + ), + Label( + '[bot] backmerge', + Colors.BOT, + 'Automated backmerge master → develop failed due to conflicts', + ), +] + + +# Helpers + + +@dataclass(frozen=True) +class CmdResult: + """Result of a shell command execution.""" + + returncode: int + stdout: str + stderr: str + + +def run_cmd( + args: list[str], + *, + dry_run: bool, + check: bool = True, +) -> CmdResult: + """Run a command (or print it in dry-run mode).""" + cmd_str = ' '.join(shlex.quote(a) for a in args) + + if dry_run: + print(f' [dry-run] {cmd_str}') + return CmdResult(0, '', '') + + proc = subprocess.run( + args=args, + text=True, + capture_output=True, + ) + result = CmdResult( + proc.returncode, + proc.stdout.strip(), + proc.stderr.strip(), + ) + + if check and proc.returncode != 0: + raise RuntimeError(f'Command failed ({proc.returncode}): {cmd_str}\n{result.stderr}') + + return result + + +def get_current_repo() -> str: + """Get the current repository name in 'owner/repo' format.""" + result = subprocess.run( + args=[ + 'gh', + 'repo', + 'view', + '--json', + 'nameWithOwner', + ], + text=True, + capture_output=True, + check=True, + ) + data = json.loads(result.stdout) + name_with_owner = data.get('nameWithOwner', '') + + if '/' not in name_with_owner: + raise RuntimeError('Could not determine current repository name') + + return name_with_owner + + +def rename_label( + repo: str, + rename: LabelRename, + *, + dry_run: bool, +) -> None: + """Rename a label, silently skipping if it doesn't exist.""" + result = run_cmd( + args=[ + 'gh', + 'label', + 'edit', + rename.old, + '--name', + rename.new, + '--repo', + repo, + ], + dry_run=dry_run, + check=False, + ) + + if dry_run or result.returncode == 0: + print(f' Rename: {rename.old!r} → {rename.new!r}') + else: + print(f' Skip (not found): {rename.old!r}') + + +def upsert_label( + repo: str, + label: Label, + *, + dry_run: bool, +) -> None: + """Create or update a label.""" + run_cmd( + [ + 'gh', + 'label', + 'create', + label.name, + '--color', + label.color, + '--description', + label.description, + '--force', + '--repo', + repo, + ], + dry_run=dry_run, + ) + print(f' Upsert: {label.name!r}') + + +# Main + + +def main() -> int: + """Entry point: parse arguments and sync labels.""" + parser = argparse.ArgumentParser(description='Sync GitHub labels for easyscience repos') + parser.add_argument( + '--repo', + help='Target repository (owner/name)', + ) + parser.add_argument( + '--dry-run', + action='store_true', + help='Print actions without applying changes', + ) + args = parser.parse_args() + + repo = args.repo or get_current_repo() + org = repo.split('/')[0] + + if org.lower() != EASYSCIENCE_ORG: + print(f"Error: repository '{repo}' is not under '{EASYSCIENCE_ORG}'", file=sys.stderr) + return 2 + + print(f'Repository: {repo}') + if args.dry_run: + print('Mode: DRY-RUN (no changes will be made)\n') + + print('\nRenaming default labels...') + for rename in LABEL_RENAMES: + rename_label(repo, rename, dry_run=args.dry_run) + + print('\nUpserting labels...') + for label in LABELS: + upsert_label(repo, label, dry_run=args.dry_run) + + print('\nDone.') + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/tools/update_spdx.py b/tools/update_spdx.py deleted file mode 100644 index df9236af..00000000 --- a/tools/update_spdx.py +++ /dev/null @@ -1,109 +0,0 @@ -"""Update or insert SPDX headers in Python files. - -- Ensures SPDX-FileCopyrightText has the current year. -- Ensures SPDX-License-Identifier is set to BSD-3-Clause. -""" - -import datetime -import fnmatch -import re -from pathlib import Path - -CURRENT_YEAR = datetime.datetime.now().year -COPYRIGHT_TEXT = ( - f'# SPDX-FileCopyrightText: 2021-{CURRENT_YEAR} EasyDiffraction contributors ' - '' -) -LICENSE_TEXT = '# SPDX-License-Identifier: BSD-3-Clause' - -# Patterns to exclude from SPDX header updates (vendored code) -EXCLUDE_PATTERNS = [ - '*/_vendored/jupyter_dark_detect/*', -] - - -def should_exclude(file_path: Path) -> bool: - """Check if a file should be excluded from SPDX header updates.""" - path_str = str(file_path) - return any(fnmatch.fnmatch(path_str, pattern) for pattern in EXCLUDE_PATTERNS) - - -def update_spdx_header(file_path: Path): - # Use Path.open to satisfy lint rule PTH123. - with file_path.open('r', encoding='utf-8') as f: - original_lines = f.readlines() - - # Regexes for SPDX lines - copy_re = re.compile(r'^#\s*SPDX-FileCopyrightText:.*$') - lic_re = re.compile(r'^#\s*SPDX-License-Identifier:.*$') - - # 1) Preserve any leading shebang / coding cookie lines - prefix = [] - body_start = 0 - if original_lines: - # Shebang line like "#!/usr/bin/env python3" - if original_lines[0].startswith('#!'): - prefix.append(original_lines[0]) - body_start = 1 - # PEP 263 coding cookie on first or second line - # e.g. "# -*- coding: utf-8 -*-" or "# coding: utf-8" - for _ in range(2): # at most one more line to inspect - if body_start < len(original_lines): - line = original_lines[body_start] - if re.match(r'^#.*coding[:=]\s*[-\w.]+', line): - prefix.append(line) - body_start += 1 - else: - break - - # 2) Work on the remaining body - body = original_lines[body_start:] - - # Remove any existing SPDX lines anywhere in the body - body = [ln for ln in body if not (copy_re.match(ln) or lic_re.match(ln))] - - # Strip leading blank lines in the body so header is tight - while body and not body[0].strip(): - body.pop(0) - - # 3) Build canonical SPDX block: two lines + exactly one blank - spdx_block = [ - COPYRIGHT_TEXT + '\n', - LICENSE_TEXT + '\n', - '\n', - ] - - # 4) New content: prefix + SPDX + body - new_lines = prefix + spdx_block + body - - # 5) Normalize: collapse any extra blank lines immediately after - # LICENSE to exactly one. This keeps the script idempotent. - # Find the index of LICENSE we just inserted (prefix may be 0, 1, - # or 2 lines) - lic_idx = len(prefix) + 1 # spdx_block[1] is the license line - # Ensure exactly one blank line after LICENSE - # Remove all blank lines after lic_idx, then insert a single blank. - j = lic_idx + 1 - # Remove any number of blank lines following - while j < len(new_lines) and not new_lines[j].strip(): - new_lines.pop(j) - # Insert exactly one blank line at this position - new_lines.insert(j, '\n') - - with file_path.open('w', encoding='utf-8') as f: - f.writelines(new_lines) - - -def main(): - """Recursively update or insert SPDX headers in all Python files - under the 'src' and 'tests' directories. - """ - for base_dir in ('src', 'tests'): - for py_file in Path(base_dir).rglob('*.py'): - if should_exclude(py_file): - continue - update_spdx_header(py_file) - - -if __name__ == '__main__': - main() diff --git a/tutorials/data/ed-3.xye b/tutorials/data/ed-3.xye deleted file mode 100644 index 0b9b63e3..00000000 --- a/tutorials/data/ed-3.xye +++ /dev/null @@ -1,3099 +0,0 @@ -# 2theta intensity su - 10.00 167.00 12.60 - 10.05 157.00 12.50 - 10.10 187.00 13.30 - 10.15 197.00 14.00 - 10.20 164.00 12.50 - 10.25 171.00 13.00 - 10.30 190.00 13.40 - 10.35 182.00 13.50 - 10.40 166.00 12.60 - 10.45 203.00 14.30 - 10.50 156.00 12.20 - 10.55 190.00 13.90 - 10.60 175.00 13.00 - 10.65 161.00 12.90 - 10.70 187.00 13.50 - 10.75 166.00 13.10 - 10.80 171.00 13.00 - 10.85 177.00 13.60 - 10.90 159.00 12.60 - 10.95 184.00 13.90 - 11.00 160.00 12.60 - 11.05 182.00 13.90 - 11.10 167.00 13.00 - 11.15 169.00 13.40 - 11.20 186.00 13.70 - 11.25 167.00 13.30 - 11.30 169.00 13.10 - 11.35 159.00 13.10 - 11.40 170.00 13.20 - 11.45 179.00 13.90 - 11.50 178.00 13.50 - 11.55 188.00 14.20 - 11.60 176.00 13.50 - 11.65 196.00 14.60 - 11.70 182.00 13.70 - 11.75 183.00 14.00 - 11.80 195.00 14.10 - 11.85 144.00 12.40 - 11.90 178.00 13.50 - 11.95 175.00 13.70 - 12.00 200.00 14.20 - 12.05 157.00 12.90 - 12.10 195.00 14.00 - 12.15 164.00 13.10 - 12.20 188.00 13.70 - 12.25 168.00 13.10 - 12.30 191.00 13.70 - 12.35 178.00 13.40 - 12.40 182.00 13.30 - 12.45 174.00 13.30 - 12.50 171.00 12.90 - 12.55 174.00 13.20 - 12.60 184.00 13.30 - 12.65 164.00 12.80 - 12.70 166.00 12.50 - 12.75 177.00 13.20 - 12.80 174.00 12.80 - 12.85 187.00 13.50 - 12.90 183.00 13.10 - 12.95 187.00 13.50 - 13.00 175.00 12.80 - 13.05 165.00 12.70 - 13.10 177.00 12.80 - 13.15 182.00 13.30 - 13.20 195.00 13.50 - 13.25 163.00 12.60 - 13.30 180.00 12.90 - 13.35 171.00 12.90 - 13.40 182.00 13.00 - 13.45 179.00 13.10 - 13.50 161.00 12.20 - 13.55 156.00 12.30 - 13.60 197.00 13.50 - 13.65 167.00 12.70 - 13.70 180.00 12.80 - 13.75 182.00 13.20 - 13.80 176.00 12.70 - 13.85 153.00 12.10 - 13.90 179.00 12.80 - 13.95 156.00 12.30 - 14.00 187.00 13.10 - 14.05 170.00 12.80 - 14.10 185.00 13.00 - 14.15 180.00 13.20 - 14.20 167.00 12.40 - 14.25 159.00 12.40 - 14.30 152.00 11.80 - 14.35 173.00 13.00 - 14.40 169.00 12.50 - 14.45 185.00 13.40 - 14.50 168.00 12.40 - 14.55 193.00 13.70 - 14.60 177.00 12.80 - 14.65 161.00 12.50 - 14.70 180.00 12.90 - 14.75 165.00 12.60 - 14.80 178.00 12.80 - 14.85 157.00 12.30 - 14.90 163.00 12.30 - 14.95 143.00 11.70 - 15.00 155.00 11.90 - 15.05 168.00 12.80 - 15.10 160.00 12.10 - 15.15 155.00 12.20 - 15.20 203.00 13.70 - 15.25 164.00 12.60 - 15.30 158.00 12.10 - 15.35 152.00 12.10 - 15.40 173.00 12.60 - 15.45 160.00 12.50 - 15.50 172.00 12.60 - 15.55 164.00 12.60 - 15.60 163.00 12.30 - 15.65 173.00 13.00 - 15.70 177.00 12.80 - 15.75 184.00 13.40 - 15.80 173.00 12.70 - 15.85 182.00 13.30 - 15.90 156.00 12.10 - 15.95 152.00 12.20 - 16.00 201.00 13.70 - 16.05 156.00 12.30 - 16.10 169.00 12.50 - 16.15 178.00 13.20 - 16.20 150.00 11.80 - 16.25 163.00 12.60 - 16.30 165.00 12.40 - 16.35 160.00 12.50 - 16.40 171.00 12.60 - 16.45 168.00 12.80 - 16.50 159.00 12.20 - 16.55 166.00 12.80 - 16.60 156.00 12.10 - 16.65 156.00 12.40 - 16.70 154.00 12.10 - 16.75 173.00 13.10 - 16.80 173.00 12.80 - 16.85 161.00 12.70 - 16.90 177.00 13.00 - 16.95 159.00 12.70 - 17.00 162.00 12.50 - 17.05 166.00 13.00 - 17.10 167.00 12.70 - 17.15 166.00 13.10 - 17.20 168.00 12.80 - 17.25 188.00 14.00 - 17.30 165.00 12.80 - 17.35 171.00 13.40 - 17.40 171.00 13.10 - 17.45 162.00 13.10 - 17.50 161.00 12.80 - 17.55 177.00 13.80 - 17.60 176.00 13.40 - 17.65 175.00 13.70 - 17.70 140.00 12.00 - 17.75 177.00 13.90 - 17.80 150.00 12.40 - 17.85 154.00 12.90 - 17.90 138.00 11.90 - 17.95 161.00 13.20 - 18.00 171.00 13.30 - 18.05 144.00 12.50 - 18.10 148.00 12.40 - 18.15 169.00 13.50 - 18.20 162.00 12.90 - 18.25 171.00 13.50 - 18.30 155.00 12.60 - 18.35 143.00 12.30 - 18.40 162.00 12.80 - 18.45 177.00 13.60 - 18.50 158.00 12.60 - 18.55 142.00 12.20 - 18.60 153.00 12.40 - 18.65 169.00 13.30 - 18.70 144.00 12.00 - 18.75 171.00 13.30 - 18.80 159.00 12.50 - 18.85 169.00 13.10 - 18.90 163.00 12.60 - 18.95 154.00 12.50 - 19.00 146.00 11.90 - 19.05 154.00 12.50 - 19.10 156.00 12.20 - 19.15 195.00 14.00 - 19.20 154.00 12.10 - 19.25 167.00 12.90 - 19.30 156.00 12.20 - 19.35 148.00 12.10 - 19.40 173.00 12.80 - 19.45 155.00 12.40 - 19.50 146.00 11.70 - 19.55 173.00 13.10 - 19.60 179.00 13.00 - 19.65 152.00 12.30 - 19.70 182.00 13.10 - 19.75 183.00 13.40 - 19.80 150.00 11.90 - 19.85 155.00 12.30 - 19.90 158.00 12.20 - 19.95 161.00 12.60 - 20.00 164.00 12.40 - 20.05 166.00 12.80 - 20.10 172.00 12.70 - 20.15 148.00 12.10 - 20.20 161.00 12.30 - 20.25 160.00 12.60 - 20.30 185.00 13.20 - 20.35 165.00 12.80 - 20.40 155.00 12.10 - 20.45 172.00 13.00 - 20.50 170.00 12.70 - 20.55 180.00 13.40 - 20.60 184.00 13.20 - 20.65 164.00 12.80 - 20.70 177.00 13.00 - 20.75 150.00 12.20 - 20.80 176.00 12.90 - 20.85 174.00 13.20 - 20.90 173.00 12.80 - 20.95 167.00 12.90 - 21.00 158.00 12.20 - 21.05 174.00 13.20 - 21.10 160.00 12.30 - 21.15 174.00 13.20 - 21.20 160.00 12.30 - 21.25 182.00 13.40 - 21.30 155.00 12.10 - 21.35 182.00 13.40 - 21.40 157.00 12.20 - 21.45 174.00 13.20 - 21.50 173.00 12.80 - 21.55 165.00 12.80 - 21.60 182.00 13.10 - 21.65 176.00 13.20 - 21.70 150.00 11.90 - 21.75 162.00 12.60 - 21.80 172.00 12.70 - 21.85 162.00 12.70 - 21.90 171.00 12.70 - 21.95 165.00 12.80 - 22.00 180.00 13.00 - 22.05 167.00 12.80 - 22.10 159.00 12.20 - 22.15 159.00 12.50 - 22.20 160.00 12.30 - 22.25 174.00 13.10 - 22.30 175.00 12.90 - 22.35 172.00 13.10 - 22.40 176.00 12.90 - 22.45 140.00 11.80 - 22.50 163.00 12.40 - 22.55 180.00 13.50 - 22.60 211.00 14.20 - 22.65 190.00 13.90 - 22.70 179.00 13.10 - 22.75 195.00 14.10 - 22.80 198.00 13.90 - 22.85 181.00 13.70 - 22.90 203.00 14.10 - 22.95 193.00 14.10 - 23.00 155.00 12.40 - 23.05 159.00 12.90 - 23.10 184.00 13.50 - 23.15 145.00 12.30 - 23.20 145.00 12.00 - 23.25 179.00 13.70 - 23.30 185.00 13.60 - 23.35 168.00 13.30 - 23.40 185.00 13.60 - 23.45 170.00 13.40 - 23.50 174.00 13.30 - 23.55 164.00 13.20 - 23.60 168.00 13.10 - 23.65 185.00 14.10 - 23.70 183.00 13.70 - 23.75 172.00 13.70 - 23.80 156.00 12.70 - 23.85 182.00 14.00 - 23.90 182.00 13.70 - 23.95 149.00 12.70 - 24.00 160.00 12.80 - 24.05 168.00 13.50 - 24.10 178.00 13.60 - 24.15 169.00 13.60 - 24.20 172.00 13.40 - 24.25 170.00 13.60 - 24.30 161.00 12.90 - 24.35 168.00 13.50 - 24.40 162.00 13.00 - 24.45 157.00 13.00 - 24.50 162.00 12.90 - 24.55 159.00 13.10 - 24.60 168.00 13.20 - 24.65 170.00 13.50 - 24.70 166.00 13.00 - 24.75 146.00 12.50 - 24.80 154.00 12.50 - 24.85 154.00 12.70 - 24.90 198.00 14.10 - 24.95 195.00 14.30 - 25.00 148.00 12.20 - 25.05 161.00 12.90 - 25.10 160.00 12.60 - 25.15 160.00 12.80 - 25.20 149.00 12.10 - 25.25 179.00 13.50 - 25.30 174.00 13.00 - 25.35 168.00 13.00 - 25.40 146.00 11.90 - 25.45 160.00 12.70 - 25.50 145.00 11.80 - 25.55 151.00 12.30 - 25.60 161.00 12.40 - 25.65 187.00 13.60 - 25.70 154.00 12.10 - 25.75 157.00 12.40 - 25.80 169.00 12.60 - 25.85 181.00 13.40 - 25.90 156.00 12.10 - 25.95 185.00 13.40 - 26.00 192.00 13.40 - 26.05 153.00 12.20 - 26.10 149.00 11.80 - 26.15 154.00 12.20 - 26.20 152.00 11.90 - 26.25 179.00 13.20 - 26.30 180.00 12.90 - 26.35 160.00 12.50 - 26.40 174.00 12.60 - 26.45 145.00 11.80 - 26.50 171.00 12.50 - 26.55 162.00 12.50 - 26.60 154.00 11.80 - 26.65 153.00 12.10 - 26.70 162.00 12.10 - 26.75 160.00 12.40 - 26.80 150.00 11.70 - 26.85 189.00 13.40 - 26.90 168.00 12.40 - 26.95 144.00 11.70 - 27.00 147.00 11.60 - 27.05 155.00 12.20 - 27.10 174.00 12.60 - 27.15 169.00 12.70 - 27.20 174.00 12.60 - 27.25 164.00 12.60 - 27.30 146.00 11.60 - 27.35 149.00 12.00 - 27.40 155.00 11.90 - 27.45 155.00 12.20 - 27.50 168.00 12.40 - 27.55 131.00 11.20 - 27.60 159.00 12.10 - 27.65 181.00 13.20 - 27.70 146.00 11.60 - 27.75 188.00 13.50 - 27.80 162.00 12.20 - 27.85 161.00 12.50 - 27.90 176.00 12.70 - 27.95 152.00 12.10 - 28.00 170.00 12.40 - 28.05 152.00 12.00 - 28.10 158.00 12.00 - 28.15 168.00 12.60 - 28.20 161.00 12.10 - 28.25 184.00 13.30 - 28.30 166.00 12.30 - 28.35 193.00 13.60 - 28.40 157.00 12.00 - 28.45 167.00 12.60 - 28.50 158.00 12.00 - 28.55 135.00 11.40 - 28.60 150.00 11.70 - 28.65 167.00 12.70 - 28.70 161.00 12.20 - 28.75 157.00 12.30 - 28.80 153.00 11.80 - 28.85 161.00 12.50 - 28.90 163.00 12.20 - 28.95 133.00 11.40 - 29.00 169.00 12.50 - 29.05 162.00 12.50 - 29.10 161.00 12.20 - 29.15 163.00 12.60 - 29.20 144.00 11.60 - 29.25 178.00 13.20 - 29.30 161.00 12.20 - 29.35 141.00 11.80 - 29.40 169.00 12.50 - 29.45 160.00 12.50 - 29.50 177.00 12.90 - 29.55 174.00 13.10 - 29.60 157.00 12.10 - 29.65 176.00 13.20 - 29.70 179.00 13.00 - 29.75 166.00 12.90 - 29.80 162.00 12.40 - 29.85 147.00 12.20 - 29.90 152.00 12.00 - 29.95 171.00 13.20 - 30.00 178.00 13.10 - 30.05 208.00 14.60 - 30.10 178.00 13.20 - 30.15 149.00 12.40 - 30.20 181.00 13.30 - 30.25 162.00 13.00 - 30.30 177.00 13.20 - 30.35 165.00 13.10 - 30.40 177.00 13.30 - 30.45 158.00 12.90 - 30.50 157.00 12.60 - 30.55 163.00 13.10 - 30.60 144.00 12.00 - 30.65 156.00 12.80 - 30.70 176.00 13.30 - 30.75 179.00 13.70 - 30.80 174.00 13.20 - 30.85 182.00 13.80 - 30.90 161.00 12.70 - 30.95 166.00 13.10 - 31.00 168.00 13.00 - 31.05 153.00 12.60 - 31.10 156.00 12.40 - 31.15 174.00 13.40 - 31.20 167.00 12.80 - 31.25 192.00 14.00 - 31.30 154.00 12.30 - 31.35 166.00 13.00 - 31.40 169.00 12.90 - 31.45 185.00 13.70 - 31.50 165.00 12.60 - 31.55 163.00 12.80 - 31.60 173.00 12.90 - 31.65 169.00 13.00 - 31.70 188.00 13.40 - 31.75 195.00 13.90 - 31.80 195.00 13.60 - 31.85 221.00 14.70 - 31.90 229.00 14.70 - 31.95 302.00 17.20 - 32.00 327.00 17.50 - 32.05 380.00 19.30 - 32.10 358.00 18.30 - 32.15 394.00 19.60 - 32.20 373.00 18.70 - 32.25 362.00 18.70 - 32.30 306.00 16.90 - 32.35 276.00 16.40 - 32.40 237.00 14.80 - 32.45 203.00 14.00 - 32.50 178.00 12.80 - 32.55 199.00 13.90 - 32.60 167.00 12.40 - 32.65 185.00 13.40 - 32.70 180.00 12.90 - 32.75 178.00 13.10 - 32.80 145.00 11.50 - 32.85 176.00 13.00 - 32.90 177.00 12.70 - 32.95 182.00 13.20 - 33.00 167.00 12.40 - 33.05 152.00 12.10 - 33.10 144.00 11.50 - 33.15 170.00 12.80 - 33.20 156.00 11.90 - 33.25 154.00 12.20 - 33.30 180.00 12.80 - 33.35 176.00 13.00 - 33.40 183.00 12.90 - 33.45 162.00 12.40 - 33.50 180.00 12.80 - 33.55 165.00 12.60 - 33.60 174.00 12.50 - 33.65 179.00 13.00 - 33.70 152.00 11.70 - 33.75 182.00 13.10 - 33.80 184.00 12.90 - 33.85 166.00 12.50 - 33.90 182.00 12.80 - 33.95 162.00 12.40 - 34.00 174.00 12.50 - 34.05 153.00 12.00 - 34.10 182.00 12.80 - 34.15 180.00 13.00 - 34.20 167.00 12.20 - 34.25 173.00 12.70 - 34.30 153.00 11.70 - 34.35 160.00 12.30 - 34.40 180.00 12.70 - 34.45 168.00 12.50 - 34.50 167.00 12.20 - 34.55 176.00 12.80 - 34.60 165.00 12.10 - 34.65 174.00 12.80 - 34.70 161.00 12.00 - 34.75 178.00 12.90 - 34.80 170.00 12.30 - 34.85 166.00 12.50 - 34.90 173.00 12.40 - 34.95 158.00 12.20 - 35.00 166.00 12.20 - 35.05 170.00 12.60 - 35.10 162.00 12.00 - 35.15 183.00 13.10 - 35.20 176.00 12.50 - 35.25 171.00 12.60 - 35.30 174.00 12.50 - 35.35 179.00 12.90 - 35.40 176.00 12.50 - 35.45 193.00 13.40 - 35.50 180.00 12.70 - 35.55 188.00 13.30 - 35.60 177.00 12.60 - 35.65 176.00 12.90 - 35.70 171.00 12.40 - 35.75 185.00 13.30 - 35.80 178.00 12.70 - 35.85 152.00 12.10 - 35.90 160.00 12.10 - 35.95 187.00 13.50 - 36.00 167.00 12.40 - 36.05 181.00 13.30 - 36.10 166.00 12.40 - 36.15 165.00 12.80 - 36.20 170.00 12.70 - 36.25 197.00 14.10 - 36.30 179.00 13.10 - 36.35 172.00 13.20 - 36.40 181.00 13.30 - 36.45 174.00 13.40 - 36.50 162.00 12.60 - 36.55 166.00 13.10 - 36.60 158.00 12.50 - 36.65 199.00 14.40 - 36.70 188.00 13.70 - 36.75 177.00 13.70 - 36.80 167.00 12.90 - 36.85 156.00 12.90 - 36.90 174.00 13.20 - 36.95 176.00 13.70 - 37.00 152.00 12.40 - 37.05 191.00 14.40 - 37.10 151.00 12.50 - 37.15 202.00 14.80 - 37.20 191.00 14.00 - 37.25 161.00 13.20 - 37.30 199.00 14.30 - 37.35 175.00 13.70 - 37.40 146.00 12.30 - 37.45 181.00 14.00 - 37.50 221.00 15.00 - 37.55 194.00 14.40 - 37.60 158.00 12.70 - 37.65 171.00 13.50 - 37.70 172.00 13.20 - 37.75 168.00 13.30 - 37.80 192.00 13.90 - 37.85 185.00 13.90 - 37.90 193.00 13.90 - 37.95 178.00 13.60 - 38.00 195.00 13.90 - 38.05 175.00 13.40 - 38.10 178.00 13.20 - 38.15 173.00 13.30 - 38.20 195.00 13.70 - 38.25 194.00 13.90 - 38.30 191.00 13.50 - 38.35 178.00 13.30 - 38.40 184.00 13.30 - 38.45 186.00 13.50 - 38.50 202.00 13.80 - 38.55 200.00 14.00 - 38.60 210.00 14.00 - 38.65 198.00 13.90 - 38.70 225.00 14.50 - 38.75 209.00 14.30 - 38.80 229.00 14.60 - 38.85 197.00 13.90 - 38.90 220.00 14.30 - 38.95 215.00 14.40 - 39.00 242.00 15.00 - 39.05 340.00 18.10 - 39.10 441.00 20.20 - 39.15 654.00 25.10 - 39.20 962.00 29.70 - 39.25 1477.00 37.70 - 39.30 2012.00 43.00 - 39.35 2634.00 50.20 - 39.40 3115.00 53.40 - 39.45 3467.00 57.50 - 39.50 3532.00 56.70 - 39.55 3337.00 56.30 - 39.60 2595.00 48.60 - 39.65 1943.00 42.90 - 39.70 1251.00 33.70 - 39.75 828.00 28.00 - 39.80 525.00 21.80 - 39.85 377.00 18.80 - 39.90 294.00 16.30 - 39.95 233.00 14.80 - 40.00 233.00 14.50 - 40.05 253.00 15.40 - 40.10 253.00 15.10 - 40.15 213.00 14.10 - 40.20 196.00 13.20 - 40.25 222.00 14.40 - 40.30 172.00 12.40 - 40.35 218.00 14.30 - 40.40 206.00 13.60 - 40.45 195.00 13.60 - 40.50 209.00 13.70 - 40.55 192.00 13.50 - 40.60 197.00 13.30 - 40.65 188.00 13.30 - 40.70 202.00 13.50 - 40.75 208.00 14.00 - 40.80 184.00 12.90 - 40.85 177.00 13.00 - 40.90 202.00 13.50 - 40.95 198.00 13.80 - 41.00 203.00 13.60 - 41.05 193.00 13.60 - 41.10 188.00 13.10 - 41.15 211.00 14.20 - 41.20 189.00 13.10 - 41.25 200.00 13.90 - 41.30 198.00 13.50 - 41.35 203.00 14.00 - 41.40 197.00 13.40 - 41.45 190.00 13.60 - 41.50 212.00 14.00 - 41.55 185.00 13.40 - 41.60 228.00 14.50 - 41.65 167.00 12.80 - 41.70 207.00 13.90 - 41.75 187.00 13.60 - 41.80 190.00 13.30 - 41.85 192.00 13.80 - 41.90 185.00 13.20 - 41.95 161.00 12.70 - 42.00 187.00 13.30 - 42.05 191.00 13.80 - 42.10 159.00 12.30 - 42.15 170.00 13.10 - 42.20 182.00 13.20 - 42.25 186.00 13.70 - 42.30 192.00 13.60 - 42.35 178.00 13.50 - 42.40 186.00 13.40 - 42.45 180.00 13.50 - 42.50 178.00 13.10 - 42.55 182.00 13.60 - 42.60 179.00 13.20 - 42.65 203.00 14.50 - 42.70 191.00 13.70 - 42.75 207.00 14.60 - 42.80 183.00 13.40 - 42.85 180.00 13.60 - 42.90 191.00 13.70 - 42.95 187.00 13.90 - 43.00 184.00 13.50 - 43.05 182.00 13.80 - 43.10 178.00 13.30 - 43.15 169.00 13.30 - 43.20 158.00 12.60 - 43.25 180.00 13.70 - 43.30 174.00 13.20 - 43.35 184.00 14.00 - 43.40 178.00 13.40 - 43.45 180.00 13.80 - 43.50 144.00 12.00 - 43.55 169.00 13.40 - 43.60 177.00 13.30 - 43.65 156.00 12.80 - 43.70 148.00 12.20 - 43.75 159.00 12.90 - 43.80 195.00 14.00 - 43.85 186.00 14.00 - 43.90 180.00 13.40 - 43.95 192.00 14.10 - 44.00 186.00 13.50 - 44.05 180.00 13.60 - 44.10 174.00 13.10 - 44.15 181.00 13.60 - 44.20 178.00 13.20 - 44.25 189.00 13.80 - 44.30 206.00 14.10 - 44.35 183.00 13.60 - 44.40 161.00 12.40 - 44.45 170.00 13.00 - 44.50 203.00 13.90 - 44.55 168.00 12.90 - 44.60 199.00 13.70 - 44.65 192.00 13.70 - 44.70 192.00 13.40 - 44.75 200.00 14.00 - 44.80 206.00 13.90 - 44.85 193.00 13.70 - 44.90 188.00 13.20 - 44.95 200.00 13.90 - 45.00 193.00 13.40 - 45.05 203.00 14.00 - 45.10 212.00 14.00 - 45.15 197.00 13.80 - 45.20 219.00 14.20 - 45.25 219.00 14.60 - 45.30 226.00 14.50 - 45.35 282.00 16.50 - 45.40 353.00 18.10 - 45.45 469.00 21.30 - 45.50 741.00 26.20 - 45.55 1176.00 33.70 - 45.60 1577.00 38.10 - 45.65 2122.00 45.30 - 45.70 2726.00 50.10 - 45.75 2990.00 53.70 - 45.80 2991.00 52.50 - 45.85 2796.00 52.00 - 45.90 2372.00 46.80 - 45.95 1752.00 41.20 - 46.00 1209.00 33.40 - 46.05 824.00 28.30 - 46.10 512.00 21.80 - 46.15 353.00 18.60 - 46.20 273.00 15.90 - 46.25 259.00 15.90 - 46.30 233.00 14.80 - 46.35 220.00 14.70 - 46.40 228.00 14.60 - 46.45 231.00 15.10 - 46.50 218.00 14.30 - 46.55 210.00 14.40 - 46.60 212.00 14.20 - 46.65 187.00 13.60 - 46.70 207.00 14.00 - 46.75 212.00 14.50 - 46.80 188.00 13.40 - 46.85 178.00 13.30 - 46.90 186.00 13.30 - 46.95 192.00 13.80 - 47.00 192.00 13.50 - 47.05 186.00 13.60 - 47.10 208.00 14.10 - 47.15 199.00 14.10 - 47.20 165.00 12.50 - 47.25 212.00 14.50 - 47.30 191.00 13.50 - 47.35 185.00 13.60 - 47.40 171.00 12.70 - 47.45 176.00 13.20 - 47.50 179.00 13.00 - 47.55 187.00 13.60 - 47.60 181.00 13.10 - 47.65 173.00 13.10 - 47.70 167.00 12.50 - 47.75 182.00 13.40 - 47.80 171.00 12.70 - 47.85 185.00 13.50 - 47.90 177.00 12.90 - 47.95 154.00 12.40 - 48.00 200.00 13.70 - 48.05 177.00 13.30 - 48.10 184.00 13.20 - 48.15 166.00 12.80 - 48.20 181.00 13.10 - 48.25 208.00 14.40 - 48.30 186.00 13.20 - 48.35 164.00 12.70 - 48.40 196.00 13.60 - 48.45 169.00 12.90 - 48.50 173.00 12.70 - 48.55 200.00 14.10 - 48.60 163.00 12.40 - 48.65 173.00 13.10 - 48.70 187.00 13.30 - 48.75 177.00 13.30 - 48.80 200.00 13.80 - 48.85 171.00 13.00 - 48.90 192.00 13.50 - 48.95 178.00 13.30 - 49.00 169.00 12.70 - 49.05 160.00 12.70 - 49.10 182.00 13.20 - 49.15 173.00 13.20 - 49.20 170.00 12.80 - 49.25 181.00 13.60 - 49.30 170.00 12.90 - 49.35 164.00 13.00 - 49.40 166.00 12.70 - 49.45 174.00 13.40 - 49.50 173.00 13.10 - 49.55 137.00 11.90 - 49.60 166.00 12.80 - 49.65 194.00 14.20 - 49.70 160.00 12.60 - 49.75 152.00 12.50 - 49.80 180.00 13.30 - 49.85 160.00 12.90 - 49.90 149.00 12.20 - 49.95 172.00 13.40 - 50.00 170.00 13.00 - 50.05 175.00 13.50 - 50.10 162.00 12.70 - 50.15 168.00 13.20 - 50.20 186.00 13.60 - 50.25 179.00 13.60 - 50.30 165.00 12.70 - 50.35 155.00 12.60 - 50.40 170.00 12.90 - 50.45 162.00 12.80 - 50.50 157.00 12.30 - 50.55 173.00 13.20 - 50.60 149.00 12.00 - 50.65 167.00 13.00 - 50.70 165.00 12.60 - 50.75 157.00 12.50 - 50.80 177.00 13.00 - 50.85 187.00 13.60 - 50.90 155.00 12.10 - 50.95 194.00 13.70 - 51.00 147.00 11.70 - 51.05 169.00 12.80 - 51.10 166.00 12.40 - 51.15 193.00 13.60 - 51.20 168.00 12.40 - 51.25 188.00 13.40 - 51.30 182.00 12.80 - 51.35 180.00 13.10 - 51.40 177.00 12.70 - 51.45 188.00 13.30 - 51.50 187.00 13.00 - 51.55 178.00 12.90 - 51.60 177.00 12.60 - 51.65 184.00 13.10 - 51.70 172.00 12.40 - 51.75 188.00 13.30 - 51.80 194.00 13.20 - 51.85 179.00 12.90 - 51.90 176.00 12.50 - 51.95 180.00 12.90 - 52.00 169.00 12.20 - 52.05 178.00 12.90 - 52.10 165.00 12.10 - 52.15 149.00 11.70 - 52.20 168.00 12.20 - 52.25 157.00 12.10 - 52.30 151.00 11.60 - 52.35 181.00 13.00 - 52.40 172.00 12.40 - 52.45 178.00 12.90 - 52.50 179.00 12.60 - 52.55 171.00 12.60 - 52.60 129.00 10.70 - 52.65 180.00 13.00 - 52.70 154.00 11.70 - 52.75 182.00 13.10 - 52.80 166.00 12.20 - 52.85 156.00 12.10 - 52.90 164.00 12.10 - 52.95 166.00 12.50 - 53.00 176.00 12.50 - 53.05 182.00 13.10 - 53.10 173.00 12.50 - 53.15 160.00 12.30 - 53.20 169.00 12.30 - 53.25 162.00 12.30 - 53.30 164.00 12.10 - 53.35 165.00 12.40 - 53.40 177.00 12.60 - 53.45 173.00 12.80 - 53.50 158.00 11.90 - 53.55 164.00 12.40 - 53.60 175.00 12.50 - 53.65 166.00 12.50 - 53.70 161.00 12.00 - 53.75 167.00 12.50 - 53.80 136.00 11.00 - 53.85 167.00 12.50 - 53.90 152.00 11.70 - 53.95 159.00 12.20 - 54.00 172.00 12.40 - 54.05 179.00 12.90 - 54.10 169.00 12.20 - 54.15 165.00 12.40 - 54.20 166.00 12.10 - 54.25 162.00 12.30 - 54.30 175.00 12.40 - 54.35 162.00 12.30 - 54.40 145.00 11.40 - 54.45 148.00 11.70 - 54.50 157.00 11.80 - 54.55 176.00 12.80 - 54.60 162.00 12.00 - 54.65 153.00 12.00 - 54.70 178.00 12.60 - 54.75 147.00 11.80 - 54.80 146.00 11.50 - 54.85 170.00 12.70 - 54.90 155.00 11.80 - 54.95 170.00 12.70 - 55.00 142.00 11.30 - 55.05 154.00 12.10 - 55.10 150.00 11.70 - 55.15 145.00 11.80 - 55.20 151.00 11.80 - 55.25 162.00 12.50 - 55.30 153.00 11.90 - 55.35 170.00 12.90 - 55.40 153.00 11.90 - 55.45 156.00 12.40 - 55.50 163.00 12.40 - 55.55 149.00 12.20 - 55.60 135.00 11.30 - 55.65 158.00 12.60 - 55.70 144.00 11.70 - 55.75 152.00 12.40 - 55.80 165.00 12.70 - 55.85 164.00 13.00 - 55.90 175.00 13.10 - 55.95 150.00 12.40 - 56.00 168.00 12.90 - 56.05 159.00 12.90 - 56.10 187.00 13.60 - 56.15 170.00 13.30 - 56.20 159.00 12.60 - 56.25 148.00 12.50 - 56.30 159.00 12.60 - 56.35 174.00 13.50 - 56.40 195.00 14.00 - 56.45 219.00 15.10 - 56.50 216.00 14.70 - 56.55 271.00 16.80 - 56.60 337.00 18.30 - 56.65 417.00 20.80 - 56.70 390.00 19.70 - 56.75 414.00 20.70 - 56.80 388.00 19.60 - 56.85 317.00 18.10 - 56.90 307.00 17.40 - 56.95 250.00 16.00 - 57.00 205.00 14.20 - 57.05 167.00 13.00 - 57.10 179.00 13.20 - 57.15 159.00 12.70 - 57.20 170.00 12.80 - 57.25 168.00 13.00 - 57.30 180.00 13.10 - 57.35 144.00 12.00 - 57.40 178.00 13.00 - 57.45 203.00 14.20 - 57.50 159.00 12.30 - 57.55 165.00 12.80 - 57.60 164.00 12.40 - 57.65 135.00 11.60 - 57.70 157.00 12.20 - 57.75 162.00 12.70 - 57.80 175.00 12.90 - 57.85 161.00 12.60 - 57.90 174.00 12.80 - 57.95 187.00 13.70 - 58.00 164.00 12.50 - 58.05 188.00 13.70 - 58.10 163.00 12.40 - 58.15 177.00 13.30 - 58.20 181.00 13.10 - 58.25 156.00 12.50 - 58.30 163.00 12.40 - 58.35 190.00 13.80 - 58.40 162.00 12.40 - 58.45 186.00 13.70 - 58.50 169.00 12.70 - 58.55 160.00 12.70 - 58.60 171.00 12.80 - 58.65 160.00 12.60 - 58.70 174.00 12.90 - 58.75 163.00 12.70 - 58.80 180.00 13.10 - 58.85 176.00 13.20 - 58.90 174.00 12.80 - 58.95 177.00 13.30 - 59.00 186.00 13.30 - 59.05 157.00 12.40 - 59.10 188.00 13.30 - 59.15 162.00 12.60 - 59.20 160.00 12.20 - 59.25 196.00 13.90 - 59.30 178.00 12.90 - 59.35 188.00 13.50 - 59.40 161.00 12.30 - 59.45 157.00 12.30 - 59.50 183.00 13.00 - 59.55 169.00 12.80 - 59.60 150.00 11.80 - 59.65 195.00 13.70 - 59.70 175.00 12.70 - 59.75 160.00 12.40 - 59.80 168.00 12.40 - 59.85 191.00 13.50 - 59.90 181.00 12.80 - 59.95 168.00 12.70 - 60.00 181.00 12.80 - 60.05 158.00 12.20 - 60.10 160.00 12.00 - 60.15 151.00 12.00 - 60.20 171.00 12.40 - 60.25 167.00 12.60 - 60.30 160.00 12.00 - 60.35 157.00 12.10 - 60.40 172.00 12.40 - 60.45 140.00 11.50 - 60.50 172.00 12.40 - 60.55 150.00 11.90 - 60.60 179.00 12.70 - 60.65 153.00 12.00 - 60.70 170.00 12.40 - 60.75 184.00 13.10 - 60.80 158.00 11.90 - 60.85 177.00 12.90 - 60.90 159.00 12.00 - 60.95 157.00 12.20 - 61.00 168.00 12.30 - 61.05 154.00 12.00 - 61.10 170.00 12.40 - 61.15 147.00 11.80 - 61.20 161.00 12.10 - 61.25 175.00 12.90 - 61.30 170.00 12.40 - 61.35 153.00 12.10 - 61.40 165.00 12.30 - 61.45 164.00 12.50 - 61.50 174.00 12.60 - 61.55 160.00 12.40 - 61.60 188.00 13.20 - 61.65 182.00 13.30 - 61.70 197.00 13.50 - 61.75 163.00 12.60 - 61.80 176.00 12.80 - 61.85 157.00 12.40 - 61.90 166.00 12.40 - 61.95 173.00 13.10 - 62.00 167.00 12.50 - 62.05 175.00 13.20 - 62.10 143.00 11.60 - 62.15 148.00 12.10 - 62.20 178.00 13.00 - 62.25 180.00 13.40 - 62.30 141.00 11.60 - 62.35 202.00 14.30 - 62.40 172.00 12.80 - 62.45 169.00 13.00 - 62.50 143.00 11.80 - 62.55 146.00 12.20 - 62.60 169.00 12.80 - 62.65 146.00 12.30 - 62.70 156.00 12.30 - 62.75 147.00 12.30 - 62.80 158.00 12.40 - 62.85 178.00 13.50 - 62.90 163.00 12.60 - 62.95 168.00 13.10 - 63.00 164.00 12.60 - 63.05 180.00 13.60 - 63.10 189.00 13.60 - 63.15 164.00 12.90 - 63.20 181.00 13.20 - 63.25 179.00 13.50 - 63.30 147.00 11.90 - 63.35 179.00 13.50 - 63.40 150.00 12.00 - 63.45 168.00 12.90 - 63.50 156.00 12.20 - 63.55 181.00 13.40 - 63.60 170.00 12.70 - 63.65 181.00 13.30 - 63.70 184.00 13.10 - 63.75 153.00 12.20 - 63.80 166.00 12.40 - 63.85 166.00 12.60 - 63.90 169.00 12.50 - 63.95 175.00 12.90 - 64.00 157.00 12.00 - 64.05 165.00 12.40 - 64.10 169.00 12.30 - 64.15 164.00 12.40 - 64.20 181.00 12.80 - 64.25 189.00 13.30 - 64.30 179.00 12.60 - 64.35 157.00 12.10 - 64.40 189.00 13.00 - 64.45 167.00 12.50 - 64.50 178.00 12.50 - 64.55 144.00 11.60 - 64.60 180.00 12.60 - 64.65 182.00 12.90 - 64.70 199.00 13.20 - 64.75 172.00 12.60 - 64.80 191.00 12.90 - 64.85 166.00 12.30 - 64.90 157.00 11.70 - 64.95 197.00 13.50 - 65.00 204.00 13.40 - 65.05 183.00 13.00 - 65.10 189.00 12.90 - 65.15 189.00 13.20 - 65.20 170.00 12.20 - 65.25 188.00 13.20 - 65.30 176.00 12.40 - 65.35 172.00 12.60 - 65.40 182.00 12.70 - 65.45 205.00 13.80 - 65.50 191.00 13.00 - 65.55 192.00 13.30 - 65.60 190.00 12.90 - 65.65 194.00 13.40 - 65.70 212.00 13.70 - 65.75 221.00 14.30 - 65.80 227.00 14.20 - 65.85 227.00 14.60 - 65.90 239.00 14.60 - 65.95 261.00 15.60 - 66.00 301.00 16.40 - 66.05 409.00 19.60 - 66.10 559.00 22.30 - 66.15 820.00 27.80 - 66.20 1276.00 33.90 - 66.25 1776.00 41.00 - 66.30 2322.00 45.70 - 66.35 2880.00 52.20 - 66.40 3051.00 52.50 - 66.45 2980.00 53.10 - 66.50 2572.00 48.20 - 66.55 1961.00 43.20 - 66.60 1315.00 34.50 - 66.65 919.00 29.60 - 66.70 548.00 22.40 - 66.75 405.00 19.70 - 66.80 299.00 16.50 - 66.85 309.00 17.20 - 66.90 279.00 15.90 - 66.95 281.00 16.40 - 67.00 235.00 14.70 - 67.05 239.00 15.10 - 67.10 212.00 14.00 - 67.15 228.00 14.80 - 67.20 231.00 14.50 - 67.25 198.00 13.80 - 67.30 223.00 14.30 - 67.35 201.00 13.90 - 67.40 208.00 13.80 - 67.45 207.00 14.10 - 67.50 217.00 14.10 - 67.55 196.00 13.70 - 67.60 182.00 12.90 - 67.65 182.00 13.20 - 67.70 186.00 13.10 - 67.75 176.00 13.00 - 67.80 192.00 13.30 - 67.85 215.00 14.50 - 67.90 178.00 12.90 - 67.95 191.00 13.70 - 68.00 178.00 12.90 - 68.05 185.00 13.50 - 68.10 171.00 12.70 - 68.15 174.00 13.30 - 68.20 193.00 13.60 - 68.25 182.00 13.60 - 68.30 178.00 13.10 - 68.35 196.00 14.10 - 68.40 178.00 13.10 - 68.45 173.00 13.30 - 68.50 175.00 13.10 - 68.55 178.00 13.60 - 68.60 177.00 13.20 - 68.65 176.00 13.60 - 68.70 200.00 14.10 - 68.75 177.00 13.60 - 68.80 185.00 13.60 - 68.85 167.00 13.20 - 68.90 158.00 12.60 - 68.95 176.00 13.60 - 69.00 192.00 13.80 - 69.05 174.00 13.50 - 69.10 154.00 12.40 - 69.15 153.00 12.70 - 69.20 167.00 12.90 - 69.25 168.00 13.30 - 69.30 167.00 12.90 - 69.35 163.00 13.10 - 69.40 157.00 12.50 - 69.45 185.00 13.90 - 69.50 151.00 12.30 - 69.55 176.00 13.50 - 69.60 187.00 13.60 - 69.65 170.00 13.20 - 69.70 164.00 12.70 - 69.75 204.00 14.50 - 69.80 169.00 12.80 - 69.85 191.00 13.90 - 69.90 177.00 13.10 - 69.95 157.00 12.60 - 70.00 173.00 12.80 - 70.05 199.00 14.10 - 70.10 168.00 12.60 - 70.15 191.00 13.70 - 70.20 165.00 12.40 - 70.25 156.00 12.30 - 70.30 163.00 12.30 - 70.35 149.00 12.00 - 70.40 199.00 13.60 - 70.45 158.00 12.30 - 70.50 158.00 12.10 - 70.55 150.00 12.00 - 70.60 197.00 13.50 - 70.65 167.00 12.60 - 70.70 180.00 12.80 - 70.75 187.00 13.40 - 70.80 190.00 13.20 - 70.85 169.00 12.70 - 70.90 214.00 14.00 - 70.95 188.00 13.50 - 71.00 200.00 13.50 - 71.05 186.00 13.30 - 71.10 169.00 12.40 - 71.15 166.00 12.60 - 71.20 175.00 12.60 - 71.25 170.00 12.80 - 71.30 191.00 13.20 - 71.35 185.00 13.30 - 71.40 191.00 13.20 - 71.45 181.00 13.20 - 71.50 188.00 13.10 - 71.55 164.00 12.60 - 71.60 185.00 13.00 - 71.65 168.00 12.70 - 71.70 168.00 12.40 - 71.75 167.00 12.60 - 71.80 158.00 12.00 - 71.85 173.00 12.90 - 71.90 177.00 12.70 - 71.95 193.00 13.60 - 72.00 190.00 13.20 - 72.05 174.00 12.90 - 72.10 161.00 12.10 - 72.15 147.00 11.80 - 72.20 165.00 12.30 - 72.25 188.00 13.40 - 72.30 172.00 12.50 - 72.35 176.00 12.90 - 72.40 167.00 12.30 - 72.45 186.00 13.30 - 72.50 178.00 12.70 - 72.55 158.00 12.20 - 72.60 168.00 12.30 - 72.65 180.00 13.10 - 72.70 154.00 11.80 - 72.75 162.00 12.40 - 72.80 168.00 12.30 - 72.85 194.00 13.50 - 72.90 164.00 12.10 - 72.95 169.00 12.60 - 73.00 160.00 12.00 - 73.05 164.00 12.50 - 73.10 171.00 12.40 - 73.15 169.00 12.60 - 73.20 167.00 12.30 - 73.25 150.00 12.00 - 73.30 173.00 12.50 - 73.35 183.00 13.20 - 73.40 169.00 12.40 - 73.45 180.00 13.10 - 73.50 173.00 12.50 - 73.55 195.00 13.70 - 73.60 178.00 12.80 - 73.65 193.00 13.60 - 73.70 179.00 12.80 - 73.75 153.00 12.20 - 73.80 169.00 12.40 - 73.85 165.00 12.60 - 73.90 172.00 12.60 - 73.95 171.00 12.80 - 74.00 178.00 12.80 - 74.05 180.00 13.20 - 74.10 168.00 12.50 - 74.15 169.00 12.80 - 74.20 190.00 13.20 - 74.25 170.00 12.80 - 74.30 178.00 12.80 - 74.35 158.00 12.40 - 74.40 185.00 13.10 - 74.45 181.00 13.30 - 74.50 173.00 12.70 - 74.55 163.00 12.60 - 74.60 184.00 13.10 - 74.65 181.00 13.40 - 74.70 192.00 13.50 - 74.75 166.00 12.90 - 74.80 168.00 12.60 - 74.85 200.00 14.20 - 74.90 188.00 13.40 - 74.95 190.00 13.90 - 75.00 211.00 14.30 - 75.05 172.00 13.20 - 75.10 198.00 13.90 - 75.15 230.00 15.40 - 75.20 264.00 16.10 - 75.25 227.00 15.20 - 75.30 289.00 16.80 - 75.35 290.00 17.20 - 75.40 284.00 16.70 - 75.45 250.00 16.10 - 75.50 233.00 15.10 - 75.55 239.00 15.70 - 75.60 239.00 15.30 - 75.65 204.00 14.40 - 75.70 178.00 13.20 - 75.75 189.00 13.90 - 75.80 202.00 14.00 - 75.85 181.00 13.50 - 75.90 190.00 13.50 - 75.95 177.00 13.30 - 76.00 199.00 13.80 - 76.05 193.00 13.90 - 76.10 170.00 12.70 - 76.15 170.00 13.00 - 76.20 165.00 12.50 - 76.25 192.00 13.70 - 76.30 171.00 12.70 - 76.35 169.00 12.80 - 76.40 168.00 12.50 - 76.45 183.00 13.30 - 76.50 173.00 12.60 - 76.55 178.00 13.10 - 76.60 175.00 12.70 - 76.65 191.00 13.50 - 76.70 166.00 12.30 - 76.75 187.00 13.40 - 76.80 191.00 13.20 - 76.85 184.00 13.30 - 76.90 168.00 12.40 - 76.95 177.00 13.00 - 77.00 205.00 13.70 - 77.05 188.00 13.40 - 77.10 166.00 12.30 - 77.15 180.00 13.10 - 77.20 179.00 12.80 - 77.25 179.00 13.10 - 77.30 163.00 12.20 - 77.35 188.00 13.40 - 77.40 169.00 12.40 - 77.45 179.00 13.00 - 77.50 169.00 12.40 - 77.55 201.00 13.80 - 77.60 184.00 12.90 - 77.65 187.00 13.30 - 77.70 207.00 13.70 - 77.75 170.00 12.70 - 77.80 193.00 13.20 - 77.85 189.00 13.50 - 77.90 205.00 13.70 - 77.95 183.00 13.20 - 78.00 179.00 12.80 - 78.05 188.00 13.40 - 78.10 194.00 13.30 - 78.15 220.00 14.50 - 78.20 195.00 13.40 - 78.25 176.00 13.00 - 78.30 208.00 13.80 - 78.35 185.00 13.30 - 78.40 217.00 14.10 - 78.45 203.00 14.00 - 78.50 200.00 13.50 - 78.55 196.00 13.70 - 78.60 197.00 13.40 - 78.65 217.00 14.40 - 78.70 179.00 12.80 - 78.75 184.00 13.30 - 78.80 187.00 13.10 - 78.85 219.00 14.40 - 78.90 193.00 13.30 - 78.95 214.00 14.30 - 79.00 207.00 13.70 - 79.05 199.00 13.80 - 79.10 224.00 14.30 - 79.15 244.00 15.20 - 79.20 217.00 14.10 - 79.25 266.00 15.90 - 79.30 281.00 16.00 - 79.35 425.00 20.10 - 79.40 527.00 21.90 - 79.45 735.00 26.50 - 79.50 1057.00 31.10 - 79.55 1483.00 37.70 - 79.60 1955.00 42.20 - 79.65 2315.00 47.10 - 79.70 2552.00 48.30 - 79.75 2506.00 49.00 - 79.80 2261.00 45.50 - 79.85 1842.00 42.10 - 79.90 1328.00 34.90 - 79.95 911.00 29.60 - 80.00 592.00 23.40 - 80.05 430.00 20.40 - 80.10 312.00 17.00 - 80.15 284.00 16.60 - 80.20 285.00 16.20 - 80.25 247.00 15.50 - 80.30 250.00 15.20 - 80.35 231.00 15.00 - 80.40 272.00 15.90 - 80.45 235.00 15.20 - 80.50 188.00 13.20 - 80.55 223.00 14.80 - 80.60 218.00 14.30 - 80.65 221.00 14.80 - 80.70 210.00 14.10 - 80.75 199.00 14.00 - 80.80 207.00 14.00 - 80.85 208.00 14.40 - 80.90 178.00 13.00 - 80.95 194.00 14.00 - 81.00 202.00 13.90 - 81.05 226.00 15.10 - 81.10 209.00 14.20 - 81.15 194.00 14.10 - 81.20 179.00 13.20 - 81.25 183.00 13.70 - 81.30 187.00 13.50 - 81.35 198.00 14.30 - 81.40 198.00 14.00 - 81.45 209.00 14.70 - 81.50 187.00 13.60 - 81.55 211.00 14.90 - 81.60 198.00 14.10 - 81.65 164.00 13.10 - 81.70 200.00 14.10 - 81.75 212.00 14.90 - 81.80 197.00 14.00 - 81.85 191.00 14.20 - 81.90 195.00 14.00 - 81.95 217.00 15.10 - 82.00 189.00 13.80 - 82.05 182.00 13.80 - 82.10 174.00 13.20 - 82.15 182.00 13.80 - 82.20 199.00 14.00 - 82.25 179.00 13.60 - 82.30 197.00 13.90 - 82.35 228.00 15.30 - 82.40 170.00 12.90 - 82.45 203.00 14.40 - 82.50 232.00 15.10 - 82.55 178.00 13.50 - 82.60 216.00 14.50 - 82.65 205.00 14.30 - 82.70 185.00 13.30 - 82.75 212.00 14.60 - 82.80 199.00 13.70 - 82.85 169.00 12.90 - 82.90 165.00 12.50 - 82.95 203.00 14.10 - 83.00 215.00 14.20 - 83.05 199.00 13.90 - 83.10 200.00 13.60 - 83.15 174.00 12.90 - 83.20 192.00 13.30 - 83.25 206.00 14.10 - 83.30 191.00 13.20 - 83.35 203.00 13.90 - 83.40 210.00 13.90 - 83.45 194.00 13.60 - 83.50 245.00 14.90 - 83.55 242.00 15.10 - 83.60 255.00 15.20 - 83.65 310.00 17.10 - 83.70 408.00 19.20 - 83.75 498.00 21.70 - 83.80 729.00 25.60 - 83.85 934.00 29.60 - 83.90 1121.00 31.70 - 83.95 1320.00 35.20 - 84.00 1476.00 36.30 - 84.05 1276.00 34.60 - 84.10 1129.00 31.80 - 84.15 887.00 28.80 - 84.20 643.00 23.90 - 84.25 490.00 21.40 - 84.30 343.00 17.50 - 84.35 284.00 16.30 - 84.40 263.00 15.30 - 84.45 229.00 14.60 - 84.50 235.00 14.50 - 84.55 246.00 15.10 - 84.60 205.00 13.50 - 84.65 217.00 14.20 - 84.70 217.00 13.90 - 84.75 197.00 13.50 - 84.80 195.00 13.10 - 84.85 232.00 14.70 - 84.90 182.00 12.70 - 84.95 192.00 13.40 - 85.00 172.00 12.40 - 85.05 191.00 13.30 - 85.10 200.00 13.30 - 85.15 186.00 13.10 - 85.20 190.00 13.00 - 85.25 211.00 14.00 - 85.30 184.00 12.80 - 85.35 180.00 12.90 - 85.40 182.00 12.70 - 85.45 184.00 13.10 - 85.50 175.00 12.40 - 85.55 176.00 12.80 - 85.60 166.00 12.10 - 85.65 180.00 12.90 - 85.70 195.00 13.10 - 85.75 183.00 13.10 - 85.80 182.00 12.70 - 85.85 168.00 12.50 - 85.90 177.00 12.60 - 85.95 190.00 13.30 - 86.00 178.00 12.60 - 86.05 180.00 13.00 - 86.10 181.00 12.70 - 86.15 177.00 12.90 - 86.20 171.00 12.40 - 86.25 193.00 13.50 - 86.30 181.00 12.70 - 86.35 180.00 13.00 - 86.40 198.00 13.30 - 86.45 177.00 12.90 - 86.50 161.00 12.00 - 86.55 166.00 12.50 - 86.60 176.00 12.60 - 86.65 190.00 13.40 - 86.70 185.00 12.90 - 86.75 173.00 12.90 - 86.80 176.00 12.60 - 86.85 159.00 12.30 - 86.90 188.00 13.10 - 86.95 199.00 13.90 - 87.00 180.00 12.90 - 87.05 164.00 12.60 - 87.10 180.00 12.90 - 87.15 190.00 13.60 - 87.20 179.00 12.90 - 87.25 177.00 13.20 - 87.30 183.00 13.10 - 87.35 174.00 13.20 - 87.40 164.00 12.50 - 87.45 165.00 12.90 - 87.50 185.00 13.30 - 87.55 191.00 13.90 - 87.60 181.00 13.20 - 87.65 143.00 12.10 - 87.70 170.00 12.90 - 87.75 150.00 12.40 - 87.80 187.00 13.50 - 87.85 181.00 13.60 - 87.90 171.00 12.90 - 87.95 179.00 13.60 - 88.00 146.00 12.00 - 88.05 175.00 13.40 - 88.10 182.00 13.40 - 88.15 176.00 13.50 - 88.20 164.00 12.70 - 88.25 152.00 12.60 - 88.30 188.00 13.60 - 88.35 152.00 12.50 - 88.40 172.00 13.00 - 88.45 140.00 12.00 - 88.50 176.00 13.10 - 88.55 168.00 13.10 - 88.60 197.00 13.80 - 88.65 190.00 13.90 - 88.70 176.00 13.10 - 88.75 167.00 13.00 - 88.80 182.00 13.30 - 88.85 175.00 13.20 - 88.90 154.00 12.10 - 88.95 168.00 12.90 - 89.00 187.00 13.30 - 89.05 163.00 12.70 - 89.10 173.00 12.80 - 89.15 161.00 12.50 - 89.20 170.00 12.60 - 89.25 178.00 13.10 - 89.30 174.00 12.70 - 89.35 172.00 12.80 - 89.40 167.00 12.40 - 89.45 168.00 12.60 - 89.50 164.00 12.20 - 89.55 183.00 13.10 - 89.60 141.00 11.30 - 89.65 173.00 12.80 - 89.70 190.00 13.10 - 89.75 180.00 13.00 - 89.80 162.00 12.10 - 89.85 166.00 12.50 - 89.90 164.00 12.10 - 89.95 166.00 12.50 - 90.00 170.00 12.40 - 90.05 176.00 12.90 - 90.10 181.00 12.80 - 90.15 175.00 12.90 - 90.20 161.00 12.10 - 90.25 170.00 12.70 - 90.30 166.00 12.30 - 90.35 175.00 12.90 - 90.40 171.00 12.50 - 90.45 172.00 12.80 - 90.50 183.00 12.90 - 90.55 165.00 12.50 - 90.60 181.00 12.80 - 90.65 168.00 12.70 - 90.70 179.00 12.70 - 90.75 157.00 12.20 - 90.80 172.00 12.50 - 90.85 187.00 13.30 - 90.90 181.00 12.80 - 90.95 163.00 12.40 - 91.00 163.00 12.10 - 91.05 166.00 12.50 - 91.10 161.00 12.00 - 91.15 167.00 12.50 - 91.20 148.00 11.50 - 91.25 175.00 12.80 - 91.30 195.00 13.20 - 91.35 181.00 13.00 - 91.40 173.00 12.50 - 91.45 160.00 12.30 - 91.50 180.00 12.70 - 91.55 183.00 13.10 - 91.60 156.00 11.90 - 91.65 163.00 12.40 - 91.70 175.00 12.50 - 91.75 189.00 13.30 - 91.80 181.00 12.70 - 91.85 186.00 13.20 - 91.90 184.00 12.80 - 91.95 187.00 13.20 - 92.00 191.00 13.10 - 92.05 203.00 13.70 - 92.10 194.00 13.10 - 92.15 237.00 14.80 - 92.20 242.00 14.60 - 92.25 307.00 16.90 - 92.30 299.00 16.30 - 92.35 340.00 17.70 - 92.40 357.00 17.70 - 92.45 354.00 18.10 - 92.50 370.00 18.00 - 92.55 375.00 18.60 - 92.60 303.00 16.30 - 92.65 264.00 15.60 - 92.70 243.00 14.60 - 92.75 207.00 13.90 - 92.80 199.00 13.20 - 92.85 180.00 12.90 - 92.90 202.00 13.30 - 92.95 188.00 13.20 - 93.00 183.00 12.70 - 93.05 170.00 12.60 - 93.10 180.00 12.60 - 93.15 182.00 13.10 - 93.20 186.00 12.90 - 93.25 196.00 13.60 - 93.30 177.00 12.60 - 93.35 198.00 13.70 - 93.40 182.00 12.80 - 93.45 183.00 13.20 - 93.50 184.00 12.90 - 93.55 181.00 13.20 - 93.60 190.00 13.20 - 93.65 176.00 13.10 - 93.70 197.00 13.50 - 93.75 174.00 13.10 - 93.80 159.00 12.20 - 93.85 171.00 13.00 - 93.90 159.00 12.20 - 93.95 170.00 13.00 - 94.00 172.00 12.70 - 94.05 159.00 12.60 - 94.10 160.00 12.30 - 94.15 173.00 13.20 - 94.20 147.00 11.90 - 94.25 143.00 12.00 - 94.30 150.00 12.00 - 94.35 155.00 12.50 - 94.40 160.00 12.40 - 94.45 155.00 12.60 - 94.50 176.00 13.00 - 94.55 198.00 14.20 - 94.60 179.00 13.20 - 94.65 161.00 12.80 - 94.70 175.00 13.10 - 94.75 157.00 12.70 - 94.80 173.00 13.00 - 94.85 168.00 13.10 - 94.90 171.00 12.90 - 94.95 173.00 13.20 - 95.00 183.00 13.30 - 95.05 148.00 12.20 - 95.10 160.00 12.40 - 95.15 171.00 13.10 - 95.20 167.00 12.60 - 95.25 195.00 13.90 - 95.30 175.00 12.90 - 95.35 200.00 14.10 - 95.40 176.00 12.90 - 95.45 175.00 13.10 - 95.50 194.00 13.50 - 95.55 190.00 13.60 - 95.60 154.00 12.00 - 95.65 166.00 12.70 - 95.70 164.00 12.30 - 95.75 166.00 12.60 - 95.80 162.00 12.20 - 95.85 183.00 13.20 - 95.90 149.00 11.60 - 95.95 171.00 12.80 - 96.00 165.00 12.30 - 96.05 181.00 13.10 - 96.10 188.00 13.00 - 96.15 184.00 13.20 - 96.20 162.00 12.10 - 96.25 163.00 12.40 - 96.30 165.00 12.20 - 96.35 183.00 13.10 - 96.40 182.00 12.80 - 96.45 156.00 12.10 - 96.50 159.00 11.90 - 96.55 139.00 11.40 - 96.60 165.00 12.10 - 96.65 164.00 12.40 - 96.70 184.00 12.80 - 96.75 159.00 12.10 - 96.80 159.00 11.90 - 96.85 155.00 12.00 - 96.90 162.00 12.00 - 96.95 157.00 12.00 - 97.00 160.00 11.90 - 97.05 168.00 12.50 - 97.10 168.00 12.20 - 97.15 151.00 11.80 - 97.20 162.00 11.90 - 97.25 163.00 12.20 - 97.30 166.00 12.10 - 97.35 161.00 12.20 - 97.40 158.00 11.80 - 97.45 151.00 11.80 - 97.50 163.00 12.00 - 97.55 179.00 12.80 - 97.60 166.00 12.10 - 97.65 155.00 11.90 - 97.70 160.00 11.80 - 97.75 152.00 11.80 - 97.80 184.00 12.70 - 97.85 175.00 12.60 - 97.90 161.00 11.80 - 97.95 166.00 12.30 - 98.00 150.00 11.40 - 98.05 179.00 12.80 - 98.10 184.00 12.70 - 98.15 151.00 11.80 - 98.20 173.00 12.30 - 98.25 164.00 12.30 - 98.30 178.00 12.50 - 98.35 176.00 12.80 - 98.40 162.00 11.90 - 98.45 173.00 12.70 - 98.50 154.00 11.60 - 98.55 184.00 13.10 - 98.60 142.00 11.20 - 98.65 184.00 13.00 - 98.70 156.00 11.70 - 98.75 177.00 12.80 - 98.80 163.00 12.00 - 98.85 173.00 12.70 - 98.90 180.00 12.70 - 98.95 181.00 13.00 - 99.00 165.00 12.10 - 99.05 177.00 12.90 - 99.10 155.00 11.80 - 99.15 147.00 11.70 - 99.20 163.00 12.10 - 99.25 172.00 12.70 - 99.30 145.00 11.40 - 99.35 156.00 12.10 - 99.40 161.00 12.00 - 99.45 189.00 13.50 - 99.50 182.00 12.90 - 99.55 172.00 12.80 - 99.60 176.00 12.70 - 99.65 166.00 12.60 - 99.70 190.00 13.20 - 99.75 154.00 12.20 - 99.80 198.00 13.50 - 99.85 152.00 12.20 - 99.90 160.00 12.20 - 99.95 174.00 13.00 - 100.00 187.00 13.20 - 100.05 178.00 13.20 - 100.10 149.00 11.80 - 100.15 171.00 13.00 - 100.20 185.00 13.20 - 100.25 207.00 14.40 - 100.30 184.00 13.20 - 100.35 187.00 13.70 - 100.40 231.00 14.90 - 100.45 226.00 15.10 - 100.50 203.00 14.00 - 100.55 214.00 14.80 - 100.60 279.00 16.50 - 100.65 319.00 18.10 - 100.70 397.00 19.70 - 100.75 435.00 21.20 - 100.80 539.00 23.00 - 100.85 665.00 26.30 - 100.90 724.00 26.80 - 100.95 723.00 27.50 - 101.00 783.00 27.90 - 101.05 719.00 27.50 - 101.10 585.00 24.20 - 101.15 465.00 22.10 - 101.20 371.00 19.30 - 101.25 328.00 18.50 - 101.30 277.00 16.70 - 101.35 248.00 16.10 - 101.40 209.00 14.40 - 101.45 221.00 15.10 - 101.50 198.00 14.00 - 101.55 203.00 14.50 - 101.60 188.00 13.60 - 101.65 207.00 14.50 - 101.70 195.00 13.80 - 101.75 170.00 13.10 - 101.80 192.00 13.60 - 101.85 172.00 13.10 - 101.90 185.00 13.30 - 101.95 183.00 13.40 - 102.00 211.00 14.10 - 102.05 147.00 12.00 - 102.10 176.00 12.80 - 102.15 186.00 13.40 - 102.20 171.00 12.60 - 102.25 169.00 12.70 - 102.30 192.00 13.20 - 102.35 215.00 14.30 - 102.40 146.00 11.50 - 102.45 169.00 12.60 - 102.50 188.00 13.10 - 102.55 175.00 12.80 - 102.60 165.00 12.20 - 102.65 184.00 13.10 - 102.70 172.00 12.40 - 102.75 179.00 13.00 - 102.80 163.00 12.10 - 102.85 167.00 12.50 - 102.90 179.00 12.70 - 102.95 171.00 12.70 - 103.00 181.00 12.70 - 103.05 171.00 12.70 - 103.10 180.00 12.70 - 103.15 173.00 12.80 - 103.20 167.00 12.20 - 103.25 186.00 13.20 - 103.30 176.00 12.50 - 103.35 191.00 13.40 - 103.40 170.00 12.30 - 103.45 167.00 12.50 - 103.50 165.00 12.10 - 103.55 182.00 13.00 - 103.60 173.00 12.40 - 103.65 186.00 13.20 - 103.70 161.00 12.00 - 103.75 166.00 12.40 - 103.80 157.00 11.80 - 103.85 170.00 12.50 - 103.90 183.00 12.70 - 103.95 179.00 12.90 - 104.00 164.00 12.00 - 104.05 169.00 12.50 - 104.10 161.00 11.90 - 104.15 156.00 12.00 - 104.20 163.00 12.00 - 104.25 174.00 12.70 - 104.30 161.00 11.90 - 104.35 169.00 12.50 - 104.40 158.00 11.80 - 104.45 180.00 12.90 - 104.50 171.00 12.30 - 104.55 165.00 12.30 - 104.60 163.00 12.00 - 104.65 172.00 12.60 - 104.70 164.00 12.00 - 104.75 174.00 12.60 - 104.80 178.00 12.50 - 104.85 154.00 11.90 - 104.90 176.00 12.40 - 104.95 142.00 11.40 - 105.00 163.00 12.00 - 105.05 177.00 12.80 - 105.10 194.00 13.00 - 105.15 176.00 12.70 - 105.20 207.00 13.50 - 105.25 158.00 12.10 - 105.30 151.00 11.50 - 105.35 183.00 13.00 - 105.40 159.00 11.80 - 105.45 179.00 12.90 - 105.50 170.00 12.20 - 105.55 192.00 13.30 - 105.60 160.00 11.90 - 105.65 168.00 12.40 - 105.70 183.00 12.70 - 105.75 163.00 12.30 - 105.80 162.00 11.90 - 105.85 182.00 12.90 - 105.90 154.00 11.60 - 105.95 180.00 12.90 - 106.00 168.00 12.20 - 106.05 166.00 12.40 - 106.10 155.00 11.70 - 106.15 190.00 13.30 - 106.20 165.00 12.10 - 106.25 163.00 12.30 - 106.30 183.00 12.80 - 106.35 165.00 12.50 - 106.40 173.00 12.50 - 106.45 163.00 12.50 - 106.50 151.00 11.70 - 106.55 198.00 13.80 - 106.60 165.00 12.20 - 106.65 157.00 12.30 - 106.70 159.00 12.10 - 106.75 177.00 13.10 - 106.80 156.00 12.00 - 106.85 182.00 13.40 - 106.90 181.00 13.00 - 106.95 158.00 12.50 - 107.00 176.00 12.80 - 107.05 163.00 12.70 - 107.10 156.00 12.10 - 107.15 213.00 14.60 - 107.20 172.00 12.80 - 107.25 170.00 13.00 - 107.30 168.00 12.60 - 107.35 169.00 13.00 - 107.40 169.00 12.70 - 107.45 168.00 13.00 - 107.50 155.00 12.10 - 107.55 164.00 12.80 - 107.60 168.00 12.70 - 107.65 144.00 12.00 - 107.70 166.00 12.60 - 107.75 172.00 13.10 - 107.80 156.00 12.20 - 107.85 154.00 12.40 - 107.90 143.00 11.60 - 107.95 152.00 12.30 - 108.00 174.00 12.80 - 108.05 168.00 12.80 - 108.10 164.00 12.40 - 108.15 160.00 12.50 - 108.20 176.00 12.80 - 108.25 174.00 13.00 - 108.30 175.00 12.70 - 108.35 163.00 12.60 - 108.40 169.00 12.50 - 108.45 180.00 13.10 - 108.50 159.00 12.00 - 108.55 173.00 12.80 - 108.60 148.00 11.60 - 108.65 169.00 12.60 - 108.70 167.00 12.30 - 108.75 168.00 12.50 - 108.80 175.00 12.50 - 108.85 163.00 12.30 - 108.90 164.00 12.10 - 108.95 189.00 13.30 - 109.00 192.00 13.10 - 109.05 181.00 13.00 - 109.10 202.00 13.40 - 109.15 190.00 13.30 - 109.20 163.00 12.00 - 109.25 216.00 14.10 - 109.30 220.00 14.00 - 109.35 230.00 14.60 - 109.40 255.00 15.00 - 109.45 253.00 15.30 - 109.50 273.00 15.50 - 109.55 296.00 16.50 - 109.60 300.00 16.30 - 109.65 331.00 17.50 - 109.70 347.00 17.50 - 109.75 349.00 18.00 - 109.80 341.00 17.40 - 109.85 332.00 17.50 - 109.90 298.00 16.20 - 109.95 259.00 15.50 - 110.00 227.00 14.10 - 110.05 203.00 13.70 - 110.10 222.00 14.00 - 110.15 175.00 12.70 - 110.20 183.00 12.70 - 110.25 197.00 13.50 - 110.30 176.00 12.40 - 110.35 179.00 12.90 - 110.40 176.00 12.50 - 110.45 178.00 12.80 - 110.50 210.00 13.60 - 110.55 181.00 13.00 - 110.60 167.00 12.20 - 110.65 165.00 12.40 - 110.70 172.00 12.30 - 110.75 175.00 12.80 - 110.80 177.00 12.50 - 110.85 194.00 13.40 - 110.90 171.00 12.30 - 110.95 177.00 12.80 - 111.00 188.00 12.90 - 111.05 175.00 12.80 - 111.10 194.00 13.10 - 111.15 179.00 12.90 - 111.20 171.00 12.30 - 111.25 165.00 12.40 - 111.30 183.00 12.70 - 111.35 184.00 13.00 - 111.40 187.00 12.90 - 111.45 178.00 12.80 - 111.50 172.00 12.30 - 111.55 179.00 12.90 - 111.60 205.00 13.40 - 111.65 168.00 12.50 - 111.70 161.00 11.90 - 111.75 182.00 13.00 - 111.80 167.00 12.20 - 111.85 193.00 13.40 - 111.90 188.00 12.90 - 111.95 204.00 13.80 - 112.00 179.00 12.60 - 112.05 176.00 12.80 - 112.10 185.00 12.80 - 112.15 174.00 12.70 - 112.20 175.00 12.50 - 112.25 198.00 13.60 - 112.30 199.00 13.30 - 112.35 207.00 13.90 - 112.40 204.00 13.50 - 112.45 180.00 13.00 - 112.50 137.00 11.10 - 112.55 179.00 13.00 - 112.60 183.00 12.80 - 112.65 166.00 12.60 - 112.70 166.00 12.30 - 112.75 189.00 13.40 - 112.80 181.00 12.80 - 112.85 194.00 13.60 - 112.90 171.00 12.50 - 112.95 202.00 13.90 - 113.00 216.00 14.10 - 113.05 198.00 14.00 - 113.10 189.00 13.30 - 113.15 170.00 13.00 - 113.20 182.00 13.10 - 113.25 195.00 14.00 - 113.30 177.00 13.00 - 113.35 180.00 13.50 - 113.40 195.00 13.70 - 113.45 201.00 14.30 - 113.50 203.00 14.00 - 113.55 200.00 14.30 - 113.60 209.00 14.20 - 113.65 231.00 15.40 - 113.70 281.00 16.60 - 113.75 287.00 17.20 - 113.80 324.00 17.80 - 113.85 395.00 20.20 - 113.90 457.00 21.20 - 113.95 580.00 24.40 - 114.00 685.00 26.00 - 114.05 873.00 30.00 - 114.10 964.00 30.80 - 114.15 1126.00 34.00 - 114.20 1266.00 35.20 - 114.25 1307.00 36.50 - 114.30 1221.00 34.50 - 114.35 1096.00 33.30 - 114.40 978.00 30.70 - 114.45 792.00 28.20 - 114.50 600.00 24.00 - 114.55 487.00 22.00 - 114.60 358.00 18.50 - 114.65 279.00 16.60 - 114.70 265.00 15.80 - 114.75 258.00 15.90 - 114.80 244.00 15.10 - 114.85 226.00 14.80 - 114.90 227.00 14.50 - 114.95 188.00 13.50 - 115.00 195.00 13.40 - 115.05 211.00 14.20 - 115.10 205.00 13.70 - 115.15 198.00 13.70 - 115.20 218.00 14.00 - 115.25 200.00 13.70 - 115.30 200.00 13.40 - 115.35 188.00 13.30 - 115.40 209.00 13.70 - 115.45 184.00 13.10 - 115.50 186.00 12.90 - 115.55 202.00 13.70 - 115.60 183.00 12.70 - 115.65 187.00 13.10 - 115.70 182.00 12.60 - 115.75 185.00 13.10 - 115.80 213.00 13.70 - 115.85 177.00 12.80 - 115.90 199.00 13.20 - 115.95 185.00 13.00 - 116.00 184.00 12.70 - 116.05 191.00 13.30 - 116.10 173.00 12.30 - 116.15 196.00 13.50 - 116.20 201.00 13.30 - 116.25 173.00 12.70 - 116.30 178.00 12.60 - 116.35 161.00 12.30 - 116.40 208.00 13.60 - 116.45 183.00 13.10 - 116.50 183.00 12.80 - 116.55 173.00 12.80 - 116.60 184.00 12.80 - 116.65 215.00 14.20 - 116.70 201.00 13.40 - 116.75 193.00 13.40 - 116.80 190.00 13.00 - 116.85 216.00 14.20 - 116.90 195.00 13.10 - 116.95 203.00 13.80 - 117.00 183.00 12.80 - 117.05 203.00 13.70 - 117.10 187.00 12.90 - 117.15 216.00 14.20 - 117.20 191.00 13.00 - 117.25 189.00 13.30 - 117.30 189.00 13.00 - 117.35 226.00 14.50 - 117.40 185.00 12.90 - 117.45 194.00 13.50 - 117.50 185.00 12.80 - 117.55 213.00 14.10 - 117.60 197.00 13.30 - 117.65 198.00 14.50 - 117.70 168.00 13.00 - 117.75 209.00 14.90 - 117.80 185.00 13.70 - 117.85 208.00 14.90 - 117.90 213.00 14.70 - 117.95 203.00 14.70 - 118.00 225.00 15.10 - 118.05 214.00 15.10 - 118.10 233.00 15.40 - 118.15 245.00 16.20 - 118.20 236.00 15.50 - 118.25 245.00 16.20 - 118.30 305.00 17.60 - 118.35 287.00 17.10 - 118.40 317.00 17.40 - 118.45 421.00 20.60 - 118.50 422.00 20.10 - 118.55 590.00 24.40 - 118.60 701.00 26.80 - 118.65 861.00 28.60 - 118.70 1054.00 31.00 - 118.75 1232.00 34.30 - 118.80 1483.00 36.80 - 118.85 1694.00 40.30 - 118.90 1819.00 40.80 - 118.95 1845.00 42.30 - 119.00 1866.00 41.50 - 119.05 1726.00 41.00 - 119.10 1492.00 37.20 - 119.15 1232.00 34.80 - 119.20 971.00 30.10 - 119.25 753.00 27.20 - 119.30 626.00 24.20 - 119.35 487.00 21.90 - 119.40 409.00 19.60 - 119.45 342.00 18.50 - 119.50 307.00 17.10 - 119.55 296.00 17.20 - 119.60 231.00 14.90 - 119.65 246.00 15.80 - 119.70 220.00 14.50 - 119.75 255.00 16.10 - 119.80 214.00 14.40 - 119.85 247.00 15.90 - 119.90 238.00 15.20 - 119.95 218.00 15.00 - 120.00 222.00 14.70 - 120.05 218.00 15.00 - 120.10 253.00 15.80 - 120.15 197.00 14.30 - 120.20 190.00 13.60 - 120.25 221.00 15.10 - 120.30 204.00 14.20 - 120.35 206.00 14.60 - 120.40 189.00 13.60 - 120.45 231.00 15.40 - 120.50 190.00 13.60 - 120.55 191.00 13.90 - 120.60 211.00 14.30 - 120.65 204.00 14.30 - 120.70 200.00 13.90 - 120.75 199.00 14.10 - 120.80 190.00 13.50 - 120.85 195.00 13.90 - 120.90 179.00 13.00 - 120.95 189.00 13.60 - 121.00 190.00 13.30 - 121.05 195.00 13.80 - 121.10 193.00 13.40 - 121.15 173.00 12.80 - 121.20 183.00 13.00 - 121.25 181.00 13.10 - 121.30 203.00 13.50 - 121.35 177.00 12.90 - 121.40 201.00 13.40 - 121.45 179.00 12.90 - 121.50 179.00 12.60 - 121.55 194.00 13.40 - 121.60 158.00 11.90 - 121.65 195.00 13.40 - 121.70 201.00 13.40 - 121.75 192.00 13.40 - 121.80 189.00 13.00 - 121.85 186.00 13.10 - 121.90 170.00 12.30 - 121.95 166.00 12.40 - 122.00 185.00 12.80 - 122.05 197.00 13.60 - 122.10 177.00 12.60 - 122.15 198.00 13.60 - 122.20 174.00 12.50 - 122.25 171.00 12.60 - 122.30 190.00 13.00 - 122.35 214.00 14.20 - 122.40 189.00 13.00 - 122.45 174.00 12.80 - 122.50 171.00 12.40 - 122.55 163.00 12.40 - 122.60 174.00 12.40 - 122.65 177.00 12.80 - 122.70 180.00 12.60 - 122.75 186.00 13.10 - 122.80 190.00 13.00 - 122.85 170.00 12.60 - 122.90 175.00 12.50 - 122.95 194.00 13.40 - 123.00 175.00 12.50 - 123.05 194.00 13.40 - 123.10 189.00 12.90 - 123.15 222.00 14.30 - 123.20 178.00 12.50 - 123.25 158.00 12.10 - 123.30 191.00 13.00 - 123.35 184.00 13.00 - 123.40 190.00 12.90 - 123.45 183.00 13.00 - 123.50 178.00 12.50 - 123.55 204.00 13.70 - 123.60 192.00 13.00 - 123.65 200.00 13.50 - 123.70 182.00 12.60 - 123.75 171.00 12.50 - 123.80 186.00 12.70 - 123.85 197.00 13.40 - 123.90 174.00 12.30 - 123.95 167.00 12.30 - 124.00 178.00 12.40 - 124.05 198.00 13.40 - 124.10 205.00 13.30 - 124.15 216.00 14.00 - 124.20 200.00 13.20 - 124.25 204.00 13.60 - 124.30 190.00 12.80 - 124.35 188.00 13.10 - 124.40 191.00 12.90 - 124.45 186.00 13.00 - 124.50 175.00 12.30 - 124.55 175.00 12.60 - 124.60 174.00 12.30 - 124.65 194.00 13.30 - 124.70 181.00 12.50 - 124.75 161.00 12.10 - 124.80 186.00 12.70 - 124.85 200.00 13.50 - 124.90 168.00 12.10 - 124.95 177.00 12.70 - 125.00 188.00 12.80 - 125.05 177.00 12.70 - 125.10 163.00 11.90 - 125.15 175.00 12.70 - 125.20 188.00 12.80 - 125.25 176.00 12.80 - 125.30 172.00 12.30 - 125.35 172.00 12.60 - 125.40 181.00 12.70 - 125.45 186.00 13.20 - 125.50 181.00 12.70 - 125.55 193.00 13.40 - 125.60 177.00 12.60 - 125.65 176.00 12.90 - 125.70 194.00 13.20 - 125.75 179.00 13.00 - 125.80 147.00 11.50 - 125.85 186.00 13.30 - 125.90 182.00 12.90 - 125.95 165.00 12.70 - 126.00 164.00 12.30 - 126.05 199.00 13.90 - 126.10 167.00 12.40 - 126.15 184.00 13.40 - 126.20 203.00 13.80 - 126.25 190.00 13.70 - 126.30 182.00 13.10 - 126.35 180.00 13.40 - 126.40 179.00 13.00 - 126.45 179.00 13.40 - 126.50 170.00 12.70 - 126.55 176.00 13.30 - 126.60 178.00 13.10 - 126.65 185.00 13.70 - 126.70 193.00 13.60 - 126.75 192.00 14.00 - 126.80 198.00 13.80 - 126.85 195.00 14.00 - 126.90 165.00 12.60 - 126.95 189.00 13.80 - 127.00 175.00 13.00 - 127.05 176.00 13.30 - 127.10 184.00 13.30 - 127.15 179.00 13.40 - 127.20 187.00 13.40 - 127.25 176.00 13.20 - 127.30 191.00 13.50 - 127.35 194.00 13.90 - 127.40 177.00 12.90 - 127.45 177.00 13.20 - 127.50 180.00 13.00 - 127.55 158.00 12.40 - 127.60 193.00 13.40 - 127.65 177.00 13.10 - 127.70 185.00 13.10 - 127.75 178.00 13.10 - 127.80 184.00 13.00 - 127.85 188.00 13.40 - 127.90 182.00 12.90 - 127.95 190.00 13.50 - 128.00 191.00 13.20 - 128.05 165.00 12.50 - 128.10 174.00 12.50 - 128.15 158.00 12.20 - 128.20 197.00 13.30 - 128.25 183.00 13.10 - 128.30 196.00 13.30 - 128.35 166.00 12.50 - 128.40 218.00 14.00 - 128.45 206.00 13.80 - 128.50 184.00 12.80 - 128.55 176.00 12.70 - 128.60 198.00 13.20 - 128.65 215.00 14.10 - 128.70 179.00 12.60 - 128.75 192.00 13.30 - 128.80 201.00 13.30 - 128.85 221.00 14.20 - 128.90 227.00 14.10 - 128.95 229.00 14.40 - 129.00 254.00 14.90 - 129.05 256.00 15.30 - 129.10 272.00 15.40 - 129.15 239.00 14.80 - 129.20 228.00 14.10 - 129.25 255.00 15.20 - 129.30 213.00 13.60 - 129.35 203.00 13.60 - 129.40 228.00 14.10 - 129.45 220.00 14.10 - 129.50 185.00 12.60 - 129.55 192.00 13.20 - 129.60 187.00 12.70 - 129.65 182.00 12.80 - 129.70 209.00 13.40 - 129.75 173.00 12.50 - 129.80 202.00 13.20 - 129.85 178.00 12.70 - 129.90 189.00 12.80 - 129.95 177.00 12.60 - 130.00 177.00 12.30 - 130.05 190.00 13.10 - 130.10 178.00 12.40 - 130.15 177.00 12.60 - 130.20 164.00 11.90 - 130.25 185.00 12.90 - 130.30 153.00 11.40 - 130.35 174.00 12.50 - 130.40 197.00 13.00 - 130.45 192.00 13.10 - 130.50 174.00 12.20 - 130.55 177.00 12.60 - 130.60 172.00 12.10 - 130.65 173.00 12.50 - 130.70 178.00 12.40 - 130.75 180.00 12.80 - 130.80 203.00 13.20 - 130.85 192.00 13.20 - 130.90 184.00 12.60 - 130.95 197.00 13.30 - 131.00 169.00 12.10 - 131.05 187.00 13.00 - 131.10 175.00 12.30 - 131.15 177.00 12.60 - 131.20 199.00 13.10 - 131.25 180.00 12.80 - 131.30 203.00 13.20 - 131.35 175.00 12.60 - 131.40 183.00 12.50 - 131.45 192.00 13.20 - 131.50 174.00 12.30 - 131.55 180.00 12.80 - 131.60 179.00 12.50 - 131.65 191.00 13.20 - 131.70 182.00 12.60 - 131.75 174.00 12.60 - 131.80 191.00 12.90 - 131.85 195.00 13.40 - 131.90 171.00 12.30 - 131.95 198.00 13.60 - 132.00 193.00 13.10 - 132.05 175.00 12.80 - 132.10 207.00 13.60 - 132.15 189.00 13.40 - 132.20 174.00 12.50 - 132.25 196.00 13.70 - 132.30 175.00 12.60 - 132.35 196.00 13.80 - 132.40 183.00 13.00 - 132.45 198.00 13.80 - 132.50 196.00 13.40 - 132.55 169.00 12.90 - 132.60 189.00 13.30 - 132.65 171.00 13.00 - 132.70 193.00 13.50 - 132.75 170.00 13.00 - 132.80 175.00 12.90 - 132.85 166.00 12.90 - 132.90 188.00 13.40 - 132.95 186.00 13.70 - 133.00 165.00 12.60 - 133.05 201.00 14.20 - 133.10 182.00 13.20 - 133.15 151.00 12.40 - 133.20 156.00 12.20 - 133.25 187.00 13.70 - 133.30 153.00 12.10 - 133.35 193.00 14.00 - 133.40 200.00 13.90 - 133.45 165.00 12.90 - 133.50 172.00 12.90 - 133.55 162.00 12.70 - 133.60 165.00 12.50 - 133.65 218.00 14.70 - 133.70 197.00 13.60 - 133.75 206.00 14.20 - 133.80 186.00 13.20 - 133.85 162.00 12.50 - 133.90 176.00 12.80 - 133.95 174.00 12.90 - 134.00 196.00 13.40 - 134.05 174.00 12.90 - 134.10 177.00 12.70 - 134.15 183.00 13.10 - 134.20 184.00 12.90 - 134.25 185.00 13.10 - 134.30 200.00 13.40 - 134.35 175.00 12.70 - 134.40 190.00 13.00 - 134.45 195.00 13.40 - 134.50 192.00 13.00 - 134.55 171.00 12.50 - 134.60 194.00 13.00 - 134.65 190.00 13.10 - 134.70 165.00 12.00 - 134.75 192.00 13.20 - 134.80 160.00 11.70 - 134.85 192.00 13.10 - 134.90 181.00 12.50 - 134.95 208.00 13.70 - 135.00 179.00 12.40 - 135.05 172.00 12.40 - 135.10 183.00 12.50 - 135.15 187.00 12.90 - 135.20 185.00 12.50 - 135.25 182.00 12.70 - 135.30 184.00 12.50 - 135.35 163.00 11.90 - 135.40 201.00 13.00 - 135.45 189.00 12.80 - 135.50 204.00 13.10 - 135.55 178.00 12.50 - 135.60 178.00 12.20 - 135.65 193.00 13.00 - 135.70 215.00 13.40 - 135.75 203.00 13.30 - 135.80 216.00 13.40 - 135.85 165.00 12.10 - 135.90 196.00 12.80 - 135.95 178.00 12.50 - 136.00 170.00 11.90 - 136.05 173.00 12.40 - 136.10 188.00 12.60 - 136.15 176.00 12.50 - 136.20 186.00 12.50 - 136.25 189.00 12.90 - 136.30 166.00 11.80 - 136.35 177.00 12.50 - 136.40 169.00 11.90 - 136.45 171.00 12.30 - 136.50 194.00 12.80 - 136.55 187.00 12.90 - 136.60 162.00 11.70 - 136.65 160.00 11.90 - 136.70 183.00 12.40 - 136.75 150.00 11.50 - 136.80 180.00 12.40 - 136.85 194.00 13.20 - 136.90 185.00 12.60 - 136.95 158.00 11.90 - 137.00 193.00 12.90 - 137.05 165.00 12.20 - 137.10 178.00 12.30 - 137.15 183.00 12.90 - 137.20 180.00 12.40 - 137.25 176.00 12.70 - 137.30 183.00 12.60 - 137.35 189.00 13.20 - 137.40 180.00 12.50 - 137.45 160.00 12.20 - 137.50 202.00 13.30 - 137.55 201.00 13.60 - 137.60 173.00 12.30 - 137.65 176.00 12.80 - 137.70 195.00 13.10 - 137.75 197.00 13.50 - 137.80 186.00 12.80 - 137.85 183.00 13.00 - 137.90 175.00 12.40 - 137.95 178.00 12.80 - 138.00 190.00 12.90 - 138.05 174.00 12.70 - 138.10 163.00 12.00 - 138.15 190.00 13.30 - 138.20 169.00 12.20 - 138.25 198.00 13.60 - 138.30 199.00 13.30 - 138.35 184.00 13.10 - 138.40 216.00 13.90 - 138.45 183.00 13.10 - 138.50 200.00 13.40 - 138.55 186.00 13.30 - 138.60 177.00 12.70 - 138.65 186.00 13.40 - 138.70 193.00 13.30 - 138.75 200.00 14.00 - 138.80 180.00 12.90 - 138.85 178.00 13.20 - 138.90 198.00 13.60 - 138.95 236.00 15.30 - 139.00 203.00 13.80 - 139.05 207.00 14.30 - 139.10 190.00 13.40 - 139.15 171.00 13.10 - 139.20 203.00 13.90 - 139.25 203.00 14.20 - 139.30 198.00 13.70 - 139.35 200.00 14.20 - 139.40 187.00 13.30 - 139.45 214.00 14.70 - 139.50 198.00 13.70 - 139.55 220.00 14.80 - 139.60 196.00 13.70 - 139.65 239.00 15.50 - 139.70 212.00 14.20 - 139.75 219.00 14.80 - 139.80 248.00 15.40 - 139.85 220.00 14.80 - 139.90 241.00 15.10 - 139.95 245.00 15.50 - 140.00 269.00 15.90 - 140.05 294.00 17.00 - 140.10 323.00 17.40 - 140.15 302.00 17.20 - 140.20 312.00 17.10 - 140.25 371.00 18.90 - 140.30 420.00 19.70 - 140.35 516.00 22.30 - 140.40 596.00 23.40 - 140.45 644.00 24.70 - 140.50 711.00 25.40 - 140.55 833.00 28.10 - 140.60 895.00 28.40 - 140.65 1010.00 30.70 - 140.70 1058.00 30.80 - 140.75 1183.00 33.10 - 140.80 1278.00 33.70 - 140.85 1298.00 34.60 - 140.90 1419.00 35.40 - 140.95 1381.00 35.60 - 141.00 1299.00 33.80 - 141.05 1371.00 35.40 - 141.10 1273.00 33.30 - 141.15 1131.00 32.10 - 141.20 992.00 29.40 - 141.25 918.00 28.90 - 141.30 832.00 26.90 - 141.35 655.00 24.50 - 141.40 629.00 23.50 - 141.45 522.00 21.90 - 141.50 472.00 20.30 - 141.55 409.00 19.30 - 141.60 371.00 18.00 - 141.65 325.00 17.30 - 141.70 306.00 16.30 - 141.75 270.00 15.70 - 141.80 238.00 14.40 - 141.85 231.00 14.50 - 141.90 232.00 14.20 - 141.95 223.00 14.30 - 142.00 221.00 13.90 - 142.05 244.00 14.90 - 142.10 228.00 14.10 - 142.15 212.00 13.90 - 142.20 226.00 14.00 - 142.25 197.00 13.40 - 142.30 204.00 13.30 - 142.35 189.00 13.10 - 142.40 201.00 13.20 - 142.45 226.00 14.30 - 142.50 210.00 13.50 - 142.55 213.00 13.90 - 142.60 202.00 13.30 - 142.65 206.00 13.70 - 142.70 189.00 12.80 - 142.75 213.00 13.90 - 142.80 193.00 12.90 - 142.85 206.00 13.70 - 142.90 204.00 13.30 - 142.95 188.00 13.10 - 143.00 221.00 13.80 - 143.05 203.00 13.60 - 143.10 192.00 12.90 - 143.15 197.00 13.40 - 143.20 187.00 12.70 - 143.25 206.00 13.70 - 143.30 197.00 13.10 - 143.35 182.00 12.80 - 143.40 186.00 12.70 - 143.45 228.00 14.40 - 143.50 201.00 13.20 - 143.55 176.00 12.60 - 143.60 193.00 12.90 - 143.65 200.00 13.50 - 143.70 189.00 12.80 - 143.75 198.00 13.40 - 143.80 188.00 12.80 - 143.85 169.00 12.40 - 143.90 183.00 12.60 - 143.95 198.00 13.40 - 144.00 156.00 11.60 - 144.05 172.00 12.50 - 144.10 190.00 12.80 - 144.15 166.00 12.30 - 144.20 163.00 11.90 - 144.25 184.00 13.00 - 144.30 182.00 12.60 - 144.35 173.00 12.60 - 144.40 182.00 12.60 - 144.45 183.00 13.00 - 144.50 186.00 12.80 - 144.55 195.00 13.40 - 144.60 204.00 13.40 - 144.65 179.00 13.00 - 144.70 192.00 13.10 - 144.75 213.00 14.10 - 144.80 187.00 12.90 - 144.85 194.00 13.50 - 144.90 185.00 12.90 - 144.95 183.00 13.20 - 145.00 192.00 13.20 - 145.05 201.00 13.90 - 145.10 211.00 13.90 - 145.15 163.00 12.50 - 145.20 202.00 13.60 - 145.25 197.00 13.80 - 145.30 183.00 13.00 - 145.35 177.00 13.20 - 145.40 188.00 13.20 - 145.45 158.00 12.50 - 145.50 184.00 13.20 - 145.55 162.00 12.70 - 145.60 169.00 12.70 - 145.65 171.00 13.10 - 145.70 188.00 13.40 - 145.75 167.00 13.00 - 145.80 182.00 13.20 - 145.85 197.00 14.10 - 145.90 179.00 13.10 - 145.95 172.00 13.20 - 146.00 163.00 12.50 - 146.05 172.00 13.10 - 146.10 178.00 13.00 - 146.15 179.00 13.40 - 146.20 171.00 12.80 - 146.25 189.00 13.70 - 146.30 190.00 13.40 - 146.35 185.00 13.50 - 146.40 169.00 12.60 - 146.45 165.00 12.70 - 146.50 185.00 13.10 - 146.55 158.00 12.40 - 146.60 190.00 13.30 - 146.65 165.00 12.60 - 146.70 173.00 12.60 - 146.75 206.00 14.10 - 146.80 170.00 12.50 - 146.85 193.00 13.60 - 146.90 167.00 12.30 - 146.95 182.00 13.10 - 147.00 191.00 13.20 - 147.05 175.00 12.90 - 147.10 184.00 12.90 - 147.15 163.00 12.40 - 147.20 174.00 12.50 - 147.25 176.00 12.90 - 147.30 163.00 12.10 - 147.35 174.00 12.80 - 147.40 155.00 11.80 - 147.45 153.00 12.00 - 147.50 190.00 13.00 - 147.55 190.00 13.30 - 147.60 169.00 12.30 - 147.65 189.00 13.30 - 147.70 177.00 12.60 - 147.75 167.00 12.50 - 147.80 163.00 12.00 - 147.85 196.00 13.50 - 147.90 175.00 12.50 - 147.95 146.00 11.60 - 148.00 170.00 12.20 - 148.05 179.00 12.90 - 148.10 182.00 12.60 - 148.15 175.00 12.70 - 148.20 171.00 12.30 - 148.25 201.00 13.60 - 148.30 181.00 12.60 - 148.35 152.00 11.80 - 148.40 194.00 13.00 - 148.45 160.00 12.20 - 148.50 179.00 12.50 - 148.55 181.00 12.90 - 148.60 175.00 12.40 - 148.65 178.00 12.80 - 148.70 186.00 12.80 - 148.75 195.00 13.40 - 148.80 166.00 12.00 - 148.85 184.00 13.00 - 148.90 215.00 13.70 - 148.95 183.00 12.90 - 149.00 184.00 12.60 - 149.05 174.00 12.60 - 149.10 175.00 12.30 - 149.15 171.00 12.50 - 149.20 166.00 12.00 - 149.25 188.00 13.00 - 149.30 165.00 11.90 - 149.35 184.00 12.90 - 149.40 181.00 12.60 - 149.45 174.00 12.60 - 149.50 178.00 12.40 - 149.55 191.00 13.20 - 149.60 181.00 12.50 - 149.65 174.00 12.60 - 149.70 180.00 12.50 - 149.75 177.00 12.70 - 149.80 164.00 11.90 - 149.85 203.00 13.60 - 149.90 178.00 12.40 - 149.95 162.00 12.20 - 150.00 192.00 12.90 - 150.05 164.00 12.20 - 150.10 151.00 11.40 - 150.15 170.00 12.50 - 150.20 166.00 12.00 - 150.25 194.00 13.30 - 150.30 168.00 12.10 - 150.35 173.00 12.50 - 150.40 175.00 12.30 - 150.45 193.00 13.30 - 150.50 177.00 12.40 - 150.55 185.00 13.00 - 150.60 178.00 12.40 - 150.65 178.00 12.70 - 150.70 179.00 12.50 - 150.75 180.00 12.90 - 150.80 169.00 12.20 - 150.85 177.00 12.80 - 150.90 159.00 11.80 - 150.95 167.00 12.40 - 151.00 180.00 12.60 - 151.05 158.00 12.20 - 151.10 173.00 12.40 - 151.15 172.00 12.70 - 151.20 163.00 12.10 - 151.25 168.00 12.60 - 151.30 166.00 12.20 - 151.35 179.00 13.00 - 151.40 159.00 12.00 - 151.45 173.00 12.90 - 151.50 170.00 12.40 - 151.55 151.00 12.10 - 151.60 174.00 12.60 - 151.65 182.00 13.20 - 151.70 182.00 12.90 - 151.75 172.00 12.90 - 151.80 157.00 12.00 - 151.85 156.00 12.30 - 151.90 168.00 12.50 - 151.95 194.00 13.80 - 152.00 177.00 12.80 - 152.05 170.00 12.90 - 152.10 169.00 12.60 - 152.15 173.00 13.00 - 152.20 161.00 12.30 - 152.25 169.00 12.90 - 152.30 167.00 12.50 - 152.35 194.00 13.80 - 152.40 150.00 11.90 - 152.45 159.00 12.50 - 152.50 181.00 13.10 - 152.55 180.00 13.30 - 152.60 193.00 13.40 - 152.65 192.00 13.70 - 152.70 152.00 11.90 - 152.75 159.00 12.50 - 152.80 147.00 11.70 - 152.85 190.00 13.60 - 152.90 167.00 12.40 - 152.95 193.00 13.60 - 153.00 159.00 12.10 - 153.05 195.00 13.60 - 153.10 172.00 12.50 - 153.15 148.00 11.90 - 153.20 174.00 12.50 - 153.25 194.00 13.50 - 153.30 159.00 11.90 - 153.35 190.00 13.30 - 153.40 181.00 12.70 - 153.45 159.00 12.10 - 153.50 168.00 12.20 - 153.55 175.00 12.70 - 153.60 184.00 12.70 - 153.65 200.00 13.50 - 153.70 161.00 11.90 - 153.75 162.00 12.10 - 153.80 152.00 11.50 - 153.85 177.00 12.70 - 153.90 173.00 12.20 - 153.95 184.00 12.90 - 154.00 169.00 12.10 - 154.05 163.00 12.10 - 154.10 177.00 12.40 - 154.15 171.00 12.50 - 154.20 180.00 12.50 - 154.25 201.00 13.40 - 154.30 206.00 13.30 - 154.35 181.00 12.70 - 154.40 170.00 12.00 - 154.45 177.00 12.60 - 154.50 196.00 12.90 - 154.55 201.00 13.40 - 154.60 161.00 11.70 - 154.65 179.00 12.60 - 154.70 185.00 12.50 - 154.75 167.00 12.10 - 154.80 162.00 11.70 - 154.85 178.00 12.60 - 154.90 203.00 13.10 - 154.95 193.00 13.10 - 155.00 164.00 11.70 - 155.05 191.00 13.00 - 155.10 173.00 12.10 - 155.15 165.00 12.00 - 155.20 178.00 12.20 - 155.25 196.00 13.20 - 155.30 188.00 12.50 - 155.35 183.00 12.70 - 155.40 188.00 12.60 - 155.45 166.00 12.10 - 155.50 189.00 12.60 - 155.55 175.00 12.40 - 155.60 173.00 12.00 - 155.65 201.00 13.30 - 155.70 177.00 12.20 - 155.75 202.00 13.30 - 155.80 169.00 11.90 - 155.85 198.00 13.20 - 155.90 191.00 12.70 - 155.95 207.00 13.50 - 156.00 226.00 13.80 - 156.05 184.00 12.80 - 156.10 218.00 13.50 - 156.15 215.00 13.80 - 156.20 239.00 14.20 - 156.25 292.00 16.10 - 156.30 251.00 14.60 - 156.35 255.00 15.10 - 156.40 244.00 14.40 - 156.45 259.00 15.20 - 156.50 260.00 14.90 - 156.55 294.00 16.30 - 156.60 303.00 16.10 - 156.65 282.00 15.90 - 156.70 312.00 16.40 - 156.75 317.00 16.90 - 156.80 342.00 17.20 - 156.85 338.00 17.50 - 156.90 351.00 17.40 - 156.95 359.00 18.10 - 157.00 394.00 18.50 - 157.05 316.00 17.00 - 157.10 379.00 18.20 - 157.15 359.00 18.20 - 157.20 404.00 18.80 - 157.25 381.00 18.80 - 157.30 359.00 17.80 - 157.35 364.00 18.40 - 157.40 347.00 17.60 - 157.45 328.00 17.50 - 157.50 344.00 17.50 - 157.55 320.00 17.40 - 157.60 333.00 17.40 - 157.65 319.00 17.50 - 157.70 289.00 16.30 - 157.75 284.00 16.60 - 157.80 283.00 16.20 - 157.85 305.00 17.20 - 157.90 281.00 16.20 - 157.95 244.00 15.60 - 158.00 253.00 15.40 - 158.05 245.00 15.60 - 158.10 210.00 14.10 - 158.15 201.00 14.20 - 158.20 226.00 14.70 - 158.25 206.00 14.40 - 158.30 218.00 14.40 - 158.35 201.00 14.30 - 158.40 226.00 14.70 - 158.45 201.00 14.20 - 158.50 210.00 14.20 - 158.55 207.00 14.40 - 158.60 176.00 13.00 - 158.65 172.00 13.10 - 158.70 173.00 12.90 - 158.75 195.00 13.90 - 158.80 168.00 12.70 - 158.85 177.00 13.30 - 158.90 186.00 13.30 - 158.95 170.00 13.00 - 159.00 190.00 13.40 - 159.05 175.00 13.10 - 159.10 191.00 13.40 - 159.15 164.00 12.70 - 159.20 189.00 13.30 - 159.25 176.00 13.10 - 159.30 175.00 12.80 - 159.35 162.00 12.50 - 159.40 184.00 13.00 - 159.45 163.00 12.50 - 159.50 179.00 12.80 - 159.55 194.00 13.60 - 159.60 165.00 12.20 - 159.65 180.00 13.00 - 159.70 174.00 12.60 - 159.75 180.00 13.00 - 159.80 179.00 12.60 - 159.85 189.00 13.30 - 159.90 185.00 12.90 - 159.95 151.00 11.80 - 160.00 176.00 12.50 - 160.05 165.00 12.30 - 160.10 163.00 12.00 - 160.15 184.00 13.00 - 160.20 157.00 11.70 - 160.25 166.00 12.30 - 160.30 160.00 11.80 - 160.35 183.00 12.90 - 160.40 167.00 12.10 - 160.45 180.00 12.80 - 160.50 183.00 12.60 - 160.55 163.00 12.20 - 160.60 178.00 12.40 - 160.65 179.00 12.80 - 160.70 161.00 11.80 - 160.75 168.00 12.40 - 160.80 173.00 12.30 - 160.85 202.00 13.60 - 160.90 145.00 11.30 - 160.95 162.00 12.20 - 161.00 180.00 12.50 - 161.05 186.00 13.10 - 161.10 166.00 12.10 - 161.15 177.00 12.70 - 161.20 194.00 13.10 - 161.25 177.00 12.80 - 161.30 178.00 12.50 - 161.35 190.00 13.20 - 161.40 160.00 11.90 - 161.45 173.00 12.60 - 161.50 191.00 12.90 - 161.55 161.00 12.20 - 161.60 181.00 12.60 - 161.65 152.00 11.80 - 161.70 195.00 13.00 - 161.75 171.00 12.50 - 161.80 188.00 12.80 - 161.85 164.00 12.20 - 161.90 185.00 12.70 - 161.95 173.00 12.60 - 162.00 162.00 11.90 - 162.05 166.00 12.30 - 162.10 201.00 13.20 - 162.15 173.00 12.60 - 162.20 172.00 12.20 - 162.25 181.00 12.80 - 162.30 159.00 11.70 - 162.35 185.00 13.00 - 162.40 170.00 12.10 - 162.45 200.00 13.50 - 162.50 196.00 13.00 - 162.55 176.00 12.60 - 162.60 197.00 13.00 - 162.65 176.00 12.60 - 162.70 181.00 12.50 - 162.75 176.00 12.60 - 162.80 184.00 12.60 - 162.85 179.00 12.70 - 162.90 165.00 11.90 - 162.95 146.00 11.50 - 163.00 165.00 11.90 - 163.05 151.00 11.70 - 163.10 164.00 11.90 - 163.15 179.00 12.80 - 163.20 186.00 12.70 - 163.25 182.00 13.00 - 163.30 168.00 12.20 - 163.35 193.00 13.50 - 163.40 177.00 12.60 - 163.45 180.00 13.10 - 163.50 171.00 12.40 - 163.55 207.00 14.10 - 163.60 180.00 12.90 - 163.65 159.00 12.40 - 163.70 165.00 12.40 - 163.75 178.00 13.20 - 163.80 150.00 11.80 - 163.85 177.00 13.20 - 163.90 174.00 12.80 - 163.95 180.00 13.40 - 164.00 184.00 13.20 - 164.05 166.00 13.60 - 164.10 182.00 13.90 - 164.15 188.00 15.60 - 164.20 186.00 15.00 - 164.25 152.00 15.20 - 164.30 200.00 16.90 - 164.35 177.00 18.00 - 164.40 202.00 18.50 - 164.45 178.00 20.40 - 164.50 153.00 18.00 - 164.55 197.00 25.30 - 164.60 153.00 20.70 - 164.65 173.00 30.10 - 164.70 187.00 27.90 - 164.75 175.00 38.20 - 164.80 168.00 30.90 - 164.85 109.00 41.20