diff --git a/.badgery.yaml b/.badgery.yaml index f0fc4e74..f22752fa 100644 --- a/.badgery.yaml +++ b/.badgery.yaml @@ -52,6 +52,7 @@ cards: - group: Coverage type: codecov title: Unit test coverage (Codecov) + flag: unittests enabled: true - group: Coverage diff --git a/.github/workflows/dashboard.yaml b/.github/workflows/dashboard.yaml index 733fac98..1076f349 100644 --- a/.github/workflows/dashboard.yaml +++ b/.github/workflows/dashboard.yaml @@ -41,33 +41,34 @@ jobs: - name: Install and setup development dependencies shell: bash - run: pixi run dev + run: | + pixi run dev + 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 ===" - if [ -d "../worktree-$BRANCH" ]; then + if [ -d "../$BRANCH" ]; then echo "Branch $BRANCH already processed, skipping" continue fi - git worktree add ../worktree-$BRANCH origin/$BRANCH + 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 ../worktree-$BRANCH/src > reports/$BRANCH/coverage-docstring.txt + 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 ../worktree-$BRANCH/src --exclude ../worktree-$BRANCH/src/easydiffraction/crystallography/space_group_lookup_table.py > reports/$BRANCH/cyclomatic-complexity.json + pixi run radon cc -s -j ../$BRANCH/src > reports/$BRANCH/cyclomatic-complexity.json echo "Maintainability index for branch $BRANCH" - pixi run radon mi -j ../worktree-$BRANCH/src --exclude ../worktree-$BRANCH/src/easydiffraction/crystallography/space_group_lookup_table.py > reports/$BRANCH/maintainability-index.json + pixi run radon mi -j ../$BRANCH/src > reports/$BRANCH/maintainability-index.json echo "Raw metrics for branch $BRANCH" - pixi run radon raw -s -j ../worktree-$BRANCH/src --exclude ../worktree-$BRANCH/src/easydiffraction/crystallography/space_group_lookup_table.py > reports/$BRANCH/raw-metrics.json + pixi run radon raw -s -j ../$BRANCH/src > reports/$BRANCH/raw-metrics.json done - name: Generate dashboard HTML - run: | - pixi add --pypi --git https://github.com/enhantica/badgery badgery - pixi run pip show badgery - pixi run python -m badgery --config .badgery.yaml --repo ${{ github.repository }} --branch ${{ env.CI_BRANCH }} --output index.html + run: > + pixi run python -m badgery --config .badgery.yaml --repo ${{ + github.repository }} --branch ${{ env.CI_BRANCH }} --output index.html - name: Prepare publish directory run: | diff --git a/.github/workflows/pypi-test.yaml b/.github/workflows/pypi-test.yaml index f7f25213..8cb5323c 100644 --- a/.github/workflows/pypi-test.yaml +++ b/.github/workflows/pypi-test.yaml @@ -20,6 +20,7 @@ env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} jobs: + # Job 1: Test installation from PyPI on multiple OS pypi-package-tests: strategy: matrix: @@ -54,10 +55,8 @@ jobs: - name: Create the environment and install dependencies run: pixi install - - name: Install package from PyPI with dev and visualization extras - run: | - pixi add --pypi "easydiffraction[dev,visualization]" - pixi run easydiffraction --version + - name: Install package from PyPI with all extras + run: pixi add --pypi "easydiffraction[all]" - name: Run unit tests to verify the installation run: pixi run unit-tests @@ -70,8 +69,31 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - pixi run tutorials-list - pixi run tutorials-fetch + pixi run easydiffraction --version + pixi run easydiffraction list-tutorials + pixi run easydiffraction fetch-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/test.yaml b/.github/workflows/test.yaml index 7868d738..ec185ee8 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -147,6 +147,10 @@ jobs: 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 @@ -156,6 +160,7 @@ jobs: path: | dist/ tests/ + pytest.ini pixi.toml pixi.lock if-no-files-found: 'error' diff --git a/.github/workflows/tutorial-tests.yaml b/.github/workflows/tutorial-tests.yaml index f5d4af8d..c8fc6448 100644 --- a/.github/workflows/tutorial-tests.yaml +++ b/.github/workflows/tutorial-tests.yaml @@ -22,7 +22,12 @@ concurrency: ${{ 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: Test tutorials as scripts and notebooks on multiple OS tutorial-tests: strategy: fail-fast: false @@ -60,3 +65,25 @@ jobs: - name: Test tutorials as notebooks 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 }}" + }); diff --git a/.gitignore b/.gitignore index 02bb32e1..2c43d2b8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,18 @@ __pycache__ .coverage .pyc +# PyTest +.pytest_cache + +# MyPy +.mypy_cache + # Pixi .pixi +# Ruff +.ruff_cache + # Jupyter Notebooks .ipynb_checkpoints diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c3d471cd..87117c70 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,9 +49,9 @@ repos: pass_filenames: false stages: [pre-push] - - id: pixi-unit-tests - name: pixi run unit-tests - entry: pixi run unit-tests - language: system - pass_filenames: false - stages: [pre-push] + #- id: pixi-unit-tests + # name: pixi run unit-tests + # entry: pixi run unit-tests + # language: system + # pass_filenames: false + # stages: [pre-push] diff --git a/.prettierignore b/.prettierignore index f599168f..4bd61611 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,6 @@ # Pixi +.pixi pixi.lock + +# MyPy +.mypy_cache diff --git a/docs/architecture/package-structure-full.md b/docs/architecture/package-structure-full.md new file mode 100644 index 00000000..312b6c18 --- /dev/null +++ b/docs/architecture/package-structure-full.md @@ -0,0 +1,245 @@ +# Package Structure (full) + +``` +📦 easydiffraction +├── 📁 analysis +│ ├── 📁 calculators +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ │ └── 🏷️ class CalculatorBase +│ │ ├── 📄 crysfml.py +│ │ │ └── 🏷️ class CrysfmlCalculator +│ │ ├── 📄 cryspy.py +│ │ │ └── 🏷️ class CryspyCalculator +│ │ ├── 📄 factory.py +│ │ │ └── 🏷️ class CalculatorFactory +│ │ └── 📄 pdffit.py +│ │ └── 🏷️ class PdffitCalculator +│ ├── 📁 categories +│ │ ├── 📄 __init__.py +│ │ ├── 📄 aliases.py +│ │ │ ├── 🏷️ class Alias +│ │ │ └── 🏷️ class Aliases +│ │ ├── 📄 constraints.py +│ │ │ ├── 🏷️ class Constraint +│ │ │ └── 🏷️ class Constraints +│ │ └── 📄 joint_fit_experiments.py +│ │ ├── 🏷️ class JointFitExperiment +│ │ └── 🏷️ class JointFitExperiments +│ ├── 📁 fit_helpers +│ │ ├── 📄 __init__.py +│ │ ├── 📄 metrics.py +│ │ ├── 📄 reporting.py +│ │ │ └── 🏷️ class FitResults +│ │ └── 📄 tracking.py +│ │ └── 🏷️ class FitProgressTracker +│ ├── 📁 minimizers +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ │ └── 🏷️ class MinimizerBase +│ │ ├── 📄 dfols.py +│ │ │ └── 🏷️ class DfolsMinimizer +│ │ ├── 📄 factory.py +│ │ │ └── 🏷️ class MinimizerFactory +│ │ └── 📄 lmfit.py +│ │ └── 🏷️ class LmfitMinimizer +│ ├── 📄 __init__.py +│ ├── 📄 analysis.py +│ │ └── 🏷️ class Analysis +│ ├── 📄 calculation.py +│ │ └── 🏷️ class Calculator +│ └── 📄 fitting.py +│ └── 🏷️ class Fitter +├── 📁 core +│ ├── 📄 __init__.py +│ ├── 📄 category.py +│ │ ├── 🏷️ class CategoryItem +│ │ └── 🏷️ class CategoryCollection +│ ├── 📄 collection.py +│ │ └── 🏷️ class CollectionBase +│ ├── 📄 datablock.py +│ │ ├── 🏷️ class DatablockItem +│ │ └── 🏷️ class DatablockCollection +│ ├── 📄 diagnostic.py +│ │ └── 🏷️ class Diagnostics +│ ├── 📄 factory.py +│ │ └── 🏷️ class FactoryBase +│ ├── 📄 guard.py +│ │ └── 🏷️ class GuardedBase +│ ├── 📄 identity.py +│ │ └── 🏷️ class Identity +│ ├── 📄 parameters.py +│ │ ├── 🏷️ class GenericDescriptorBase +│ │ ├── 🏷️ class GenericStringDescriptor +│ │ ├── 🏷️ class GenericNumericDescriptor +│ │ ├── 🏷️ class GenericParameter +│ │ ├── 🏷️ class StringDescriptor +│ │ ├── 🏷️ class NumericDescriptor +│ │ └── 🏷️ class Parameter +│ ├── 📄 singletons.py +│ │ ├── 🏷️ class SingletonBase +│ │ ├── 🏷️ class UidMapHandler +│ │ └── 🏷️ class ConstraintsHandler +│ └── 📄 validation.py +│ ├── 🏷️ class DataTypes +│ ├── 🏷️ class ValidationStage +│ ├── 🏷️ class ValidatorBase +│ ├── 🏷️ class TypeValidator +│ ├── 🏷️ class RangeValidator +│ ├── 🏷️ class MembershipValidator +│ ├── 🏷️ class RegexValidator +│ └── 🏷️ class AttributeSpec +├── 📁 crystallography +│ ├── 📄 __init__.py +│ ├── 📄 crystallography.py +│ └── 📄 space_groups.py +├── 📁 experiments +│ ├── 📁 categories +│ │ ├── 📁 background +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ │ └── 🏷️ class BackgroundBase +│ │ │ ├── 📄 chebyshev.py +│ │ │ │ ├── 🏷️ class PolynomialTerm +│ │ │ │ └── 🏷️ class ChebyshevPolynomialBackground +│ │ │ ├── 📄 enums.py +│ │ │ │ └── 🏷️ class BackgroundTypeEnum +│ │ │ ├── 📄 factory.py +│ │ │ │ └── 🏷️ class BackgroundFactory +│ │ │ └── 📄 line_segment.py +│ │ │ ├── 🏷️ class LineSegment +│ │ │ └── 🏷️ class LineSegmentBackground +│ │ ├── 📁 instrument +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ │ └── 🏷️ class InstrumentBase +│ │ │ ├── 📄 cwl.py +│ │ │ │ └── 🏷️ class CwlInstrument +│ │ │ ├── 📄 factory.py +│ │ │ │ └── 🏷️ class InstrumentFactory +│ │ │ └── 📄 tof.py +│ │ │ └── 🏷️ class TofInstrument +│ │ ├── 📁 peak +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ │ └── 🏷️ class PeakBase +│ │ │ ├── 📄 cwl.py +│ │ │ │ ├── 🏷️ class CwlPseudoVoigt +│ │ │ │ ├── 🏷️ class CwlSplitPseudoVoigt +│ │ │ │ └── 🏷️ class CwlThompsonCoxHastings +│ │ │ ├── 📄 cwl_mixins.py +│ │ │ │ ├── 🏷️ class CwlBroadeningMixin +│ │ │ │ ├── 🏷️ class EmpiricalAsymmetryMixin +│ │ │ │ └── 🏷️ class FcjAsymmetryMixin +│ │ │ ├── 📄 factory.py +│ │ │ │ └── 🏷️ class PeakFactory +│ │ │ ├── 📄 tof.py +│ │ │ │ ├── 🏷️ class TofPseudoVoigt +│ │ │ │ ├── 🏷️ class TofPseudoVoigtIkedaCarpenter +│ │ │ │ └── 🏷️ class TofPseudoVoigtBackToBack +│ │ │ ├── 📄 tof_mixins.py +│ │ │ │ ├── 🏷️ class TofBroadeningMixin +│ │ │ │ └── 🏷️ class IkedaCarpenterAsymmetryMixin +│ │ │ ├── 📄 total.py +│ │ │ │ └── 🏷️ class TotalGaussianDampedSinc +│ │ │ └── 📄 total_mixins.py +│ │ │ └── 🏷️ class TotalBroadeningMixin +│ │ ├── 📄 __init__.py +│ │ ├── 📄 excluded_regions.py +│ │ │ ├── 🏷️ class ExcludedRegion +│ │ │ └── 🏷️ class ExcludedRegions +│ │ ├── 📄 experiment_type.py +│ │ │ └── 🏷️ class ExperimentType +│ │ └── 📄 linked_phases.py +│ │ ├── 🏷️ class LinkedPhase +│ │ └── 🏷️ class LinkedPhases +│ ├── 📁 datastore +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ │ └── 🏷️ class DatastoreBase +│ │ ├── 📄 factory.py +│ │ │ └── 🏷️ class DatastoreFactory +│ │ ├── 📄 pd.py +│ │ │ └── 🏷️ class PdDatastore +│ │ └── 📄 sc.py +│ │ └── 🏷️ class ScDatastore +│ ├── 📁 experiment +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ │ ├── 🏷️ class ExperimentBase +│ │ │ └── 🏷️ class PdExperimentBase +│ │ ├── 📄 bragg_pd.py +│ │ │ └── 🏷️ class BraggPdExperiment +│ │ ├── 📄 bragg_sc.py +│ │ │ └── 🏷️ class BraggScExperiment +│ │ ├── 📄 enums.py +│ │ │ ├── 🏷️ class SampleFormEnum +│ │ │ ├── 🏷️ class ScatteringTypeEnum +│ │ │ ├── 🏷️ class RadiationProbeEnum +│ │ │ ├── 🏷️ class BeamModeEnum +│ │ │ └── 🏷️ class PeakProfileTypeEnum +│ │ ├── 📄 factory.py +│ │ │ └── 🏷️ class ExperimentFactory +│ │ ├── 📄 instrument_mixin.py +│ │ │ └── 🏷️ class InstrumentMixin +│ │ └── 📄 total_pd.py +│ │ └── 🏷️ class TotalPdExperiment +│ ├── 📄 __init__.py +│ └── 📄 experiments.py +│ └── 🏷️ class Experiments +├── 📁 io +│ └── 📁 cif +│ ├── 📄 handler.py +│ │ └── 🏷️ class CifHandler +│ └── 📄 serialize.py +├── 📁 plotting +│ ├── 📁 plotters +│ │ ├── 📄 __init__.py +│ │ ├── 📄 plotter_ascii.py +│ │ │ └── 🏷️ class AsciiPlotter +│ │ ├── 📄 plotter_base.py +│ │ │ └── 🏷️ class PlotterBase +│ │ └── 📄 plotter_plotly.py +│ │ └── 🏷️ class PlotlyPlotter +│ ├── 📄 __init__.py +│ └── 📄 plotting.py +│ ├── 🏷️ class Plotter +│ └── 🏷️ class PlotterFactory +├── 📁 project +│ ├── 📄 __init__.py +│ ├── 📄 project.py +│ │ └── 🏷️ class Project +│ └── 📄 project_info.py +│ └── 🏷️ class ProjectInfo +├── 📁 sample_models +│ ├── 📁 categories +│ │ ├── 📄 __init__.py +│ │ ├── 📄 atom_sites.py +│ │ │ ├── 🏷️ class AtomSite +│ │ │ └── 🏷️ class AtomSites +│ │ ├── 📄 cell.py +│ │ │ └── 🏷️ class Cell +│ │ └── 📄 space_group.py +│ │ └── 🏷️ class SpaceGroup +│ ├── 📁 sample_model +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ │ └── 🏷️ class SampleModelBase +│ │ └── 📄 factory.py +│ │ └── 🏷️ class SampleModelFactory +│ ├── 📄 __init__.py +│ └── 📄 sample_models.py +│ └── 🏷️ class SampleModels +├── 📁 summary +│ ├── 📄 __init__.py +│ └── 📄 summary.py +│ └── 🏷️ class Summary +├── 📁 utils +│ ├── 📄 __init__.py +│ ├── 📄 formatting.py +│ ├── 📄 logging.py +│ │ └── 🏷️ class Logger +│ └── 📄 utils.py +├── 📄 __init__.py +└── 📄 __main__.py +``` diff --git a/docs/architecture/package-structure-short.md b/docs/architecture/package-structure-short.md new file mode 100644 index 00000000..d664e1b4 --- /dev/null +++ b/docs/architecture/package-structure-short.md @@ -0,0 +1,133 @@ +# Package Structure (short) + +``` +📦 easydiffraction +├── 📁 analysis +│ ├── 📁 calculators +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ ├── 📄 crysfml.py +│ │ ├── 📄 cryspy.py +│ │ ├── 📄 factory.py +│ │ └── 📄 pdffit.py +│ ├── 📁 categories +│ │ ├── 📄 __init__.py +│ │ ├── 📄 aliases.py +│ │ ├── 📄 constraints.py +│ │ └── 📄 joint_fit_experiments.py +│ ├── 📁 fit_helpers +│ │ ├── 📄 __init__.py +│ │ ├── 📄 metrics.py +│ │ ├── 📄 reporting.py +│ │ └── 📄 tracking.py +│ ├── 📁 minimizers +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ ├── 📄 dfols.py +│ │ ├── 📄 factory.py +│ │ └── 📄 lmfit.py +│ ├── 📄 __init__.py +│ ├── 📄 analysis.py +│ ├── 📄 calculation.py +│ └── 📄 fitting.py +├── 📁 core +│ ├── 📄 __init__.py +│ ├── 📄 category.py +│ ├── 📄 collection.py +│ ├── 📄 datablock.py +│ ├── 📄 diagnostic.py +│ ├── 📄 factory.py +│ ├── 📄 guard.py +│ ├── 📄 identity.py +│ ├── 📄 parameters.py +│ ├── 📄 singletons.py +│ └── 📄 validation.py +├── 📁 crystallography +│ ├── 📄 __init__.py +│ ├── 📄 crystallography.py +│ └── 📄 space_groups.py +├── 📁 experiments +│ ├── 📁 categories +│ │ ├── 📁 background +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ ├── 📄 chebyshev.py +│ │ │ ├── 📄 enums.py +│ │ │ ├── 📄 factory.py +│ │ │ └── 📄 line_segment.py +│ │ ├── 📁 instrument +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ ├── 📄 cwl.py +│ │ │ ├── 📄 factory.py +│ │ │ └── 📄 tof.py +│ │ ├── 📁 peak +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ ├── 📄 cwl.py +│ │ │ ├── 📄 cwl_mixins.py +│ │ │ ├── 📄 factory.py +│ │ │ ├── 📄 tof.py +│ │ │ ├── 📄 tof_mixins.py +│ │ │ ├── 📄 total.py +│ │ │ └── 📄 total_mixins.py +│ │ ├── 📄 __init__.py +│ │ ├── 📄 excluded_regions.py +│ │ ├── 📄 experiment_type.py +│ │ └── 📄 linked_phases.py +│ ├── 📁 datastore +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ ├── 📄 factory.py +│ │ ├── 📄 pd.py +│ │ └── 📄 sc.py +│ ├── 📁 experiment +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ ├── 📄 bragg_pd.py +│ │ ├── 📄 bragg_sc.py +│ │ ├── 📄 enums.py +│ │ ├── 📄 factory.py +│ │ ├── 📄 instrument_mixin.py +│ │ └── 📄 total_pd.py +│ ├── 📄 __init__.py +│ └── 📄 experiments.py +├── 📁 io +│ └── 📁 cif +│ ├── 📄 handler.py +│ └── 📄 serialize.py +├── 📁 plotting +│ ├── 📁 plotters +│ │ ├── 📄 __init__.py +│ │ ├── 📄 plotter_ascii.py +│ │ ├── 📄 plotter_base.py +│ │ └── 📄 plotter_plotly.py +│ ├── 📄 __init__.py +│ └── 📄 plotting.py +├── 📁 project +│ ├── 📄 __init__.py +│ ├── 📄 project.py +│ └── 📄 project_info.py +├── 📁 sample_models +│ ├── 📁 categories +│ │ ├── 📄 __init__.py +│ │ ├── 📄 atom_sites.py +│ │ ├── 📄 cell.py +│ │ └── 📄 space_group.py +│ ├── 📁 sample_model +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ └── 📄 factory.py +│ ├── 📄 __init__.py +│ └── 📄 sample_models.py +├── 📁 summary +│ ├── 📄 __init__.py +│ └── 📄 summary.py +├── 📁 utils +│ ├── 📄 __init__.py +│ ├── 📄 formatting.py +│ ├── 📄 logging.py +│ └── 📄 utils.py +├── 📄 __init__.py +└── 📄 __main__.py +``` diff --git a/pixi.lock b/pixi.lock index cdbb77db..0b02e764 100644 --- a/pixi.lock +++ b/pixi.lock @@ -9,699 +9,730 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py313h7033f15_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py313hf01b4d8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.16-py313h5d5ffb9_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh82676e8_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py313h78bf25f_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1423503_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.1-he9a06e4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_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/markupsafe-3.0.2-py313h8060acc_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.4.1-heeeca48_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.9.0-heeeca48_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.0.0-py313h07c4f96_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.7-h2b335a9_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.8-h2b335a9_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py313h8060acc_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hfb55c3c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py313h843e2db_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh0d859eb_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh0d859eb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py313h07c4f96_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py313h54dd161_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1e/02/45b388b49e37933f316e1fb39c0de6fb1d77384b0c8f4cf6af5f2cbe3ea6/aiohttp-3.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.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/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-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/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.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/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-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/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.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/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-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/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/47/807e225343ee50f71132bc6185d62230c8f6950e3e6a6e161ae11eed9495/diffpy.pdffit2-1.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_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/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f2/9f/bf231c2a3fac99d1d7f1d89c76594f158693f981a4aa02be406e9f036832/fonttools-4.59.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-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/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.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/db/9e/024450978a674b2f021aa1f46b6fa73823713c7fe8b5d713fbd6defdb157/gemmi-0.7.3-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/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e7/ec/86f59025306dcc6deee5fda54d980d077075b8d9889aac80f158bd585f1b/h5py-3.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-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/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.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/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-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/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.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/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/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-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/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-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/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-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/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/b8/9eea6630198cb303d131d95d285a024b3b8645b1763a2916fddb44ca8760/matplotlib-3.10.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/ff/6425bf5c20d79aa5b959d1ce9e65f599632345391381c9a104133fe0b171/matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-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/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-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/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-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/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - 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/9d/87/bc14f49bc95c4cb0dd0a8c56028a67c014ee7e6818ccdce74a4862af259b/msgspec-0.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/8f/52/0634adaace9be2d8cac9ef78f05c47f3a675882e068438b9d7ec7ef0c13f/pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-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/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/a5/a8c7562ec39f2647245b52ea4aeb13b5b125b3f48c0c152e9ebce7047a0a/pycifrw-5.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-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/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.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/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/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - 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/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/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.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/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - 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/dc/29/11ae2c2b981de60187f7cbc84277d9d21f101093d1b2e945c63774477aba/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.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/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-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/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-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/8f/35/cb47d2d07a383c07b0e5043c6fe5555f0fd79683c6d7f9760222987c8be9/uv-0.8.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/45/488417c6c0127c00bcdfac3556ae2ea0597df8245fe5f9bcfda35ebdbe85/uv-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl osx-64: - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py313h585f44e_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py313h253db18_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-1.17.1-py313he20ea1e_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.16-py313h03db916_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/jsonpointer-3.0.0-py313habf4b1d_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.1-h3d58e20_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.6-h281671d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.20-hfdf4475_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_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/markupsafe-3.0.2-py313h717bdf5_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.4.1-h2e7699b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.2-h6e31bce_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.9.0-h09bb5a9_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.0.0-py313h585f44e_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-11.1-py313h6971d95_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-11.1-py313h55ae1d7_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.7-h5eba815_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.8-h2bd861f_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py313h717bdf5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py312hb7d603e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.1-py313h66e1e84_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.2-py313h585f44e_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zeromq-4.3.5-h6c33b1e_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py313hcb05632_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/13/18/1ac95683e1c1d48ef4503965c96f5401618a04c139edae12e200392daae8/aiohttp-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.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/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-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/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.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/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-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/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.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/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-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/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ed/a1/1aec6b9c471074a184c93ffe95fa16593ebd96a017b1a08a4aebed5cd6c6/diffpy.pdffit2-1.5.1-cp313-cp313-macosx_13_0_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/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/be/fc5fe58dd76af7127b769b68071dbc32d4b95adc8b58d1d28d42d93c90f2/fonttools-4.59.2-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-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/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.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/93/e2/c45cd48ec7cc0f49e182d8a736355a61edf0fc2ac060b90fc822c4fca067/gemmi-0.7.3-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/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-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/6c/c2/7efe82d09ca10afd77cd7c286e42342d520c049a8c43650194928bcc635c/h5py-3.14.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/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-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/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.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/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/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-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/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-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/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-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/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/db/18380e788bb837e724358287b08e223b32bc8dccb3b0c12fa8ca20bc7f3b/matplotlib-3.10.6-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/02/9c/207547916a02c78f6bdd83448d9b21afbc42f6379ed887ecf610984f3b4e/matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-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/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-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/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-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/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - 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/3c/cb/2842c312bbe618d8fefc8b9cedce37f773cdc8fa453306546dba2c21fd98/msgspec-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7d/b9/984c2b1ee61a8b803bf63582b4ac4242cf76e2dbd663efeafcb620cc0ccb/numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/27/64/a2f7bf678af502e16b472527735d168b22b7824e45a4d7e96a4fbb634b59/pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-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/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/0d/6af0bb9a45c771ffccd5c4c035c57ac9005e711b1191ddad1dd954187cfe/pycifrw-5.0.1-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-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/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.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/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/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - 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/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/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.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/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - 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/41/1c/a7260bd47a6fae7e03768bf66451437b36451143f36b285522b865987ced/sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.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/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-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/65/34/609b72034df0c62bcfb0c0ad4b11e2b55e537c0f0817588b5337d3dcca71/uv-0.8.17-py3-none-macosx_10_12_x86_64.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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/1a/8e68d0020c29f6f329a265773c23b0c01e002794ea884b8bdbd594c7ea97/uv-0.9.3-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl osx-arm64: - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313hcdf3177_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py313hb4b7877_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-1.17.1-py313h755b2b2_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.16-py313hab38a8b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py313h8f79df9_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.1-hf598326_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.3-hf598326_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_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/markupsafe-3.0.2-py313ha9b7d5b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-24.4.1-hab9d20b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.2-he92f556_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.0.0-py313hcdf3177_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-11.1-py313had225c5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-11.1-py313h4e140e3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.7-h5c937ed_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.8-h09175d0_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.2-py313ha9b7d5b_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312hd65ceae_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py313h80e0809_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py313hcdf3177_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h888dc83_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py313h9734d34_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fd/79/ef0d477c771a642d1a881b92d226314c43d3c74bc674c93e12e679397a97/aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.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/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-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/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.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/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-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/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.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/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-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/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/d9/e96af817e50310f9891dd67399e8e9fd651b21a7c8be13ed21d04ceaeb61/diffpy.pdffit2-1.5.1-cp313-cp313-macosx_13_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/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/7b/d0d3b9431642947b5805201fbbbe938a47b70c76685ef1f0cb5f5d7140d6/fonttools-4.59.2-cp313-cp313-macosx_10_13_universal2.whl - - pypi: https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-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/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.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/60/06/6e3a083a02d2a1b7da69dce5538d51b4a83dc308e3ea9e21edcf324e10de/gemmi-0.7.3-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/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-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/4f/31/f570fab1239b0d9441024b92b6ad03bb414ffa69101a985e4c83d37608bd/h5py-3.14.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/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-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/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.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/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/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-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/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-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/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-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/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/0f/38dd49445b297e0d4f12a322c30779df0d43cb5873c7847df8a82e82ec67/matplotlib-3.10.6-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-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/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-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/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-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/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - 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/58/95/c40b01b93465e1a5f3b6c7d91b10fb574818163740cc3acbe722d1e0e7e4/msgspec-0.19.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a6/e4/07970e3bed0b1384d22af1e9912527ecbeb47d3b26e9b6a3bced068b3bea/numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/54/4c/c3d21b2b7769ef2f4c2b9299fcadd601efa6729f1357a8dbce8dd949ed70/pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-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/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/81/bdd4bfabe70b7c9a8c0716a722ced4ebd27311afd1f4800cd405d3229c1b/pycifrw-5.0.1-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-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/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.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/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/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - 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/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/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.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/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - 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/8e/84/8a337454e82388283830b3586ad7847aa9c76fdd4f1df09cdd1f94591873/sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.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/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-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 - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-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/b6/bc/9417df48f0c18a9d54c2444096e03f2f56a3534c5b869f50ac620729cbc8/uv-0.8.17-py3-none-macosx_11_0_arm64.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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/25/6df8be6cd549200e80d19374579689fda39b18735afde841345284fb113d/uv-0.9.3-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl win-64: - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py313hfe59770_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-h4c7d964_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-1.17.1-py313h5ea7bf4_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.16-py313h927ade5_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/icu-75.1-he0c23c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh3521513_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyh6be1c34_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/jsonpointer-3.0.0-py313hfa70ccb_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh5737063_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.21.3-hdf4eb48_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-35_h5709861_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.9.0-35_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda @@ -710,155 +741,242 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.20-hc70643c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.50.4-hf5d6505_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_9.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.0-h06f855e_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_0.conda + - 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.0-h06f855e_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_1.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-20.1.8-hfa2b4ca_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.2-py313hb4c8b1a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.3-hfa2b4ca_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2024.2.2-h57928b3_16.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.4.1-he453025_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.2-h725018a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.9.0-he453025_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.0.0-py313h5ea7bf4_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.7-hdf00ec1_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.8-hdf00ec1_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.2-py313hb4c8b1a_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312hbb5da91_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.27.1-py313hfbe8231_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh5737063_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2021.13.0-h18a62a1_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh5737063_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.2-py313h5ea7bf4_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_31.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_31.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_31.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h5bddc39_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py313h5fd188c_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h2b53caa_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c9/58/afab7f2b9e7df88c995995172eb78cae8a3d5a62d5681abaade86b3f0089/aiohttp-3.13.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.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/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-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/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.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/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-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/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.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/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-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/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/93/8239b7cd3bb4c134295c1c07318c6fac06fe920b0f615787bb67738e867a/diffpy.pdffit2-1.5.1-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/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/a9/be7219fc64a6026cc0aded17fa3720f9277001c185434230bd351bf678e6/fonttools-4.59.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-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/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.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/28/38/c5c1b52c16ef70b9e7e0392f6298b93dd17783c3c34a92512825b1617841/gemmi-0.7.3-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/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/3f/6d/0084ed0b78d4fd3e7530c32491f2884140d9b06365dac8a08de726421d4a/h5py-3.14.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-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/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-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/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-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/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.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/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/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-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/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-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/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-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/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ff/1d/70c28528794f6410ee2856cd729fa1f1756498b8d3126443b0a94e1a8695/matplotlib-3.10.6-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e1/b6/23064a96308b9aeceeffa65e96bcde459a2ea4934d311dee20afde7407a0/matplotlib-3.10.7-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-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/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-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/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-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/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/23/d8/f15b40611c2d5753d1abb0ca0da0c75348daf1252220e5dda2867bd81062/msgspec-0.19.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/b5/263ebbbbcede85028f30047eab3d58028d7ebe389d6493fc95ae66c636ab/numpy-2.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/22/3c/f2af1ce8840ef648584a6156489636b5692c162771918aa95707c165ad2b/pandas-2.3.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9f/9b/50835e8fd86073fa7aa921df61b4cebc1f0ff400e4338541675cb72b5507/pycifrw-5.0.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-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/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.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/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/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.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/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-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/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/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.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/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - 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/ac/a5/ca2f07a2a201f9497de1928f787926613db6307992fe5cda97624eb07c2f/sqlalchemy-2.0.43-cp313-cp313-win_amd64.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/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-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/1a/c4/0082f437bac162ab95e5a3a389a184c122d45eb5593960aab92fdf80374b/uv-0.8.17-py3-none-win_amd64.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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/cd/667f6249a9a3a8d2d7ba1aa72db6b1fc6cdaf7b0d7aeda43478702e2a13e/uv-0.9.3-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl py311-dev: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -868,691 +986,732 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py311h49ec1c0_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py311h1ddb823_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py311h5b438cf_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.16-py311hc665b79_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh82676e8_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py311h38be061_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1423503_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.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/libsodium-1.0.20-h4ab18f5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.1-he9a06e4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_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/markupsafe-3.0.2-py311h2dc5d0c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.4.1-heeeca48_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.9.0-heeeca48_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.0.0-py311h49ec1c0_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.13-h9e4cc4f_0_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py311h2dc5d0c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py311h2315fbb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.14-hfe2f287_1_cpython.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py311h902ca64_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh0d859eb_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh0d859eb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py311h49ec1c0_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py311haee01d2_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c6/82/1ddf0ea4f2f3afe79dffed5e8a246737cff6cbe781887a6a170299e33204/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/51/6d/7b1e020fe1d2a2be7cf0ce5e35922f345e3507cf337faa1a6563c42065c1/aiohttp-3.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.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/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-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/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.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/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-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/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/40/b8/7a3f1f33b35cc4a6c37e759137533119560d06c0cc14753d1a803be0cd4a/coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.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/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-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/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1f/8a/3308fe30660a0a585b7bc4b68b216ce6dc63d69fe9cbff9bd97ca411a8f9/diffpy.pdffit2-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_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/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/69/95/864726eaa8f9d4e053d0c462e64d5830ec7c599cbdf1db9e40f25ca3972e/fonttools-4.59.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4d/83/220a374bd7b2aeba9d0725130665afe11de347d95c3620b9b82cc2fcab97/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-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/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/d2/9f4e4c4374dd1daa8367784e1bd910f18ba886db1d6b825b12edf6db3edc/fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.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/e5/4e/55e3410500c274a15b44997a14c16cc0f11b4793fbd90c7fc8b009f83a9f/gemmi-0.7.3-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/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/21/d4/d461649cafd5137088fb7f8e78fdc6621bb0c4ff2c090a389f68e8edc136/h5py-3.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-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/8b/23/4ab1108e87851ccc69694b03b817d92e142966a6c4abd99e17db77f2c066/h5py-3.15.1-cp311-cp311-manylinux_2_27_x86_64.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/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-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/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.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/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/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-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/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-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/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-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/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/29/4a8650a3dcae97fa4f375d46efcb25920d67b512186f8a6788b896062a81/matplotlib-3.10.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/10/b7/4aa196155b4d846bd749cf82aa5a4c300cf55a8b5e0dfa5b722a63c0f8a0/matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-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/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-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/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-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/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - 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/85/2e/db7e189b57901955239f7689b5dcd6ae9458637a9c66747326726c650523/msgspec-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/00/6e/fac58b1072a6fc59af5e7acb245e8754d3e1f97f4f808a6559951f72a0d4/multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/10/a2/010b0e27ddeacab7839957d7a8f00e91206e0c2c47abbb5f35a2630e5387/numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/8b/ef/0e2ffb30b1f7fbc9a588bd01e3c14a0d96854d09a887e15e30cc19961227/pandas-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/32/67e3b0f07b0aba57a078c4ab777a9e8e6bc62f24fb53a2337f75f9691699/numpy-2.3.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-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/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/75/35/a44ce3d7c3f52a2a443cae261a05c2affc52fde7f1643974adbef105785f/pycifrw-5.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-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/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.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/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/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - 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/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/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.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/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/1a/bc/a5c75095089b96ea72c1bd37a4497c24b581ec73db4ef58ebee142ad2d14/scipy-1.16.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - 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/03/b5/cacf432e6f1fc9d156eca0560ac61d4355d2181e751ba8c0cd9cb232c8c1/sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.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/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/2d/fdb9246d9d32518bda5d90f4b65030b9bf403a935cfe4c36a474846517cb/sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-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/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-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/8f/35/cb47d2d07a383c07b0e5043c6fe5555f0fd79683c6d7f9760222987c8be9/uv-0.8.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/45/488417c6c0127c00bcdfac3556ae2ea0597df8245fe5f9bcfda35ebdbe85/uv-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl osx-64: - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py311h13e5629_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py311h7b20566_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-1.17.1-py311he66fa18_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.16-py311hc651eee_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/jsonpointer-3.0.0-py311h6eed73b_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.1-h3d58e20_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.6-h281671d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.20-hfdf4475_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_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/markupsafe-3.0.2-py311ha3cf9ac_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.4.1-h2e7699b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.2-h6e31bce_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.9.0-h09bb5a9_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.0.0-py311h13e5629_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-11.1-py311h2f44256_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-11.1-py311hbc8e8a3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.13-h9ccd52b_0_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py311ha3cf9ac_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py311h0ab6910_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.14-h3999593_1_cpython.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.1-py311hd3d88a1_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.2-py311h13e5629_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zeromq-4.3.5-h6c33b1e_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py311h62e9434_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/f9/0a31fcb1a7d4629ac9d8f01f1cb9242e2f9943f47f5d03215af91c3c1a26/aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ae/f9/2d6d93fd57ab4726e18a7cdab083772eda8302d682620fbf2aef48322351/aiohttp-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.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/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-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/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.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/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-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/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/49/3a/ee1074c15c408ddddddb1db7dd904f6b81bc524e01f5a1c5920e13dbde23/coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.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/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-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/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/85/c6/5cfa31bb0ac6fd0a1370bdced0640eb8821fe6b10ffd5f00dbe77e4721aa/diffpy.pdffit2-1.5.1-cp311-cp311-macosx_13_0_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/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/57/2a/976f5f9fa3b4dd911dc58d07358467bec20e813d933bc5d3db1a955dd456/fonttools-4.59.2-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/75/a9/9c2c5760b6ba45eae11334db454c189d43d34a4c0b489feb2175e5e64277/frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-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/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/47/3c63158459c95093be9618794acb1067b3f4d30dcc5c3e8114b70e67a092/fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.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/7c/d1/283c9d103b8b605cc4cdbb8e398d314b01b4bac309be03e19f7cecc5a4d9/gemmi-0.7.3-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/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-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/61/1b/ad24a8ce846cf0519695c10491e99969d9d203b9632c4fcd5004b1641c2e/h5py-3.14.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/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-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/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.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/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/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-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/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-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/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-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/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/80/d6/5d3665aa44c49005aaacaa68ddea6fcb27345961cd538a98bb0177934ede/matplotlib-3.10.6-cp311-cp311-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fc/bc/0fb489005669127ec13f51be0c6adc074d7cf191075dab1da9fe3b7a3cfc/matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-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/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-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/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-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/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - 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/24/d4/2ec2567ac30dab072cce3e91fb17803c52f0a37aab6b0c24375d2b20a581/msgspec-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/54/a3/bed07bc9e2bb302ce752f1dabc69e884cd6a676da44fb0e501b246031fdd/multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7a/45/e80d203ef6b267aa29b22714fb558930b27960a0c5ce3c19c999232bb3eb/numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/7a/59/f3e010879f118c2d400902d2d871c2226cef29b08c09fb8dc41111730400/pandas-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e7/0e07379944aa8afb49a556a2b54587b828eb41dc9adc56fb7615b678ca53/numpy-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-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/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/b6/84364503e0726da4a263e1736d0e1754526d1b1729d0087c680d96345570/pycifrw-5.0.1-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-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/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.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/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/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - 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/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/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.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/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/0b/ef/37ed4b213d64b48422df92560af7300e10fe30b5d665dd79932baebee0c6/scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - 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/9d/77/fa7189fe44114658002566c6fe443d3ed0ec1fa782feb72af6ef7fbe98e7/sqlalchemy-2.0.43-cp311-cp311-macosx_10_9_x86_64.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/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e3/81/15d7c161c9ddf0900b076b55345872ed04ff1ed6a0666e5e94ab44b0163c/sqlalchemy-2.0.44-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-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/65/34/609b72034df0c62bcfb0c0ad4b11e2b55e537c0f0817588b5337d3dcca71/uv-0.8.17-py3-none-macosx_10_12_x86_64.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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/1a/8e68d0020c29f6f329a265773c23b0c01e002794ea884b8bdbd594c7ea97/uv-0.9.3-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl osx-arm64: - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py311h3696347_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py311hf719da1_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-1.17.1-py311h146a0b8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.16-py311ha59bd64_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py311h267d04e_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.1-hf598326_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.3-hf598326_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_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/markupsafe-3.0.2-py311h4921393_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-24.4.1-hab9d20b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.2-he92f556_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.0.0-py311h3696347_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-11.1-py311hf0763de_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-11.1-py311hc5b188e_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.13-hc22306f_0_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.2-py311h4921393_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py311h13abfa4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.14-hec0b533_1_cpython.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py311h1c3fc1a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py311h3696347_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h888dc83_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py311h5bb9006_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/62/6c/94846f576f1d11df0c2e41d3001000527c0fdf63fce7e69b3927a731325d/aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/89/a6/e1c061b079fed04ffd6777950c82f2e8246fd08b7b3c4f56fdd47f697e5a/aiohttp-3.13.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.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/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-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/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.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/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-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/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/c4/9f44bebe5cb15f31608597b037d78799cc5f450044465bcd1ae8cb222fe1/coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.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/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-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/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7c/26/47f142b99ad0e5d0e1e94beb7d05e6bed0cfd84669c263df382f5b6ce99b/diffpy.pdffit2-1.5.1-cp311-cp311-macosx_13_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/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f8/53/742fcd750ae0bdc74de4c0ff923111199cc2f90a4ee87aaddad505b6f477/fonttools-4.59.2-cp311-cp311-macosx_10_9_universal2.whl - - pypi: https://files.pythonhosted.org/packages/47/be/4038e2d869f8a2da165f35a6befb9158c259819be22eeaf9c9a8f6a87771/frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-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/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ea/85/639aa9bface1537e0fb0f643690672dde0695a5bbbc90736bc571b0b1941/fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.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/05/78/64628f519ff553a0d8101dd3852b87441caa69c6617250d48b3c6bad9422/gemmi-0.7.3-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/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-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/36/5b/a066e459ca48b47cc73a5c668e9924d9619da9e3c500d9fb9c29c03858ec/h5py-3.14.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/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-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/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.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/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/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-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/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-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/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-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/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8c/af/30ddefe19ca67eebd70047dabf50f899eaff6f3c5e6a1a7edaecaf63f794/matplotlib-3.10.6-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e2/6a/d42588ad895279ff6708924645b5d2ed54a7fb2dc045c8a804e955aeace1/matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-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/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-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/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-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/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - 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/2b/c0/18226e4328897f4f19875cb62bb9259fe47e901eade9d9376ab5f251a929/msgspec-0.19.0-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/a7/4b/ceeb4f8f33cf81277da464307afeaf164fb0297947642585884f5cad4f28/multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/52/18/cf2c648fccf339e59302e00e5f2bc87725a3ce1992f30f3f78c9044d7c43/numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/38/18/48f10f1cc5c397af59571d638d211f494dba481f449c19adbd282aa8f4ca/pandas-2.3.2-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/cb/5a69293561e8819b09e34ed9e873b9a82b5f2ade23dce4c51dc507f6cfe1/numpy-2.3.4-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-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/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/5c/b999ea3e64981018d52846b9b69193fa581a70cd255912cb6962a33a666a/pycifrw-5.0.1-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-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/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.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/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/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - 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/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/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.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/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/85/ab/5c2eba89b9416961a982346a4d6a647d78c91ec96ab94ed522b3b6baf444/scipy-1.16.2-cp311-cp311-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - 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/99/ea/92ac27f2fbc2e6c1766bb807084ca455265707e041ba027c09c17d697867/sqlalchemy-2.0.43-cp311-cp311-macosx_11_0_arm64.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/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/d5/4abd13b245c7d91bdf131d4916fd9e96a584dac74215f8b5bc945206a974/sqlalchemy-2.0.44-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 - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-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/b6/bc/9417df48f0c18a9d54c2444096e03f2f56a3534c5b869f50ac620729cbc8/uv-0.8.17-py3-none-macosx_11_0_arm64.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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/25/6df8be6cd549200e80d19374579689fda39b18735afde841345284fb113d/uv-0.9.3-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl win-64: - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py311h3485c13_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py311h3e6a449_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-h4c7d964_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-1.17.1-py311h3485c13_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.11.13-py311hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.16-py311h5dfdfe8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/icu-75.1-he0c23c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh3521513_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyh6be1c34_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/jsonpointer-3.0.0-py311h1ea47a8_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh5737063_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.21.3-hdf4eb48_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-35_h5709861_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.9.0-35_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda @@ -1560,155 +1719,243 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.1-default_h64bd3f2_1002.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.20-hc70643c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.50.4-hf5d6505_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_9.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.0-h06f855e_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_0.conda + - 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.0-h06f855e_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_1.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-20.1.8-hfa2b4ca_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.2-py311h5082efb_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.3-hfa2b4ca_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2024.2.2-h57928b3_16.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.4.1-he453025_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.2-h725018a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.9.0-he453025_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.0.0-py311h3485c13_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.13-h3f84c4b_0_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py311hefeebc8_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py311hda3d55a_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.2-py311h5082efb_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py311hb77b9c8_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.27.1-py311hf51aa87_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh5737063_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.14-h30ce641_1_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2021.13.0-h18a62a1_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh5737063_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.2-py311h3485c13_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_31.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_31.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_31.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h2b53caa_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h5bddc39_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py311hf893f09_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/10/97/ad2b18700708452400278039272032170246a1bf8ec5d832772372c71f1a/aiohttp-3.12.15-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/3e/bd/485d98b372a2cd6998484a93ddd401ec6b6031657661c36846a10e2a1f6e/aiohttp-3.13.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.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/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-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/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.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/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-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/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/29/2ac1dfcdd4ab9a70026edc8d715ece9b4be9a1653075c658ee6f271f394d/coverage-7.11.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.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/c2/c5/c012c60a2922cc91caa9675d0ddfbb14ba59e1e36228355f41cab6483469/debugpy-1.8.17-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/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/59/b7/5464cb1ffcf0cfaf34b8fb1604c23982855d453c987ccb18cb273b547e9f/diffpy.pdffit2-1.5.1-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/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/de/35d839aa69db737a3f9f3a45000ca24721834d40118652a5775d5eca8ebb/fonttools-4.59.2-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/58/17/fe61124c5c333ae87f09bb67186d65038834a47d974fc10a5fadb4cc5ae1/frozenlist-1.7.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-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/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/78/0e1a6d22b427579ea5c8273e1c07def2f325b977faaf60bb7ddc01456cb1/fonttools-4.60.1-cp311-cp311-win_amd64.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/fb/f4/6d50077a2bf4449fab360e85790db4031be1545de77cce239a215866d34d/gemmi-0.7.3-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/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/db/0c/6c3f879a0f8e891625817637fad902da6e764e36919ed091dc77529004ac/h5py-3.14.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-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/23/95/499b4e56452ef8b6c95a271af0dde08dac4ddb70515a75f346d4f400579b/h5py-3.15.1-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/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-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/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.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/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/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-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/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-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/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-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/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fc/8e/0a18d6d7d2d0a2e66585032a760d13662e5250c784d53ad50434e9560991/matplotlib-3.10.6-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/cd/b145f9797126f3f809d177ca378de57c45413c5099c5990de2658760594a/matplotlib-3.10.7-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-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/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-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/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-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/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ce/3d/71b2dffd3a1c743ffe13296ff701ee503feaebc3f04d0e75613b6563c374/msgspec-0.19.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/74/7d/36b045c23a1ab98507aefd44fd8b264ee1dd5e5010543c6fccf82141ccef/multidict-6.6.4-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0a/a2/a4f78cb2241fe5664a22a10332f2be886dcdea8784c9f6a01c272da9b426/numpy-2.3.3-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/a7/e7/ae86261695b6c8a36d6a4c8d5f9b9ede8248510d689a2f379a18354b37d7/pandas-2.3.2-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7d/10/f8850982021cb90e2ec31990291f9e830ce7d94eef432b15066e7cbe0bec/numpy-2.3.4-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7c/58/e60915c59f4adcbd97af30047694978127d63139ae05a0cf987c6f2e90f9/pycifrw-5.0.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-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/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.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/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/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.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/a6/a1/409c1651c9f874d598c10f51ff586c416625601df4bca315d08baec4c3e3/pywinpty-3.0.2-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/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/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.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/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/d6/73/c449a7d56ba6e6f874183759f8483cde21f900a8be117d67ffbb670c2958/scipy-1.16.2-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - 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/ee/13/744a32ebe3b4a7a9c7ea4e57babae7aa22070d47acf330d8e5a1359607f1/sqlalchemy-2.0.43-cp311-cp311-win_amd64.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/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/23/907193c2f4d680aedbfbdf7bf24c13925e3c7c292e813326c1b84a0b878e/sqlalchemy-2.0.44-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-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/1a/c4/0082f437bac162ab95e5a3a389a184c122d45eb5593960aab92fdf80374b/uv-0.8.17-py3-none-win_amd64.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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/cd/667f6249a9a3a8d2d7ba1aa72db6b1fc6cdaf7b0d7aeda43478702e2a13e/uv-0.9.3-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl py313-dev: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -1718,699 +1965,730 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py313h7033f15_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py313hf01b4d8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.16-py313h5d5ffb9_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh82676e8_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py313h78bf25f_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1423503_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.1-he9a06e4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_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/markupsafe-3.0.2-py313h8060acc_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.4.1-heeeca48_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.9.0-heeeca48_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.0.0-py313h07c4f96_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.7-h2b335a9_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.8-h2b335a9_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py313h8060acc_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hfb55c3c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py313h843e2db_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh0d859eb_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh0d859eb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py313h07c4f96_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py313h54dd161_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1e/02/45b388b49e37933f316e1fb39c0de6fb1d77384b0c8f4cf6af5f2cbe3ea6/aiohttp-3.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.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/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-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/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.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/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-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/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.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/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-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/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/47/807e225343ee50f71132bc6185d62230c8f6950e3e6a6e161ae11eed9495/diffpy.pdffit2-1.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_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/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f2/9f/bf231c2a3fac99d1d7f1d89c76594f158693f981a4aa02be406e9f036832/fonttools-4.59.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-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/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.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/db/9e/024450978a674b2f021aa1f46b6fa73823713c7fe8b5d713fbd6defdb157/gemmi-0.7.3-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/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e7/ec/86f59025306dcc6deee5fda54d980d077075b8d9889aac80f158bd585f1b/h5py-3.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-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/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.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/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-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/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.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/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/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-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/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-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/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-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/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/b8/9eea6630198cb303d131d95d285a024b3b8645b1763a2916fddb44ca8760/matplotlib-3.10.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/ff/6425bf5c20d79aa5b959d1ce9e65f599632345391381c9a104133fe0b171/matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-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/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-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/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-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/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - 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/9d/87/bc14f49bc95c4cb0dd0a8c56028a67c014ee7e6818ccdce74a4862af259b/msgspec-0.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/8f/52/0634adaace9be2d8cac9ef78f05c47f3a675882e068438b9d7ec7ef0c13f/pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-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/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/a5/a8c7562ec39f2647245b52ea4aeb13b5b125b3f48c0c152e9ebce7047a0a/pycifrw-5.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-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/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.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/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/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - 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/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/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.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/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - 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/dc/29/11ae2c2b981de60187f7cbc84277d9d21f101093d1b2e945c63774477aba/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.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/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-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/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-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/8f/35/cb47d2d07a383c07b0e5043c6fe5555f0fd79683c6d7f9760222987c8be9/uv-0.8.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/45/488417c6c0127c00bcdfac3556ae2ea0597df8245fe5f9bcfda35ebdbe85/uv-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl osx-64: - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py313h585f44e_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py313h253db18_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-1.17.1-py313he20ea1e_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.16-py313h03db916_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/jsonpointer-3.0.0-py313habf4b1d_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.1-h3d58e20_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.6-h281671d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.20-hfdf4475_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_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/markupsafe-3.0.2-py313h717bdf5_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.4.1-h2e7699b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.2-h6e31bce_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.9.0-h09bb5a9_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.0.0-py313h585f44e_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-11.1-py313h6971d95_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-11.1-py313h55ae1d7_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.7-h5eba815_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.8-h2bd861f_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py313h717bdf5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py312hb7d603e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.1-py313h66e1e84_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.2-py313h585f44e_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zeromq-4.3.5-h6c33b1e_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py313hcb05632_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/13/18/1ac95683e1c1d48ef4503965c96f5401618a04c139edae12e200392daae8/aiohttp-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.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/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-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/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.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/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-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/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.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/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-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/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ed/a1/1aec6b9c471074a184c93ffe95fa16593ebd96a017b1a08a4aebed5cd6c6/diffpy.pdffit2-1.5.1-cp313-cp313-macosx_13_0_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/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/be/fc5fe58dd76af7127b769b68071dbc32d4b95adc8b58d1d28d42d93c90f2/fonttools-4.59.2-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-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/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.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/93/e2/c45cd48ec7cc0f49e182d8a736355a61edf0fc2ac060b90fc822c4fca067/gemmi-0.7.3-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/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-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/6c/c2/7efe82d09ca10afd77cd7c286e42342d520c049a8c43650194928bcc635c/h5py-3.14.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/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-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/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.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/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/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-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/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-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/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-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/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/db/18380e788bb837e724358287b08e223b32bc8dccb3b0c12fa8ca20bc7f3b/matplotlib-3.10.6-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/02/9c/207547916a02c78f6bdd83448d9b21afbc42f6379ed887ecf610984f3b4e/matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-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/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-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/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-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/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - 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/3c/cb/2842c312bbe618d8fefc8b9cedce37f773cdc8fa453306546dba2c21fd98/msgspec-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7d/b9/984c2b1ee61a8b803bf63582b4ac4242cf76e2dbd663efeafcb620cc0ccb/numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/27/64/a2f7bf678af502e16b472527735d168b22b7824e45a4d7e96a4fbb634b59/pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-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/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/0d/6af0bb9a45c771ffccd5c4c035c57ac9005e711b1191ddad1dd954187cfe/pycifrw-5.0.1-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-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/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.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/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/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - 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/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/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.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/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - 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/41/1c/a7260bd47a6fae7e03768bf66451437b36451143f36b285522b865987ced/sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.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/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-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/65/34/609b72034df0c62bcfb0c0ad4b11e2b55e537c0f0817588b5337d3dcca71/uv-0.8.17-py3-none-macosx_10_12_x86_64.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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/1a/8e68d0020c29f6f329a265773c23b0c01e002794ea884b8bdbd594c7ea97/uv-0.9.3-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl osx-arm64: - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313hcdf3177_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py313hb4b7877_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-1.17.1-py313h755b2b2_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.16-py313hab38a8b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py313h8f79df9_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.1-hf598326_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.3-hf598326_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_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/markupsafe-3.0.2-py313ha9b7d5b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-24.4.1-hab9d20b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.2-he92f556_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.0.0-py313hcdf3177_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-11.1-py313had225c5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-11.1-py313h4e140e3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.7-h5c937ed_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.8-h09175d0_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.2-py313ha9b7d5b_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312hd65ceae_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py313h80e0809_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py313hcdf3177_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h888dc83_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py313h9734d34_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fd/79/ef0d477c771a642d1a881b92d226314c43d3c74bc674c93e12e679397a97/aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.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/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-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/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.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/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-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/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.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/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-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/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/d9/e96af817e50310f9891dd67399e8e9fd651b21a7c8be13ed21d04ceaeb61/diffpy.pdffit2-1.5.1-cp313-cp313-macosx_13_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/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/7b/d0d3b9431642947b5805201fbbbe938a47b70c76685ef1f0cb5f5d7140d6/fonttools-4.59.2-cp313-cp313-macosx_10_13_universal2.whl - - pypi: https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-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/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.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/60/06/6e3a083a02d2a1b7da69dce5538d51b4a83dc308e3ea9e21edcf324e10de/gemmi-0.7.3-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/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-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/4f/31/f570fab1239b0d9441024b92b6ad03bb414ffa69101a985e4c83d37608bd/h5py-3.14.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/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-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/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.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/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/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-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/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-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/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-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/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/0f/38dd49445b297e0d4f12a322c30779df0d43cb5873c7847df8a82e82ec67/matplotlib-3.10.6-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-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/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-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/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-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/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - 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/58/95/c40b01b93465e1a5f3b6c7d91b10fb574818163740cc3acbe722d1e0e7e4/msgspec-0.19.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a6/e4/07970e3bed0b1384d22af1e9912527ecbeb47d3b26e9b6a3bced068b3bea/numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/54/4c/c3d21b2b7769ef2f4c2b9299fcadd601efa6729f1357a8dbce8dd949ed70/pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-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/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/81/bdd4bfabe70b7c9a8c0716a722ced4ebd27311afd1f4800cd405d3229c1b/pycifrw-5.0.1-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-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/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.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/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/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - 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/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/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.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/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - 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/8e/84/8a337454e82388283830b3586ad7847aa9c76fdd4f1df09cdd1f94591873/sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.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/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-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 - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-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/b6/bc/9417df48f0c18a9d54c2444096e03f2f56a3534c5b869f50ac620729cbc8/uv-0.8.17-py3-none-macosx_11_0_arm64.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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/25/6df8be6cd549200e80d19374579689fda39b18735afde841345284fb113d/uv-0.9.3-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl win-64: - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py313hfe59770_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-h4c7d964_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-1.17.1-py313h5ea7bf4_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.16-py313h927ade5_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/icu-75.1-he0c23c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh3521513_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyh6be1c34_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/jsonpointer-3.0.0-py313hfa70ccb_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh5737063_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.21.3-hdf4eb48_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-35_h5709861_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.9.0-35_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda @@ -2419,155 +2697,242 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.20-hc70643c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.50.4-hf5d6505_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_9.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.0-h06f855e_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_0.conda + - 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.0-h06f855e_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_1.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-20.1.8-hfa2b4ca_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.2-py313hb4c8b1a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.3-hfa2b4ca_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2024.2.2-h57928b3_16.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.4.1-he453025_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.2-h725018a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.9.0-he453025_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.0.0-py313h5ea7bf4_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.7-hdf00ec1_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.8-hdf00ec1_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.2-py313hb4c8b1a_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312hbb5da91_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.27.1-py313hfbe8231_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh5737063_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2021.13.0-h18a62a1_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh5737063_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.2-py313h5ea7bf4_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_31.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_31.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_31.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h5bddc39_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py313h5fd188c_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h2b53caa_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c9/58/afab7f2b9e7df88c995995172eb78cae8a3d5a62d5681abaade86b3f0089/aiohttp-3.13.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.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/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-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/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.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/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-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/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.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/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-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/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/93/8239b7cd3bb4c134295c1c07318c6fac06fe920b0f615787bb67738e867a/diffpy.pdffit2-1.5.1-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/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/a9/be7219fc64a6026cc0aded17fa3720f9277001c185434230bd351bf678e6/fonttools-4.59.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-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/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.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/28/38/c5c1b52c16ef70b9e7e0392f6298b93dd17783c3c34a92512825b1617841/gemmi-0.7.3-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/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/3f/6d/0084ed0b78d4fd3e7530c32491f2884140d9b06365dac8a08de726421d4a/h5py-3.14.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-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/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-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/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-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/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.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/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/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-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/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-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/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-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/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ff/1d/70c28528794f6410ee2856cd729fa1f1756498b8d3126443b0a94e1a8695/matplotlib-3.10.6-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e1/b6/23064a96308b9aeceeffa65e96bcde459a2ea4934d311dee20afde7407a0/matplotlib-3.10.7-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-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/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-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/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-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/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/23/d8/f15b40611c2d5753d1abb0ca0da0c75348daf1252220e5dda2867bd81062/msgspec-0.19.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/b5/263ebbbbcede85028f30047eab3d58028d7ebe389d6493fc95ae66c636ab/numpy-2.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/22/3c/f2af1ce8840ef648584a6156489636b5692c162771918aa95707c165ad2b/pandas-2.3.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9f/9b/50835e8fd86073fa7aa921df61b4cebc1f0ff400e4338541675cb72b5507/pycifrw-5.0.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-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/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.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/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/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.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/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-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/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/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.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/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - 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/ac/a5/ca2f07a2a201f9497de1928f787926613db6307992fe5cda97624eb07c2f/sqlalchemy-2.0.43-cp313-cp313-win_amd64.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/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-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/1a/c4/0082f437bac162ab95e5a3a389a184c122d45eb5593960aab92fdf80374b/uv-0.8.17-py3-none-win_amd64.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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/cd/667f6249a9a3a8d2d7ba1aa72db6b1fc6cdaf7b0d7aeda43478702e2a13e/uv-0.9.3-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl packages: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 @@ -2590,26 +2955,15 @@ packages: purls: [] size: 23621 timestamp: 1650670423406 -- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 - md5: aaa2a381ccc56eac91d63b6c1240312f - depends: - - cpython - - python-gil - license: MIT - license_family: MIT - purls: [] - size: 8191 - timestamp: 1744137672556 - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl name: aiohappyeyeballs version: 2.6.1 sha256: f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/10/97/ad2b18700708452400278039272032170246a1bf8ec5d832772372c71f1a/aiohttp-3.12.15-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/13/18/1ac95683e1c1d48ef4503965c96f5401618a04c139edae12e200392daae8/aiohttp-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl name: aiohttp - version: 3.12.15 - sha256: edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d + version: 3.13.0 + sha256: 059978d2fddc462e9211362cbc8446747ecd930537fa559d3d25c256f032ff54 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2622,11 +2976,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/1e/02/45b388b49e37933f316e1fb39c0de6fb1d77384b0c8f4cf6af5f2cbe3ea6/aiohttp-3.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: aiohttp - version: 3.12.15 - sha256: 1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84 + version: 3.13.0 + sha256: d169c47e40c911f728439da853b6fd06da83761012e6e76f11cb62cddae7282b requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2639,11 +2994,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/3e/bd/485d98b372a2cd6998484a93ddd401ec6b6031657661c36846a10e2a1f6e/aiohttp-3.13.0-cp311-cp311-win_amd64.whl name: aiohttp - version: 3.12.15 - sha256: 3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4 + version: 3.13.0 + sha256: 0f19f7798996d4458c669bd770504f710014926e9970f4729cf55853ae200469 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2656,11 +3012,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/62/6c/94846f576f1d11df0c2e41d3001000527c0fdf63fce7e69b3927a731325d/aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/51/6d/7b1e020fe1d2a2be7cf0ce5e35922f345e3507cf337faa1a6563c42065c1/aiohttp-3.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: aiohttp - version: 3.12.15 - sha256: 3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9 + version: 3.13.0 + sha256: cc6d5fc5edbfb8041d9607f6a417997fa4d02de78284d386bea7ab767b5ea4f3 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2673,11 +3030,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/71/f9/0a31fcb1a7d4629ac9d8f01f1cb9242e2f9943f47f5d03215af91c3c1a26/aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/89/a6/e1c061b079fed04ffd6777950c82f2e8246fd08b7b3c4f56fdd47f697e5a/aiohttp-3.13.0-cp311-cp311-macosx_11_0_arm64.whl name: aiohttp - version: 3.12.15 - sha256: 010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe + version: 3.13.0 + sha256: 3e6a38366f7f0d0f6ed7a1198055150c52fda552b107dad4785c0852ad7685d1 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2690,11 +3048,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/ae/f9/2d6d93fd57ab4726e18a7cdab083772eda8302d682620fbf2aef48322351/aiohttp-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl name: aiohttp - version: 3.12.15 - sha256: 5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d + version: 3.13.0 + sha256: 4696665b2713021c6eba3e2b882a86013763b442577fe5d2056a42111e732eca requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2707,11 +3066,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/c9/58/afab7f2b9e7df88c995995172eb78cae8a3d5a62d5681abaade86b3f0089/aiohttp-3.13.0-cp313-cp313-win_amd64.whl name: aiohttp - version: 3.12.15 - sha256: 2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd + version: 3.13.0 + sha256: 7897298b3eedc790257fef8a6ec582ca04e9dbe568ba4a9a890913b925b8ea21 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2724,11 +3084,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/c6/82/1ddf0ea4f2f3afe79dffed5e8a246737cff6cbe781887a6a170299e33204/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/fd/79/ef0d477c771a642d1a881b92d226314c43d3c74bc674c93e12e679397a97/aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl name: aiohttp - version: 3.12.15 - sha256: b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b + version: 3.13.0 + sha256: 564b36512a7da3b386143c611867e3f7cfb249300a1bf60889bd9985da67ab77 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2741,6 +3102,7 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl name: aiosignal @@ -2750,184 +3112,81 @@ packages: - frozenlist>=1.1.0 - typing-extensions>=4.2 ; python_full_version < '3.13' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - sha256: d1b50686672ebe7041e44811eda563e45b94a8354db67eca659040392ac74d63 - md5: cc2613bfa71dec0eb2113ee21ac9ccbf - depends: - - exceptiongroup >=1.0.2 - - idna >=2.8 - - python >=3.9 - - sniffio >=1.1 - - typing_extensions >=4.5 - - python - constrains: - - trio >=0.26.1 - - uvloop >=0.21 - license: MIT - license_family: MIT - purls: - - pkg:pypi/anyio?source=compressed-mapping - size: 134857 - timestamp: 1754315087747 -- conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - sha256: 8f032b140ea4159806e4969a68b4a3c0a7cab1ad936eb958a2b5ffe5335e19bf - md5: 54898d0f524c9dee622d44bbb081a8ab - depends: - - python >=3.9 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/appnope?source=hash-mapping - size: 10076 - timestamp: 1733332433806 -- conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - sha256: bea62005badcb98b1ae1796ec5d70ea0fc9539e7d59708ac4e7d41e2f4bb0bad - md5: 8ac12aff0860280ee0cff7fa2cf63f3b - depends: +- pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + name: anyio + version: 4.11.0 + sha256: 0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc + requires_dist: + - exceptiongroup>=1.0.2 ; python_full_version < '3.11' + - idna>=2.8 + - sniffio>=1.1 + - typing-extensions>=4.5 ; python_full_version < '3.13' + - trio>=0.31.0 ; extra == 'trio' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl + name: appnope + version: 0.1.4 + sha256: 502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + name: argon2-cffi + version: 25.1.0 + sha256: fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741 + requires_dist: - argon2-cffi-bindings - - python >=3.9 - - typing-extensions - constrains: - - argon2_cffi ==999 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi?source=hash-mapping - size: 18715 - timestamp: 1749017288144 -- conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py311h49ec1c0_0.conda - sha256: d6d2f38ece253492a3e00800b5d4a5c2cc4b2de73b2c0fcc580c218f1cf58de6 - md5: 112c5e2b7fe99e3678bbd64316d38f0c - depends: - - __glibc >=2.17,<3.0.a0 - - cffi >=1.0.1 - - libgcc >=14 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 35657 - timestamp: 1753994819264 -- conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_0.conda - sha256: 659d3876bd07ae8548b9be869708507bf56112ac300e43ca05860d5fbe73072e - md5: 99b4a1dea9e1d402c26dbbc0aef4f47c - depends: - - __glibc >=2.17,<3.0.a0 - - cffi >=1.0.1 - - libgcc >=14 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 35811 - timestamp: 1753994992173 -- conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py311h13e5629_0.conda - sha256: cab14d6bdcaf64f0911dcd994b51ddb753650d041a74c87a2107041763605e66 - md5: 8051f5bb22c95da482362f7a8c35fd68 - depends: - - __osx >=10.13 - - cffi >=1.0.1 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 33400 - timestamp: 1753995045262 -- conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py313h585f44e_0.conda - sha256: 1ff3a2d7f533aa8b3c3ab1ea812aee4a21584b77c0308a3df5eaf3e7a7f21369 - md5: aa855240032c28272b48199eda601486 - depends: - - __osx >=10.13 - - cffi >=1.0.1 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 33475 - timestamp: 1753994999531 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py311h3696347_0.conda - sha256: f5b4102716a568877e212a9d4c677027b887f215d4735acfe4532efb2da59de1 - md5: 3b4ba20f581ec2268df5a76c64232ae5 - depends: - - __osx >=11.0 - - cffi >=1.0.1 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 34325 - timestamp: 1753995031680 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313hcdf3177_0.conda - sha256: 4318f43b5a524caf824f65a1ca7428d0a4a659d7c0a27f70adabfc660280fdc6 - md5: 337c72a8f36fb82bbd21bad12d502909 - depends: - - __osx >=11.0 - - cffi >=1.0.1 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 34145 - timestamp: 1753995019248 -- conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py311h3485c13_0.conda - sha256: 4bde4487abbca4c8834a582928a80692a32ebba67e906ce676e931035a13d004 - md5: fdb37c9bd914e2a2c20f204f9cb15e6b - depends: - - cffi >=1.0.1 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 38615 - timestamp: 1753995128176 -- conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_0.conda - sha256: b40e78275538abbf138a1a0233dbdca876bb4c03295a06ea0c475ee846d741c0 - md5: f68ecfe2b2dcd299454f3e3ee0968e2f - depends: - - cffi >=1.0.1 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 38542 - timestamp: 1753995252162 -- conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - sha256: c4b0bdb3d5dee50b60db92f99da3e4c524d5240aafc0a5fcc15e45ae2d1a3cd1 - md5: 46b53236fdd990271b03c3978d4218a9 - depends: - - python >=3.9 - - python-dateutil >=2.7.0 - - types-python-dateutil >=2.8.10 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/arrow?source=hash-mapping - size: 99951 - timestamp: 1733584345583 + requires_python: '>=3.8' +- 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 + name: argon2-cffi-bindings + version: 25.1.0 + sha256: d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a + requires_dist: + - cffi>=1.0.1 ; python_full_version < '3.14' + - cffi>=2.0.0b1 ; python_full_version >= '3.14' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl + name: argon2-cffi-bindings + version: 25.1.0 + sha256: 2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44 + requires_dist: + - cffi>=1.0.1 ; python_full_version < '3.14' + - cffi>=2.0.0b1 ; python_full_version >= '3.14' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl + name: argon2-cffi-bindings + version: 25.1.0 + sha256: 7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0 + requires_dist: + - cffi>=1.0.1 ; python_full_version < '3.14' + - cffi>=2.0.0b1 ; python_full_version >= '3.14' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl + name: argon2-cffi-bindings + version: 25.1.0 + sha256: a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98 + requires_dist: + - cffi>=1.0.1 ; python_full_version < '3.14' + - cffi>=2.0.0b1 ; python_full_version >= '3.14' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl + name: arrow + version: 1.3.0 + sha256: c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80 + requires_dist: + - python-dateutil>=2.7.0 + - types-python-dateutil>=2.8.10 + - doc8 ; extra == 'doc' + - sphinx>=7.0.0 ; extra == 'doc' + - sphinx-autobuild ; extra == 'doc' + - sphinx-autodoc-typehints ; extra == 'doc' + - sphinx-rtd-theme>=1.3.0 ; extra == 'doc' + - dateparser==1.* ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytz==2021.1 ; extra == 'test' + - simplejson==3.* ; extra == 'test' + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl name: asciichartpy version: 1.5.25 @@ -2948,251 +3207,129 @@ packages: - coverage ; extra == 'test' - asteval[dev,doc,test] ; extra == 'all' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - sha256: 93b14414b3b3ed91e286e1cbe4e7a60c4e1b1c730b0814d1e452a8ac4b9af593 - md5: 8f587de4bcf981e26228f268df374a9b - depends: - - python >=3.9 - constrains: - - astroid >=2,<4 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/asttokens?source=hash-mapping - size: 28206 - timestamp: 1733250564754 -- conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - sha256: 3b7233041e462d9eeb93ea1dfe7b18aca9c358832517072054bb8761df0c324b - md5: d9d0f99095a9bb7e3641bca8c6ad2ac7 - depends: - - python >=3.9 - - typing_extensions >=4.0.0 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/async-lru?source=hash-mapping - size: 17335 - timestamp: 1742153708859 -- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - sha256: 99c53ffbcb5dc58084faf18587b215f9ac8ced36bbfb55fa807c00967e419019 - md5: a10d11958cadc13fdb43df75f8b1903f - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/attrs?source=hash-mapping - size: 57181 - timestamp: 1741918625732 -- conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - sha256: 1c656a35800b7f57f7371605bc6507c8d3ad60fbaaec65876fce7f73df1fc8ac - md5: 0a01c169f0ab0f91b26e77a3301fbfe4 - depends: - - python >=3.9 - - pytz >=2015.7 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/babel?source=hash-mapping - size: 6938256 - timestamp: 1738490268466 -- conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - sha256: d2124c0ea13527c7f54582269b3ae19541141a3740d6d779e7aa95aa82eaf561 - md5: de0fd9702fd4c1186e930b8c35af6b6b - depends: - - python >=3.10 - - soupsieve >=1.2 - - typing-extensions - license: MIT - license_family: MIT - purls: - - pkg:pypi/beautifulsoup4?source=compressed-mapping - size: 88278 - timestamp: 1756094375546 +- pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + name: asttokens + version: 3.0.0 + sha256: e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2 + requires_dist: + - astroid>=2,<4 ; extra == 'astroid' + - astroid>=2,<4 ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-xdist ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + name: async-lru + version: 2.0.5 + sha256: ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943 + requires_dist: + - typing-extensions>=4.0.0 ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + name: attrs + version: 25.4.0 + sha256: adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + name: autopep8 + version: 2.3.2 + sha256: ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128 + requires_dist: + - pycodestyle>=2.12.0 + - tomli ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + name: babel + version: 2.17.0 + sha256: 4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2 + requires_dist: + - pytz>=2015.7 ; python_full_version < '3.9' + - tzdata ; sys_platform == 'win32' and extra == 'dev' + - backports-zoneinfo ; python_full_version < '3.9' and extra == 'dev' + - freezegun~=1.0 ; extra == 'dev' + - jinja2>=3.0 ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest>=6.0 ; extra == 'dev' + - pytz ; extra == 'dev' + - setuptools ; extra == 'dev' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + name: backrefs + version: '5.9' + sha256: cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b + requires_dist: + - regex ; extra == 'extras' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl + name: backrefs + version: '5.9' + sha256: 6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf + requires_dist: + - regex ; extra == 'extras' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl + name: beautifulsoup4 + version: 4.14.2 + sha256: 5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515 + requires_dist: + - soupsieve>1.2 + - typing-extensions>=4.0.0 + - cchardet ; extra == 'cchardet' + - chardet ; extra == 'chardet' + - charset-normalizer ; extra == 'charset-normalizer' + - html5lib ; extra == 'html5lib' + - lxml ; extra == 'lxml' + requires_python: '>=3.7.0' - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl name: bidict version: 0.23.1 sha256: 5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5 requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - sha256: a05971bb80cca50ce9977aad3f7fc053e54ea7d5321523efc7b9a6e12901d3cd - md5: f0b4c8e370446ef89797608d60a564b3 - depends: - - python >=3.9 +- pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl + name: bleach + version: 6.2.0 + sha256: 117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e + requires_dist: - webencodings - - python - constrains: - - tinycss >=1.1.0,<1.5 - license: Apache-2.0 AND MIT - purls: - - pkg:pypi/bleach?source=hash-mapping - size: 141405 - timestamp: 1737382993425 -- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - sha256: 0aba699344275b3972bd751f9403316edea2ceb942db12f9f493b63c74774a46 - md5: a30e9406c873940383555af4c873220d - depends: - - bleach ==6.2.0 pyh29332c3_4 - - tinycss2 - license: Apache-2.0 AND MIT - purls: [] - size: 4213 - timestamp: 1737382993425 + - tinycss2>=1.1.0,<1.5 ; extra == 'css' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl name: blinker version: 1.9.0 sha256: ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py311h1ddb823_4.conda - sha256: 318d4985acbf46457d254fbd6f0df80cc069890b5fc0013b3546d88eee1b1a1f - md5: 7138a06a7b0d11a23cfae323e6010a08 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - libbrotlicommon 1.1.0 hb03c661_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=compressed-mapping - size: 354304 - timestamp: 1756599521587 -- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py313h7033f15_4.conda - sha256: b1941426e564d326097ded7af8b525540be219be7a88ca961d58a8d4fc116db2 - md5: bc8624c405856b1d047dd0a81829b08c - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - constrains: - - libbrotlicommon 1.1.0 hb03c661_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=compressed-mapping - size: 353639 - timestamp: 1756599425945 -- conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py311h7b20566_4.conda - sha256: 10afc3a0df8e7447c56b0753848336eeeeea04be9bf1817569c45755392de14b - md5: 13de3b969fd0ba12c4f6f9513f486f16 - depends: - - __osx >=10.13 - - libcxx >=19 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - libbrotlicommon 1.1.0 h1c43f85_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=hash-mapping - size: 368751 - timestamp: 1756600247737 -- conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py313h253db18_4.conda - sha256: fc4db6916598d1c634de85337db6d351d6f1cb8a93679715e0ee572777a5007e - md5: 8643345f12d0db3096a8aa0abd74f6e9 - depends: - - __osx >=10.13 - - libcxx >=19 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - constrains: - - libbrotlicommon 1.1.0 h1c43f85_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=compressed-mapping - size: 369082 - timestamp: 1756600456664 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py311hf719da1_4.conda - sha256: 64645da991de052f0e522486cf97f8457fb37ed5c30d67655d3a32d2b9f56167 - md5: 4cd43bb7ba1a3cb4cd7a2e335f6d3c32 - depends: - - __osx >=11.0 - - libcxx >=19 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - constrains: - - libbrotlicommon 1.1.0 h6caf38d_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=compressed-mapping - size: 340889 - timestamp: 1756599941690 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py313hb4b7877_4.conda - sha256: a6402a7186ace5c3eb21ed4ce50eda3592c44ce38ab4e9a7ddd57d72b1e61fb3 - md5: 9518cd948fc334d66119c16a2106a959 - depends: - - __osx >=11.0 - - libcxx >=19 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - constrains: - - libbrotlicommon 1.1.0 h6caf38d_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=compressed-mapping - size: 341104 - timestamp: 1756600117644 -- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py311h3e6a449_4.conda - sha256: d524edc172239fec70ad946e3b2fa1b9d7eea145ad80e9e66da25a4d815770ea - md5: 21d3a7fa95d27938158009cd08e461f2 - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - constrains: - - libbrotlicommon 1.1.0 hfd05255_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=hash-mapping - size: 323289 - timestamp: 1756600106141 -- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py313hfe59770_4.conda - sha256: 0e98ebafd586c4da7d848f9de94770cb27653ba9232a2badb28f8a01f6e48fb5 - md5: 477bf04a8a3030368068ccd39b8c5532 - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - constrains: - - libbrotlicommon 1.1.0 hfd05255_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=compressed-mapping - size: 323459 - timestamp: 1756600051044 -- pypi: https://files.pythonhosted.org/packages/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + name: build + version: 1.3.0 + sha256: 7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4 + requires_dist: + - packaging>=19.1 + - pyproject-hooks + - colorama ; os_name == 'nt' + - importlib-metadata>=4.6 ; python_full_version < '3.10.2' + - tomli>=1.1.0 ; python_full_version < '3.11' + - uv>=0.1.18 ; extra == 'uv' + - virtualenv>=20.11 ; python_full_version < '3.10' and extra == 'virtualenv' + - virtualenv>=20.17 ; python_full_version >= '3.10' and python_full_version < '3.14' and extra == 'virtualenv' + - virtualenv>=20.31 ; python_full_version >= '3.14' and extra == 'virtualenv' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl name: bumps - version: 1.0.2 - sha256: 5bdd117c0afc85caea0444daa3571a24f84eb68ce4f1a5f5574b9cdb03cd8fce + version: 1.0.3 + sha256: 4f503c814b9ddd2cda760b2e35aaa6285651434fc2e64ccac55b1a666bca17f6 requires_dist: - numpy - scipy - h5py - dill + - cloudpickle - matplotlib - blinker - aiohttp - python-socketio - plotly - mpld3 + - msgpack - graphlib-backport ; python_full_version < '3.9' - build ; extra == 'dev' - pre-commit ; extra == 'dev' @@ -3203,7 +3340,7 @@ packages: - setuptools ; extra == 'dev' - sphinx ; extra == 'dev' - versioningit ; extra == 'dev' - requires_python: '>=3.8' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda sha256: c30daba32ddebbb7ded490f0e371eae90f51e72db620554089103b4a6934b0d5 md5: 51a19bba1b8ebfb60df25cde030b7ebc @@ -3247,228 +3384,144 @@ packages: purls: [] size: 55977 timestamp: 1757437738856 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-h4c7d964_0.conda - sha256: 3b82f62baad3fd33827b01b0426e8203a2786c8f452f633740868296bcbe8485 - md5: c9e0c0f82f6e63323827db462b40ede8 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-h4c7d964_0.conda + sha256: bfb7f9f242f441fdcd80f1199edd2ecf09acea0f2bcef6f07d7cbb1a8131a345 + md5: e54200a1cd1fe33d61c9df8d3b00b743 depends: - __win license: ISC purls: [] - size: 154489 - timestamp: 1754210967212 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - sha256: 837b795a2bb39b75694ba910c13c15fa4998d4bb2a622c214a6a5174b2ae53d1 - md5: 74784ee3d225fc3dca89edb635b4e5cc + size: 156354 + timestamp: 1759649104842 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda + sha256: 3b5ad78b8bb61b6cdc0978a6a99f8dfb2cc789a451378d054698441005ecbdb6 + md5: f9e5fbc24009179e8b0409624691758a depends: - __unix license: ISC purls: [] - size: 154402 - timestamp: 1754210968730 -- conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - noarch: python - sha256: 561e6660f26c35d137ee150187d89767c988413c978e1b712d53f27ddf70ea17 - md5: 9b347a7ec10940d3f7941ff6c460b551 - depends: - - cached_property >=1.5.2,<1.5.3.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 4134 - timestamp: 1615209571450 -- conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - sha256: 6dbf7a5070cc43d90a1e4c2ec0c541c69d8e30a0e25f50ce9f6e4a432e42c5d7 - md5: 576d629e47797577ab0f1b351297ef4a - depends: - - python >=3.6 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/cached-property?source=hash-mapping - size: 11065 - timestamp: 1615209567874 -- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - sha256: a1ad5b0a2a242f439608f22a538d2175cac4444b7b3f4e2b8c090ac337aaea40 - md5: 11f59985f49df4620890f3e746ed7102 - depends: - - python >=3.9 - license: ISC - purls: - - pkg:pypi/certifi?source=compressed-mapping - size: 158692 - timestamp: 1754231530168 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py311h5b438cf_1.conda - sha256: bbd04c8729e6400fa358536b1007c1376cc396d569b71de10f1df7669d44170e - md5: 82e0123a459d095ac99c76d150ccdacf - depends: - - __glibc >=2.17,<3.0.a0 - - libffi >=3.4.6,<3.5.0a0 - - libgcc >=14 - - pycparser - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=compressed-mapping - size: 303055 - timestamp: 1756808613387 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py313hf01b4d8_1.conda - sha256: 2c2d68ef3480c22e0d5837b9314579b4a8484ccfed264b8b7d5da70f695afdd9 - md5: c4a0f01c46bc155d205694bec57bd709 - depends: - - __glibc >=2.17,<3.0.a0 - - libffi >=3.4.6,<3.5.0a0 - - libgcc >=14 - - pycparser - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 297231 - timestamp: 1756808418076 -- conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-1.17.1-py311he66fa18_1.conda - sha256: f5c6c73e0a389d2c89e10b36883e18cd3abd14756d9d01d53856aaae3131f219 - md5: 70cd671f73c5c08899d5c43366d37787 - depends: - - __osx >=10.13 - - libffi >=3.4.6,<3.5.0a0 - - pycparser - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 294021 - timestamp: 1756808523082 -- conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-1.17.1-py313he20ea1e_1.conda - sha256: be88bd2cbb3f1f4e16326affc22b2c26f926dd18e03defc24df1fe6c80e7ce18 - md5: fc55afa9103145b51e5227a4ef0b8bad - depends: - - __osx >=10.13 - - libffi >=3.4.6,<3.5.0a0 - - pycparser - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 289490 - timestamp: 1756808563 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-1.17.1-py311h146a0b8_1.conda - sha256: 97635f50d473eae17e11b954570efdc66b615dfa6321dd069742d6df4c14a8ba - md5: 1c72ccc307e7681c34e1c06c1711ee33 - depends: - - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 - - pycparser - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=compressed-mapping - size: 293204 - timestamp: 1756808628759 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-1.17.1-py313h755b2b2_1.conda - sha256: 6dd0ba5c68f59eb4039399d0c51d060b89f2028acd5c2f7f6879476ab108d797 - md5: a9024b1e15f59ce83654d542f83c23be - depends: - - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 - - pycparser - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 289263 - timestamp: 1756808593662 -- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-1.17.1-py311h3485c13_1.conda - sha256: 46baee342b50ce7fbf4c52267f73327cb0512b970332037c8911afee1e54f063 - md5: 553a1836df919ca232b80ce1324fa5bb - depends: - - pycparser - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 296743 - timestamp: 1756808544874 -- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-1.17.1-py313h5ea7bf4_1.conda - sha256: 8e2cd5b827a717d4a9f14594404a7d3693a5a7b7a9a181409ca1bd24a995d78c - md5: 69a537fed13191160f1a139b6d42f6c1 - depends: - - pycparser - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 290632 - timestamp: 1756808584791 -- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - sha256: 838d5a011f0e7422be6427becba3de743c78f3874ad2743c341accbba9bb2624 - md5: 7e7d5ef1b9ed630e4a1c358d6bc62284 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/charset-normalizer?source=hash-mapping - size: 51033 - timestamp: 1754767444665 -- pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + size: 155907 + timestamp: 1759649036195 +- pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl + name: certifi + version: 2025.10.5 + sha256: 0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl + name: cffi + version: 2.0.0 + sha256: b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl + name: cffi + version: 2.0.0 + sha256: 19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75 + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl + name: cffi + version: 2.0.0 + sha256: 45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl + name: cffi + version: 2.0.0 + sha256: 00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl + name: cffi + version: 2.0.0 + sha256: 2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: cffi + version: 2.0.0 + sha256: c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26 + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl + name: cffi + version: 2.0.0 + sha256: 66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5 + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: cffi + version: 2.0.0 + sha256: 8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26 + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + name: cfgv + version: 3.4.0 + sha256: b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl + name: charset-normalizer + version: 3.4.4 + sha256: 5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: charset-normalizer + version: 3.4.4 + sha256: 840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + name: charset-normalizer + version: 3.4.4 + sha256: e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl + name: charset-normalizer + version: 3.4.4 + sha256: b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl + name: charset-normalizer + version: 3.4.4 + sha256: 6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: charset-normalizer + version: 3.4.4 + sha256: a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl name: click - version: 8.2.1 - sha256: 61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b + version: 8.3.0 + sha256: 9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc requires_dist: - colorama ; sys_platform == 'win32' requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl + name: cloudpickle + version: 3.1.1 + sha256: c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl name: colorama version: 0.4.6 sha256: 4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 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/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 - md5: 962b9857ee8e7018c22f2776ffa0b2d7 - depends: - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/colorama?source=hash-mapping - size: 27011 - timestamp: 1733218222191 -- conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - sha256: 576a44729314ad9e4e5ebe055fbf48beb8116b60e58f9070278985b2b634f212 - md5: 2da13f2b299d8e1995bafbbe9689a2f7 - depends: - - python >=3.9 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/comm?source=hash-mapping - size: 14690 - timestamp: 1753453984907 +- pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl + name: comm + version: 0.2.3 + sha256: c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417 + requires_dist: + - pytest ; extra == 'test' + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl name: contourpy version: 1.3.3 @@ -3669,32 +3722,66 @@ packages: - pytest-xdist ; extra == 'test-no-images' - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.11' -- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.11.13-py311hd8ed1ab_0.conda - noarch: generic - sha256: ab70477f5cfb60961ba27d84a4c933a24705ac4b1736d8f3da14858e95bbfa7a - md5: 4666fd336f6d48d866a58490684704cd - depends: - - python >=3.11,<3.12.0a0 - - python_abi * *_cp311 - license: Python-2.0 - purls: [] - size: 47495 - timestamp: 1749048148121 -- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - noarch: generic - sha256: e9ac20662d1c97ef96e44751a78c0057ec308f7cc208ef1fbdc868993c9f5eb3 - md5: c5623ddbd37c5dafa7754a83f97de01e - depends: - - python >=3.13,<3.14.0a0 - - python_abi * *_cp313 - license: Python-2.0 - purls: [] - size: 48174 - timestamp: 1756909387263 -- pypi: https://files.pythonhosted.org/packages/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/2a/29/2ac1dfcdd4ab9a70026edc8d715ece9b4be9a1653075c658ee6f271f394d/coverage-7.11.0-cp311-cp311-win_amd64.whl + name: coverage + version: 7.11.0 + sha256: 865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/40/b8/7a3f1f33b35cc4a6c37e759137533119560d06c0cc14753d1a803be0cd4a/coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: coverage + version: 7.11.0 + sha256: aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/49/3a/ee1074c15c408ddddddb1db7dd904f6b81bc524e01f5a1c5920e13dbde23/coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl + name: coverage + version: 7.11.0 + sha256: 3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl + name: coverage + version: 7.11.0 + sha256: cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: coverage + version: 7.11.0 + sha256: 10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/70/c4/9f44bebe5cb15f31608597b037d78799cc5f450044465bcd1ae8cb222fe1/coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl + name: coverage + version: 7.11.0 + sha256: b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl + name: coverage + version: 7.11.0 + sha256: f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl + name: coverage + version: 7.11.0 + sha256: 2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.0-py3-none-any.whl name: cryspy - version: 0.7.8 - sha256: 5d389d7b932317ca6ab60996883aefacc9df94ac6f74b9cc080600304e298577 + version: 0.8.0 + sha256: be7f08232e583f57d455aaa3de57a3b50411172b77d67d69d58d6fc4efda1a19 requires_dist: - numpy - scipy @@ -3720,154 +3807,31 @@ packages: requires_dist: - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'macos-listener' requires_python: '>=3.6' -- conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.16-py311hc665b79_1.conda - sha256: 19b0d1d9b0459db1466ad5846f6a30408ca9bbe244dcbbf32708116b564ceb11 - md5: 06e8c743932cc7788624128d08bc8806 - depends: - - python - - libgcc >=14 - - libstdcxx >=14 - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2729957 - timestamp: 1756742061937 -- conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.16-py313h5d5ffb9_1.conda - sha256: a5cefff8a7ef8a0ede9af01247bcebbc6222bc95a8e7eeeb0495765e22fc3f1c - md5: 17e1e8f60454cf198f17234ccbb2fa8d - depends: - - python - - libstdcxx >=14 - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2868399 - timestamp: 1756742083538 -- conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.16-py311hc651eee_1.conda - sha256: 7f7300ee62b58658813e99a08f4a6f8daf585420cab6330f083ae697f569e66a - md5: 32c16e5c0e427989aff77ead02bf7f88 - depends: - - python - - __osx >=10.13 - - libcxx >=19 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2665552 - timestamp: 1756742018979 -- conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.16-py313h03db916_1.conda - sha256: e57e4f91b2433d5726430cfe0a1b60cebb9743a18114614e7953ee726ff743d3 - md5: 35b920d8af8948d80dcbc01fe2d88467 - depends: - - python - - __osx >=10.13 - - libcxx >=19 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2769428 - timestamp: 1756742032895 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.16-py311ha59bd64_1.conda - sha256: eea58c83203a96c1967574d495860bab109c52e10b41c5ac12daad7b66b81e70 - md5: 9d7f1641fc7385ed8dfc337d35ad4008 - depends: - - python - - libcxx >=19 - - python 3.11.* *_cpython - - __osx >=11.0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2666633 - timestamp: 1756742041729 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.16-py313hab38a8b_1.conda - sha256: 25062aad4e332f5f083584b13f4e1e06748ad8e53779482fa62c036c761bddbd - md5: ceea9fb6d7a6205ad329692ae053736e - depends: - - python - - libcxx >=19 - - __osx >=11.0 - - python 3.13.* *_cp313 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2755888 - timestamp: 1756742003334 -- conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.16-py311h5dfdfe8_1.conda - sha256: 810fa69eca6adfbf707e2e31e26f24842ab313d2efbfdb8e73c15c164a8010d9 - md5: 5996fd469da1e196fd42c72a7b7a65ca - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 3933261 - timestamp: 1756742011482 -- conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.16-py313h927ade5_1.conda - sha256: 89e86812b7163820e7316971e7b4cfcc32db9e043cf4698b7793207c3ef2592a - md5: f8323ed8df3c1a137fd0ef88cdaec20d - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 4000266 - timestamp: 1756742028369 -- conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - sha256: c17c6b9937c08ad63cb20a26f403a3234088e57d4455600974a0ce865cb14017 - md5: 9ce473d1d1be1cc3810856a48b3fab32 - depends: - - python >=3.9 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/decorator?source=hash-mapping - size: 14129 - timestamp: 1740385067843 -- conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - sha256: 9717a059677553562a8f38ff07f3b9f61727bd614f505658b0a5ecbcf8df89be - md5: 961b3a227b437d82ad7054484cfa71b2 - depends: - - python >=3.6 - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/defusedxml?source=hash-mapping - size: 24062 - timestamp: 1615232388757 +- pypi: https://files.pythonhosted.org/packages/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-cp313-cp313-win_amd64.whl + name: debugpy + version: 1.8.17 + sha256: 6c5cd6f009ad4fca8e33e5238210dc1e5f42db07d4b6ab21ac7ffa904a196420 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl + name: debugpy + version: 1.8.17 + sha256: 60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c2/c5/c012c60a2922cc91caa9675d0ddfbb14ba59e1e36228355f41cab6483469/debugpy-1.8.17-cp311-cp311-win_amd64.whl + name: debugpy + version: 1.8.17 + sha256: b532282ad4eca958b1b2d7dbcb2b7218e02cb934165859b918e3b6ba7772d3f4 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + name: decorator + version: 5.2.1 + sha256: d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl + name: defusedxml + 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/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl name: dfo-ls version: '1.6' @@ -3963,10 +3927,23 @@ packages: - objgraph>=1.7.2 ; extra == 'graph' - gprof2dot>=2022.7.29 ; extra == 'profile' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + name: distlib + version: 0.4.0 + sha256: 9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 +- 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/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl name: easydiffraction - version: 0.7.2 - sha256: 0a76e0a22e85bc8c9267293b53c935d3b3d3b240e6c1d3741ac32b5091a5bd5d + version: 0.7.3 + sha256: f43ea86fb9492d182033653ff5ad082740d61cbb0233f676e7876deaf90d0ae9 requires_dist: - asciichartpy - asteval @@ -3985,16 +3962,49 @@ packages: - tabulate - typer - varname + - build ; extra == 'all' + - darkdetect ; extra == 'all' + - docformatter ; extra == 'all' + - interrogate ; extra == 'all' + - jinja2 ; extra == 'all' + - jupyterquiz ; extra == 'all' + - jupytext ; extra == 'all' + - mkdocs ; extra == 'all' + - mkdocs-autorefs<1.3.0 ; 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' + - interrogate ; extra == 'dev' - jinja2 ; extra == 'dev' - jupyterquiz ; extra == 'dev' - jupytext ; extra == 'dev' - nbmake ; extra == 'dev' - nbqa ; extra == 'dev' - nbstripout ; extra == 'dev' + - pre-commit ; extra == 'dev' - pytest ; extra == 'dev' - pytest-cov ; extra == 'dev' - pytest-xdist ; extra == 'dev' + - radon ; extra == 'dev' - ruff ; extra == 'dev' - validate-pyproject[all] ; extra == 'dev' - versioningit ; extra == 'dev' @@ -4011,32 +4021,51 @@ packages: - plotly ; extra == 'visualization' - py3dmol ; extra == 'visualization' requires_python: '>=3.11,<3.14' -- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - sha256: ce61f4f99401a4bd455b89909153b40b9c823276aefcbb06f2044618696009ca - md5: 72e42d28960d875c7654614f8b50939a - depends: - - python >=3.9 - - typing_extensions >=4.6.0 - license: MIT and PSF-2.0 - purls: - - pkg:pypi/exceptiongroup?source=hash-mapping - size: 21284 - timestamp: 1746947398083 -- conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - sha256: 210c8165a58fdbf16e626aac93cc4c14dbd551a01d1516be5ecad795d2422cad - md5: ff9efb7f7469aed3c4a8106ffa29593c - depends: - - python >=3.10 - license: MIT - license_family: MIT - purls: - - pkg:pypi/executing?source=compressed-mapping - size: 30753 - timestamp: 1756729456476 -- pypi: https://files.pythonhosted.org/packages/13/7b/d0d3b9431642947b5805201fbbbe938a47b70c76685ef1f0cb5f5d7140d6/fonttools-4.59.2-cp313-cp313-macosx_10_13_universal2.whl +- pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl + name: execnet + version: 2.1.1 + sha256: 26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc + requires_dist: + - hatch ; extra == 'testing' + - pre-commit ; extra == 'testing' + - pytest ; extra == 'testing' + - tox ; extra == 'testing' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + name: executing + version: 2.2.1 + sha256: 760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017 + requires_dist: + - asttokens>=2.1.0 ; extra == 'tests' + - ipython ; extra == 'tests' + - pytest ; extra == 'tests' + - coverage ; extra == 'tests' + - coverage-enable-subprocess ; extra == 'tests' + - littleutils ; extra == 'tests' + - rich ; python_full_version >= '3.11' and extra == 'tests' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + name: fastjsonschema + version: 2.21.2 + sha256: 1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463 + requires_dist: + - colorama ; extra == 'devel' + - jsonschema ; extra == 'devel' + - json-spec ; extra == 'devel' + - pylint ; extra == 'devel' + - pytest ; extra == 'devel' + - pytest-benchmark ; extra == 'devel' + - pytest-cache ; extra == 'devel' + - validictory ; extra == 'devel' +- pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + name: filelock + version: 3.20.0 + sha256: 339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl name: fonttools - version: 4.59.2 - sha256: 381bde13216ba09489864467f6bc0c57997bd729abfbb1ce6f807ba42c06cceb + version: 4.60.1 + sha256: b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4067,10 +4096,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/57/2a/976f5f9fa3b4dd911dc58d07358467bec20e813d933bc5d3db1a955dd456/fonttools-4.59.2-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/6b/47/3c63158459c95093be9618794acb1067b3f4d30dcc5c3e8114b70e67a092/fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl name: fonttools - version: 4.59.2 - sha256: 8e5e2682cf7be766d84f462ba8828d01e00c8751a8e8e7ce12d7784ccb69a30d + version: 4.60.1 + sha256: 3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4101,10 +4130,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/69/95/864726eaa8f9d4e053d0c462e64d5830ec7c599cbdf1db9e40f25ca3972e/fonttools-4.59.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl name: fonttools - version: 4.59.2 - sha256: c52694eae5d652361d59ecdb5a2246bff7cff13b6367a12da8499e9df56d148d + version: 4.60.1 + sha256: a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4135,10 +4164,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/76/be/fc5fe58dd76af7127b769b68071dbc32d4b95adc8b58d1d28d42d93c90f2/fonttools-4.59.2-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl name: fonttools - version: 4.59.2 - sha256: f33839aa091f7eef4e9078f5b7ab1b8ea4b1d8a50aeaef9fdb3611bba80869ec + version: 4.60.1 + sha256: 6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4169,10 +4198,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d7/de/35d839aa69db737a3f9f3a45000ca24721834d40118652a5775d5eca8ebb/fonttools-4.59.2-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/bb/78/0e1a6d22b427579ea5c8273e1c07def2f325b977faaf60bb7ddc01456cb1/fonttools-4.60.1-cp311-cp311-win_amd64.whl name: fonttools - version: 4.59.2 - sha256: 9836394e2f4ce5f9c0a7690ee93bd90aa1adc6b054f1a57b562c5d242c903104 + version: 4.60.1 + sha256: d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4203,10 +4232,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/ea/a9/be7219fc64a6026cc0aded17fa3720f9277001c185434230bd351bf678e6/fonttools-4.59.2-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/d2/d2/9f4e4c4374dd1daa8367784e1bd910f18ba886db1d6b825b12edf6db3edc/fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: fonttools - version: 4.59.2 - sha256: a72155928d7053bbde499d32a9c77d3f0f3d29ae72b5a121752481bcbd71e50f + version: 4.60.1 + sha256: e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4237,10 +4266,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f2/9f/bf231c2a3fac99d1d7f1d89c76594f158693f981a4aa02be406e9f036832/fonttools-4.59.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl name: fonttools - version: 4.59.2 - sha256: 6235fc06bcbdb40186f483ba9d5d68f888ea68aa3c8dac347e05a7c54346fbc8 + version: 4.60.1 + sha256: eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4271,10 +4300,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f8/53/742fcd750ae0bdc74de4c0ff923111199cc2f90a4ee87aaddad505b6f477/fonttools-4.59.2-cp311-cp311-macosx_10_9_universal2.whl +- pypi: https://files.pythonhosted.org/packages/ea/85/639aa9bface1537e0fb0f643690672dde0695a5bbbc90736bc571b0b1941/fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl name: fonttools - version: 4.59.2 - sha256: 511946e8d7ea5c0d6c7a53c4cb3ee48eda9ab9797cd9bf5d95829a398400354f + version: 4.60.1 + sha256: 7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4305,57 +4334,52 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - sha256: 2509992ec2fd38ab27c7cdb42cf6cadc566a1cc0d1021a2673475d9fa87c6276 - md5: d3549fd50d450b6d9e7dddff25dd2110 - depends: - - cached-property >=1.3.0 - - python >=3.9,<4 - license: MPL-2.0 - license_family: MOZILLA - purls: - - pkg:pypi/fqdn?source=hash-mapping - size: 16705 - timestamp: 1733327494780 -- pypi: https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + name: fqdn + version: 1.5.1 + sha256: 3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014 + requires_dist: + - cached-property>=1.3.0 ; python_full_version < '3.8' + requires_python: '>=2.7,!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,<4' +- pypi: https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl name: frozenlist - version: 1.7.0 - sha256: 52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba + version: 1.8.0 + sha256: ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/47/be/4038e2d869f8a2da165f35a6befb9158c259819be22eeaf9c9a8f6a87771/frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl name: frozenlist - version: 1.7.0 - sha256: 34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd + version: 1.8.0 + sha256: f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/4d/83/220a374bd7b2aeba9d0725130665afe11de347d95c3620b9b82cc2fcab97/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.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 name: frozenlist - version: 1.7.0 - sha256: 61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae + version: 1.8.0 + sha256: 2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/58/17/fe61124c5c333ae87f09bb67186d65038834a47d974fc10a5fadb4cc5ae1/frozenlist-1.7.0-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl name: frozenlist - version: 1.7.0 - sha256: 387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d + version: 1.8.0 + sha256: 96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl name: frozenlist - version: 1.7.0 - sha256: 8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00 + version: 1.8.0 + sha256: fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/75/a9/9c2c5760b6ba45eae11334db454c189d43d34a4c0b489feb2175e5e64277/frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.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 name: frozenlist - version: 1.7.0 - sha256: 9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750 + version: 1.8.0 + sha256: fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl name: frozenlist - version: 1.7.0 - sha256: d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d + version: 1.8.0 + sha256: 878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl name: frozenlist - version: 1.7.0 - sha256: cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43 + version: 1.8.0 + sha256: 17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9 requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/05/78/64628f519ff553a0d8101dd3852b87441caa69c6617250d48b3c6bad9422/gemmi-0.7.3-cp311-cp311-macosx_11_0_arm64.whl name: gemmi @@ -4397,6 +4421,16 @@ packages: version: 0.7.3 sha256: c0d39acb44c552449a07f1056c7fd370e3781e2b9b0bf55b065df2079935d6ec requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + name: ghp-import + version: 2.1.0 + sha256: 8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 + requires_dist: + - python-dateutil>=2.8.1 + - twine ; extra == 'dev' + - markdown ; extra == 'dev' + - flake8 ; extra == 'dev' + - wheel ; extra == 'dev' - pypi: https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl name: greenlet version: 3.2.4 @@ -4463,39 +4497,18 @@ packages: - psutil ; extra == 'test' - setuptools ; extra == 'test' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - sha256: f64b68148c478c3bfc8f8d519541de7d2616bf59d44485a5271041d40c061887 - md5: 4b69232755285701bc86a5afe4d9933a - depends: - - python >=3.9 - - typing_extensions - license: MIT - license_family: MIT - purls: - - pkg:pypi/h11?source=hash-mapping - size: 37697 - timestamp: 1745526482242 -- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 - md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 - depends: - - python >=3.10 - - hyperframe >=6.1,<7 - - hpack >=4.1,<5 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/h2?source=compressed-mapping - size: 95967 - timestamp: 1756364871835 -- pypi: https://files.pythonhosted.org/packages/21/d4/d461649cafd5137088fb7f8e78fdc6621bb0c4ff2c090a389f68e8edc136/h5py-3.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: h5py - version: 3.14.0 - sha256: 723a40ee6505bd354bfd26385f2dae7bbfa87655f4e61bab175a49d72ebfc06b +- pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl + name: griffe + version: 1.14.0 + sha256: 0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0 requires_dist: - - numpy>=1.19.3 + - colorama>=0.4 requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + name: h11 + version: 0.16.0 + sha256: 63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/36/5b/a066e459ca48b47cc73a5c668e9924d9619da9e3c500d9fb9c29c03858ec/h5py-3.14.0-cp311-cp311-macosx_11_0_arm64.whl name: h5py version: 3.14.0 @@ -4503,13 +4516,6 @@ packages: requires_dist: - numpy>=1.19.3 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/3f/6d/0084ed0b78d4fd3e7530c32491f2884140d9b06365dac8a08de726421d4a/h5py-3.14.0-cp313-cp313-win_amd64.whl - name: h5py - version: 3.14.0 - sha256: ae18e3de237a7a830adb76aaa68ad438d85fe6e19e0d99944a3ce46b772c69b3 - requires_dist: - - numpy>=1.19.3 - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/4f/31/f570fab1239b0d9441024b92b6ad03bb414ffa69101a985e4c83d37608bd/h5py-3.14.0-cp313-cp313-macosx_11_0_arm64.whl name: h5py version: 3.14.0 @@ -4531,74 +4537,64 @@ packages: requires_dist: - numpy>=1.19.3 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/db/0c/6c3f879a0f8e891625817637fad902da6e764e36919ed091dc77529004ac/h5py-3.14.0-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/23/95/499b4e56452ef8b6c95a271af0dde08dac4ddb70515a75f346d4f400579b/h5py-3.15.1-cp311-cp311-win_amd64.whl name: h5py - version: 3.14.0 - sha256: d2744b520440a996f2dae97f901caa8a953afc055db4673a993f2d87d7f38713 + version: 3.15.1 + sha256: 550e51131376889656feec4aff2170efc054a7fe79eb1da3bb92e1625d1ac878 requires_dist: - - numpy>=1.19.3 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/e7/ec/86f59025306dcc6deee5fda54d980d077075b8d9889aac80f158bd585f1b/h5py-3.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/8b/23/4ab1108e87851ccc69694b03b817d92e142966a6c4abd99e17db77f2c066/h5py-3.15.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: h5py - version: 3.14.0 - sha256: d90e6445ab7c146d7f7981b11895d70bc1dd91278a4f9f9028bc0c95e4a53f13 + version: 3.15.1 + sha256: 5b849ba619a066196169763c33f9f0f02e381156d61c03e000bb0100f9950faf + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: h5py + version: 3.15.1 + sha256: 121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8 + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl + name: h5py + version: 3.15.1 + sha256: dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52 + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + name: httpcore + version: 1.0.9 + sha256: 2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 requires_dist: - - numpy>=1.19.3 - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba - md5: 0a802cb9888dd14eeefc611f05c40b6e - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/hpack?source=hash-mapping - size: 30731 - timestamp: 1737618390337 -- conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - sha256: 04d49cb3c42714ce533a8553986e1642d0549a05dc5cc48e0d43ff5be6679a5b - md5: 4f14640d58e2cc0aa0819d9d8ba125bb - depends: - - python >=3.9 - - h11 >=0.16 - - h2 >=3,<5 - - sniffio 1.* - - anyio >=4.0,<5.0 - certifi - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/httpcore?source=hash-mapping - size: 49483 - timestamp: 1745602916758 -- conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - sha256: cd0f1de3697b252df95f98383e9edb1d00386bfdd03fdf607fa42fe5fcb09950 - md5: d6989ead454181f4f9bc987d3dc4e285 - depends: + - h11>=0.16 + - anyio>=4.0,<5.0 ; extra == 'asyncio' + - h2>=3,<5 ; extra == 'http2' + - socksio==1.* ; extra == 'socks' + - trio>=0.22.0,<1.0 ; extra == 'trio' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + name: httpx + version: 0.28.1 + sha256: d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad + requires_dist: - anyio - certifi - - httpcore 1.* + - httpcore==1.* - idna - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/httpx?source=hash-mapping - size: 63082 - timestamp: 1733663449209 -- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 - md5: 8e6923fc12f1fe8f8c4e5c9f343256ac - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/hyperframe?source=hash-mapping - size: 17397 - timestamp: 1737618427549 + - brotli ; platform_python_implementation == 'CPython' and extra == 'brotli' + - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'brotli' + - click==8.* ; extra == 'cli' + - pygments==2.* ; extra == 'cli' + - rich>=10,<14 ; extra == 'cli' + - h2>=3,<5 ; extra == 'http2' + - socksio==1.* ; extra == 'socks' + - zstandard>=0.18.0 ; extra == 'zstd' + requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda sha256: 71e750d509f5fa3421087ba88ef9a7b9be11c53174af3aa4d06aff4c18b38e8e md5: 8b189310083baabfb622af68fd9d3ae3 @@ -4643,551 +4639,598 @@ packages: purls: [] size: 14544252 timestamp: 1720853966338 -- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - sha256: d7a472c9fd479e2e8dcb83fb8d433fce971ea369d704ece380e876f9c3494e87 - md5: 39a4f67be3286c86d696df570b1201b7 - depends: - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/idna?source=hash-mapping - size: 49765 - timestamp: 1733211921194 -- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - sha256: c18ab120a0613ada4391b15981d86ff777b5690ca461ea7e9e49531e8f374745 - md5: 63ccfdc3a3ce25b027b8767eb722fca8 - depends: - - python >=3.9 - - zipp >=3.20 - - python - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/importlib-metadata?source=hash-mapping - size: 34641 - timestamp: 1747934053147 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh3521513_0.conda - sha256: 3dd6fcdde5e40a3088c9ecd72c29c6e5b1429b99e927f41c8cee944a07062046 - md5: 953007d45edeb098522ac860aade4fcf - depends: - - __win - - comm >=0.1.1 - - debugpy >=1.6.5 - - ipython >=7.23.1 - - jupyter_client >=8.0.0 - - jupyter_core >=4.12,!=5.0.* - - matplotlib-inline >=0.1 - - nest-asyncio >=1.4 - - packaging >=22 - - psutil >=5.7 - - python >=3.9 - - pyzmq >=25 - - tornado >=6.2 - - traitlets >=5.4.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipykernel?source=hash-mapping - size: 121976 - timestamp: 1754353094360 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh82676e8_0.conda - sha256: cfc2c4e31dfedbb3d124d0055f55fda4694538fb790d52cd1b37af5312833e36 - md5: b0cc25825ce9212b8bee37829abad4d6 - depends: - - __linux - - comm >=0.1.1 - - debugpy >=1.6.5 - - ipython >=7.23.1 - - jupyter_client >=8.0.0 - - jupyter_core >=4.12,!=5.0.* - - matplotlib-inline >=0.1 - - nest-asyncio >=1.4 - - packaging >=22 - - psutil >=5.7 - - python >=3.9 - - pyzmq >=25 - - tornado >=6.2 - - traitlets >=5.4.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipykernel?source=hash-mapping - size: 121367 - timestamp: 1754352984703 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - sha256: ec80ed5f68c96dd46ff1b533b28d2094b6f07e2ec8115c8c60803920fdd6eb13 - md5: f208c1a85786e617a91329fa5201168c - depends: - - __osx - - appnope - - comm >=0.1.1 - - debugpy >=1.6.5 - - ipython >=7.23.1 - - jupyter_client >=8.0.0 - - jupyter_core >=4.12,!=5.0.* - - matplotlib-inline >=0.1 - - nest-asyncio >=1.4 - - packaging >=22 - - psutil >=5.7 - - python >=3.9 - - pyzmq >=25 - - tornado >=6.2 - - traitlets >=5.4.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipykernel?source=hash-mapping - size: 121397 - timestamp: 1754353050327 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyh6be1c34_0.conda - sha256: 658c547dafb10cd0989f2cdf72f8ee9fe8f66240307b64555ee43f6908e9d0ad - md5: aec1868dd4cbe028b2c8cb11377895a6 - depends: - - __win +- pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + name: identify + version: 2.6.15 + sha256: 1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757 + requires_dist: + - ukkonen ; extra == 'license' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + name: idna + version: '3.11' + sha256: 771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea + requires_dist: + - ruff>=0.6.2 ; extra == 'all' + - mypy>=1.11.2 ; extra == 'all' + - pytest>=8.3.2 ; extra == 'all' + - flake8>=7.1.1 ; extra == 'all' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + name: iniconfig + version: 2.1.0 + sha256: 9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + name: interrogate + version: 1.7.0 + sha256: b13ff4dd8403369670e2efe684066de9fcb868ad9d7f2b4095d8112142dc9d12 + requires_dist: + - attrs + - click>=7.1 - colorama + - py + - tabulate + - tomli ; python_full_version < '3.11' + - cairosvg ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-autobuild ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest-mock ; extra == 'dev' + - coverage[toml] ; extra == 'dev' + - wheel ; extra == 'dev' + - pre-commit ; extra == 'dev' + - sphinx ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - cairosvg ; extra == 'png' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-mock ; extra == 'tests' + - coverage[toml] ; extra == 'tests' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + name: ipykernel + version: 6.30.1 + sha256: aa6b9fb93dca949069d8b85b6c79b2518e32ac583ae9c7d37c51d119e18b3fb4 + 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.* + - matplotlib-inline>=0.1 + - nest-asyncio>=1.4 + - packaging>=22 + - psutil>=5.7 + - pyzmq>=25 + - tornado>=6.2 + - traitlets>=5.4.0 + - coverage[toml] ; extra == 'cov' + - matplotlib ; extra == 'cov' + - pytest-cov ; extra == 'cov' + - trio ; extra == 'cov' + - intersphinx-registry ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinxcontrib-github-alt ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - trio ; extra == 'docs' + - pyqt5 ; extra == 'pyqt5' + - pyside6 ; extra == 'pyside6' + - flaky ; extra == 'test' + - ipyparallel ; extra == 'test' + - pre-commit ; extra == 'test' + - 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' +- pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl + name: ipython + version: 9.6.0 + sha256: 5f77efafc886d2f023442479b8149e7d86547ad0a979e9da9f045d252f648196 + requires_dist: + - colorama ; sys_platform == 'win32' - decorator - - exceptiongroup - - ipython_pygments_lexers - - jedi >=0.16 - - matplotlib-inline - - pickleshare - - prompt-toolkit >=3.0.41,<3.1.0 - - pygments >=2.4.0 - - python >=3.11 - - stack_data - - traitlets >=5.13.0 - - typing_extensions >=4.6 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipython?source=hash-mapping - size: 630157 - timestamp: 1756474536497 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - sha256: e9ca009d3aab9d8a85f0241d6ada2c7fbc84072008e95f803fa59da3294aa863 - md5: c0916cc4b733577cd41df93884d857b0 - depends: - - __unix - - pexpect >4.3 - - decorator - - exceptiongroup - - ipython_pygments_lexers - - jedi >=0.16 + - ipython-pygments-lexers + - jedi>=0.16 - matplotlib-inline - - pickleshare - - prompt-toolkit >=3.0.41,<3.1.0 - - pygments >=2.4.0 - - python >=3.11 - - stack_data - - traitlets >=5.13.0 - - typing_extensions >=4.6 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipython?source=hash-mapping - size: 630826 - timestamp: 1756474504536 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - sha256: 894682a42a7d659ae12878dbcb274516a7031bbea9104e92f8e88c1f2765a104 - md5: bd80ba060603cc228d9d81c257093119 - depends: + - pexpect>4.3 ; sys_platform != 'emscripten' and sys_platform != 'win32' + - prompt-toolkit>=3.0.41,<3.1.0 + - pygments>=2.4.0 + - stack-data + - traitlets>=5.13.0 + - typing-extensions>=4.6 ; python_full_version < '3.12' + - black ; extra == 'black' + - docrepr ; extra == 'doc' + - exceptiongroup ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - ipykernel ; extra == 'doc' + - ipython[matplotlib,test] ; extra == 'doc' + - setuptools>=61.2 ; extra == 'doc' + - sphinx-toml==0.0.4 ; extra == 'doc' + - sphinx-rtd-theme ; extra == 'doc' + - sphinx>=1.3 ; extra == 'doc' + - typing-extensions ; extra == 'doc' + - pytest ; extra == 'test' + - pytest-asyncio ; extra == 'test' + - testpath ; extra == 'test' + - packaging ; extra == 'test' + - ipython[test] ; extra == 'test-extra' + - curio ; extra == 'test-extra' + - jupyter-ai ; extra == 'test-extra' + - ipython[matplotlib] ; extra == 'test-extra' + - nbformat ; extra == 'test-extra' + - nbclient ; extra == 'test-extra' + - ipykernel ; extra == 'test-extra' + - numpy>=1.25 ; extra == 'test-extra' + - pandas>2.0 ; extra == 'test-extra' + - trio ; extra == 'test-extra' + - matplotlib>3.7 ; extra == 'matplotlib' + - ipython[doc,matplotlib,test,test-extra] ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + name: ipython-pygments-lexers + version: 1.1.1 + sha256: a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c + requires_dist: - pygments - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipython-pygments-lexers?source=hash-mapping - size: 13993 - timestamp: 1737123723464 -- conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - sha256: 08e838d29c134a7684bca0468401d26840f41c92267c4126d7b43a6b533b0aed - md5: 0b0154421989637d424ccf0f104be51a - depends: - - arrow >=0.15.0 - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/isoduration?source=hash-mapping - size: 19832 - timestamp: 1733493720346 -- conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - sha256: 92c4d217e2dc68983f724aa983cca5464dcb929c566627b26a2511159667dba8 - md5: a4f4c5dc9b80bc50e0d3dc4e6e8f1bd9 - depends: - - parso >=0.8.3,<0.9.0 - - python >=3.9 - license: Apache-2.0 AND MIT - purls: - - pkg:pypi/jedi?source=hash-mapping - size: 843646 - timestamp: 1733300981994 -- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - sha256: f1ac18b11637ddadc05642e8185a851c7fab5998c6f5470d716812fae943b2af - md5: 446bd6c8cb26050d528881df495ce646 - depends: - - markupsafe >=2.0 - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jinja2?source=hash-mapping - size: 112714 - timestamp: 1741263433881 -- conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - sha256: 4e08ccf9fa1103b617a4167a270768de736a36be795c6cd34c2761100d332f74 - md5: 0fc93f473c31a2f85c0bde213e7c63ca - depends: - - python >=3.9 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/json5?source=hash-mapping - size: 34191 - timestamp: 1755034963991 -- conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py311h38be061_2.conda - sha256: 4e744b30e3002b519c48868b3f5671328274d1d78cc8cbc0cda43057b570c508 - md5: 5dd29601defbcc14ac6953d9504a80a7 - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 18368 - timestamp: 1756754243123 -- conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py313h78bf25f_2.conda - sha256: 9174f5209f835cc8918acddc279be919674d874179197e025181fe2a71cb0bce - md5: c1375f38e5f3ee38a9ee0e405a601c35 - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 18143 - timestamp: 1756754243113 -- conda: https://conda.anaconda.org/conda-forge/osx-64/jsonpointer-3.0.0-py311h6eed73b_2.conda - sha256: 8e0f5af4d5bd59f52d27926750416638e32a65d58a202593fa0e97f312ad78c3 - md5: 9983b3959da3b695c8da48c93510cbdf - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 18407 - timestamp: 1756754411612 -- conda: https://conda.anaconda.org/conda-forge/osx-64/jsonpointer-3.0.0-py313habf4b1d_2.conda - sha256: 444b99db8da2f87910967012bca4767dbc27024604cb11674baf5b33ad05e216 - md5: 6c9ecc26d54c3b9f9c40a7a21f780243 - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 18208 - timestamp: 1756754393540 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py311h267d04e_2.conda - sha256: 92b998fa9e68b7793b5b15882932f7703cdc1cc6e1348d0a1799567ef6f04428 - md5: 0edc5f25c32d86d5b4327e0f4de539f9 - depends: - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 18716 - timestamp: 1756754690458 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py313h8f79df9_2.conda - sha256: 6e6097b156b2c69bcf05842f86a6650eb474477bec5e2233b4b8bcd2936cda5a - md5: 51a61cf0de7b177b4c09fa5eb4775c76 - depends: - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 18604 - timestamp: 1756754452012 -- conda: https://conda.anaconda.org/conda-forge/win-64/jsonpointer-3.0.0-py311h1ea47a8_2.conda - sha256: 64bcf78dbbda7ec523672c4b3f085527fd109732518e33907eac6b8049125113 - md5: c8f80d7bee5c66371969936eba774c45 - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 43432 - timestamp: 1756754355044 -- conda: https://conda.anaconda.org/conda-forge/win-64/jsonpointer-3.0.0-py313hfa70ccb_2.conda - sha256: dda25a66128a7b883515a659cd53c694e735374ccfbfa87a998160a33679424a - md5: 8da802c2a92986f7054f97c45e0f4bee - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 43276 - timestamp: 1756754377785 -- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - sha256: ac377ef7762e49cb9c4f985f1281eeff471e9adc3402526eea78e6ac6589cf1d - md5: 341fd940c242cf33e832c0402face56f - depends: - - attrs >=22.2.0 - - jsonschema-specifications >=2023.3.6 - - python >=3.9 - - referencing >=0.28.4 - - rpds-py >=0.7.1 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/jsonschema?source=compressed-mapping - size: 81688 - timestamp: 1755595646123 -- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - sha256: 0a4f3b132f0faca10c89fdf3b60e15abb62ded6fa80aebfc007d05965192aa04 - md5: 439cd0f567d697b20a8f45cb70a1005a - depends: - - python >=3.10 - - referencing >=0.31.0 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/jsonschema-specifications?source=compressed-mapping - size: 19236 - timestamp: 1757335715225 -- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - sha256: aef6705fe1335e6472e1b6365fcdb586356b18dceff72d8d6a315fc90e900ccf - md5: 13e31c573c884962318a738405ca3487 - depends: - - jsonschema >=4.25.1,<4.25.2.0a0 - - fqdn - - idna - - isoduration - - jsonpointer >1.13 - - rfc3339-validator - - rfc3986-validator >0.1.0 - - rfc3987-syntax >=1.1.0 - - uri-template - - webcolors >=24.6.0 - license: MIT - license_family: MIT - purls: [] - size: 4744 - timestamp: 1755595646123 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - sha256: 897ad2e2c2335ef3c2826d7805e16002a1fd0d509b4ae0bc66617f0e0ff07bc2 - md5: 62b7c96c6cd77f8173cc5cada6a9acaa - depends: - - importlib-metadata >=4.8.3 - - jupyter_server >=1.1.2 - - python >=3.10 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyter-lsp?source=hash-mapping - size: 60377 - timestamp: 1756388269267 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - sha256: 19d8bd5bb2fde910ec59e081eeb59529491995ce0d653a5209366611023a0b3a - md5: 4ebae00eae9705b0c3d6d1018a81d047 - depends: - - importlib-metadata >=4.8.3 - - jupyter_core >=4.12,!=5.0.* - - python >=3.9 - - python-dateutil >=2.8.2 - - pyzmq >=23.0 - - tornado >=6.2 - - traitlets >=5.3 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyter-client?source=hash-mapping - size: 106342 - timestamp: 1733441040958 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - sha256: 56a7a7e907f15cca8c4f9b0c99488276d4cb10821d2d15df9245662184872e81 - md5: b7d89d860ebcda28a5303526cdee68ab - depends: - - __unix - - platformdirs >=2.5 - - python >=3.8 - - traitlets >=5.3 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyter-core?source=hash-mapping - size: 59562 - timestamp: 1748333186063 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh5737063_0.conda - sha256: 928c2514c2974fda78447903217f01ca89a77eefedd46bf6a2fe97072df57e8d - md5: 324e60a0d3f39f268e899709575ea3cd - depends: - - __win - - cpython - - platformdirs >=2.5 - - python >=3.8 - - pywin32 >=300 - - traitlets >=5.3 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyter-core?source=hash-mapping - size: 59972 - timestamp: 1748333368923 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - sha256: 37e6ac3ccf7afcc730c3b93cb91a13b9ae827fd306f35dd28f958a74a14878b5 - md5: f56000b36f09ab7533877e695e4e8cb0 - depends: - - jsonschema-with-format-nongpl >=4.18.0 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + name: isoduration + version: 20.11.0 + sha256: b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042 + requires_dist: + - arrow>=0.15.0 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + name: jedi + version: 0.19.2 + sha256: a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9 + requires_dist: + - parso>=0.8.4,<0.9.0 + - jinja2==2.11.3 ; extra == 'docs' + - markupsafe==1.1.1 ; extra == 'docs' + - pygments==2.8.1 ; extra == 'docs' + - alabaster==0.7.12 ; extra == 'docs' + - babel==2.9.1 ; extra == 'docs' + - chardet==4.0.0 ; extra == 'docs' + - commonmark==0.8.1 ; extra == 'docs' + - docutils==0.17.1 ; extra == 'docs' + - future==0.18.2 ; extra == 'docs' + - idna==2.10 ; extra == 'docs' + - imagesize==1.2.0 ; extra == 'docs' + - mock==1.0.1 ; extra == 'docs' + - packaging==20.9 ; extra == 'docs' + - pyparsing==2.4.7 ; extra == 'docs' + - pytz==2021.1 ; extra == 'docs' + - readthedocs-sphinx-ext==2.1.4 ; extra == 'docs' + - recommonmark==0.5.0 ; extra == 'docs' + - requests==2.25.1 ; extra == 'docs' + - six==1.15.0 ; extra == 'docs' + - snowballstemmer==2.1.0 ; extra == 'docs' + - sphinx-rtd-theme==0.4.3 ; extra == 'docs' + - sphinx==1.8.5 ; extra == 'docs' + - sphinxcontrib-serializinghtml==1.1.4 ; extra == 'docs' + - sphinxcontrib-websupport==1.2.4 ; extra == 'docs' + - urllib3==1.26.4 ; extra == 'docs' + - flake8==5.0.4 ; extra == 'qa' + - mypy==0.971 ; extra == 'qa' + - types-setuptools==67.2.0.1 ; extra == 'qa' + - django ; extra == 'testing' + - attrs ; extra == 'testing' + - colorama ; extra == 'testing' + - docopt ; extra == 'testing' + - pytest<9.0.0 ; extra == 'testing' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + name: jinja2 + version: 3.1.6 + sha256: 85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 + requires_dist: + - markupsafe>=2.0 + - babel>=2.7 ; extra == 'i18n' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + name: json5 + version: 0.12.1 + sha256: d9c9b3bc34a5f54d43c35e11ef7cb87d8bdd098c6ace87117a7b7e83e705c1d5 + requires_dist: + - build==1.2.2.post1 ; extra == 'dev' + - coverage==7.5.4 ; python_full_version < '3.9' and extra == 'dev' + - coverage==7.8.0 ; python_full_version >= '3.9' and extra == 'dev' + - mypy==1.14.1 ; python_full_version < '3.9' and extra == 'dev' + - mypy==1.15.0 ; python_full_version >= '3.9' and extra == 'dev' + - pip==25.0.1 ; extra == 'dev' + - pylint==3.2.7 ; python_full_version < '3.9' and extra == 'dev' + - pylint==3.3.6 ; python_full_version >= '3.9' and extra == 'dev' + - ruff==0.11.2 ; extra == 'dev' + - twine==6.1.0 ; extra == 'dev' + - uv==0.6.11 ; extra == 'dev' + requires_python: '>=3.8.0' +- pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + name: jsonpointer + version: 3.0.0 + sha256: 13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + name: jsonschema + version: 4.25.1 + sha256: 3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63 + requires_dist: + - attrs>=22.2.0 + - jsonschema-specifications>=2023.3.6 + - referencing>=0.28.4 + - rpds-py>=0.7.1 + - fqdn ; extra == 'format' + - idna ; extra == 'format' + - isoduration ; extra == 'format' + - jsonpointer>1.13 ; extra == 'format' + - rfc3339-validator ; extra == 'format' + - rfc3987 ; extra == 'format' + - uri-template ; extra == 'format' + - webcolors>=1.11 ; extra == 'format' + - fqdn ; extra == 'format-nongpl' + - idna ; extra == 'format-nongpl' + - isoduration ; extra == 'format-nongpl' + - jsonpointer>1.13 ; extra == 'format-nongpl' + - rfc3339-validator ; extra == 'format-nongpl' + - rfc3986-validator>0.1.0 ; extra == 'format-nongpl' + - rfc3987-syntax>=1.1.0 ; extra == 'format-nongpl' + - uri-template ; extra == 'format-nongpl' + - webcolors>=24.6.0 ; extra == 'format-nongpl' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + name: jsonschema-specifications + version: 2025.9.1 + sha256: 98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe + requires_dist: + - referencing>=0.31.0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + name: jupyter-client + version: 8.6.3 + sha256: e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f + requires_dist: + - importlib-metadata>=4.8.3 ; python_full_version < '3.10' + - jupyter-core>=4.12,!=5.0.* + - python-dateutil>=2.8.2 + - pyzmq>=23.0 + - tornado>=6.2 + - traitlets>=5.3 + - ipykernel ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx>=4 ; extra == 'docs' + - sphinxcontrib-github-alt ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - coverage ; extra == 'test' + - ipykernel>=6.14 ; extra == 'test' + - mypy ; extra == 'test' + - paramiko ; sys_platform == 'win32' and extra == 'test' + - pre-commit ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-jupyter[client]>=0.4.1 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest<8.2.0 ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + name: jupyter-core + version: 5.8.1 + sha256: c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0 + requires_dist: + - platformdirs>=2.5 + - pywin32>=300 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' + - traitlets>=5.3 + - intersphinx-registry ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - traitlets ; extra == 'docs' + - ipykernel ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest<9 ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + name: jupyter-events + version: 0.12.0 + sha256: 6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb + requires_dist: + - jsonschema[format-nongpl]>=4.18.0 - packaging - - python >=3.9 - - python-json-logger >=2.0.4 - - pyyaml >=5.3 + - python-json-logger>=2.0.4 + - pyyaml>=5.3 - referencing - rfc3339-validator - - rfc3986-validator >=0.1.1 - - traitlets >=5.3 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyter-events?source=hash-mapping - size: 23647 - timestamp: 1738765986736 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - sha256: 74c4e642be97c538dae1895f7052599dfd740d8bd251f727bce6453ce8d6cd9a - md5: d79a87dcfa726bcea8e61275feed6f83 - depends: - - anyio >=3.1.0 - - argon2-cffi >=21.1 - - jinja2 >=3.0.3 - - jupyter_client >=7.4.4 - - jupyter_core >=4.12,!=5.0.* - - jupyter_events >=0.11.0 - - jupyter_server_terminals >=0.4.4 - - nbconvert-core >=6.4.4 - - nbformat >=5.3.0 - - overrides >=5.0 - - packaging >=22.0 - - prometheus_client >=0.9 - - python >=3.10 - - pyzmq >=24 - - send2trash >=1.8.2 - - terminado >=0.8.3 - - tornado >=6.2.0 - - traitlets >=5.6.0 - - websocket-client >=1.7 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyter-server?source=hash-mapping - size: 347094 - timestamp: 1755870522134 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - sha256: 0890fc79422191bc29edf17d7b42cff44ba254aa225d31eb30819f8772b775b8 - md5: 2d983ff1b82a1ccb6f2e9d8784bdd6bd - depends: - - python >=3.9 - - terminado >=0.8.3 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyter-server-terminals?source=hash-mapping - size: 19711 - timestamp: 1733428049134 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - sha256: 042bdb981ad5394530bee8329a10c76b9e17c12651d15a885d68e2cbbfef6869 - md5: 460d51bb21b7a4c4b6e100c824405fbb - depends: - - async-lru >=1.0.0 - - httpx >=0.25.0,<1 - - importlib-metadata >=4.8.3 - - ipykernel >=6.5.0,!=6.30.0 - - jinja2 >=3.0.3 - - jupyter-lsp >=2.0.0 - - jupyter_core - - jupyter_server >=2.4.0,<3 - - jupyterlab_server >=2.27.1,<3 - - notebook-shim >=0.2 + - rfc3986-validator>=0.1.1 + - traitlets>=5.3 + - click ; extra == 'cli' + - rich ; extra == 'cli' + - jupyterlite-sphinx ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme>=0.16 ; extra == 'docs' + - sphinx>=8 ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - click ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest-asyncio>=0.19.0 ; extra == 'test' + - pytest-console-scripts ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - rich ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + name: jupyter-lsp + version: 2.3.0 + sha256: e914a3cb2addf48b1c7710914771aaf1819d46b2e5a79b0f917b5478ec93f34f + requires_dist: + - 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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + name: jupyter-server + version: 2.17.0 + sha256: e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f + requires_dist: + - anyio>=3.1.0 + - argon2-cffi>=21.1 + - jinja2>=3.0.3 + - jupyter-client>=7.4.4 + - jupyter-core>=4.12,!=5.0.* + - jupyter-events>=0.11.0 + - jupyter-server-terminals>=0.4.4 + - nbconvert>=6.4.4 + - nbformat>=5.3.0 + - overrides>=5.0 ; python_full_version < '3.12' + - packaging>=22.0 + - prometheus-client>=0.9 + - pywinpty>=2.0.1 ; os_name == 'nt' + - pyzmq>=24 + - send2trash>=1.8.2 + - terminado>=0.8.3 + - tornado>=6.2.0 + - traitlets>=5.6.0 + - websocket-client>=1.7 + - ipykernel ; extra == 'docs' + - jinja2 ; extra == 'docs' + - jupyter-client ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbformat ; extra == 'docs' + - prometheus-client ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - send2trash ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinxcontrib-github-alt ; extra == 'docs' + - sphinxcontrib-openapi>=0.8.0 ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - sphinxemoji ; extra == 'docs' + - tornado ; extra == 'docs' + - typing-extensions ; extra == 'docs' + - flaky ; extra == 'test' + - ipykernel ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest-console-scripts ; extra == 'test' + - pytest-jupyter[server]>=0.7 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest>=7.0,<9 ; extra == 'test' + - requests ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + name: jupyter-server-terminals + version: 0.5.3 + sha256: 41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa + requires_dist: + - pywinpty>=2.0.3 ; os_name == 'nt' + - terminado>=0.8.3 + - jinja2 ; extra == 'docs' + - jupyter-server ; extra == 'docs' + - mistune<4.0 ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbformat ; extra == 'docs' + - packaging ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinxcontrib-github-alt ; extra == 'docs' + - sphinxcontrib-openapi ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - sphinxemoji ; extra == 'docs' + - tornado ; extra == 'docs' + - jupyter-server>=2.0.0 ; extra == 'test' + - pytest-jupyter[server]>=0.5.3 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + name: jupyterlab + version: 4.4.9 + sha256: 394c902827350c017430a8370b9f40c03c098773084bc53930145c146d3d2cb2 + requires_dist: + - async-lru>=1.0.0 + - httpx>=0.25.0,<1 + - importlib-metadata>=4.8.3 ; python_full_version < '3.10' + - ipykernel>=6.5.0,!=6.30.0 + - jinja2>=3.0.3 + - jupyter-core + - jupyter-lsp>=2.0.0 + - jupyter-server>=2.4.0,<3 + - jupyterlab-server>=2.27.1,<3 + - notebook-shim>=0.2 - packaging - - python >=3.10 - - setuptools >=41.1.0 - - tomli >=1.2.2 - - tornado >=6.2.0 + - setuptools>=41.1.0 + - tomli>=1.2.2 ; python_full_version < '3.11' + - tornado>=6.2.0 - traitlets - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyterlab?source=compressed-mapping - size: 8479512 - timestamp: 1756911706349 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - sha256: dc24b900742fdaf1e077d9a3458fd865711de80bca95fe3c6d46610c532c6ef0 - md5: fd312693df06da3578383232528c468d - depends: - - pygments >=2.4.1,<3 - - python >=3.9 - constrains: - - jupyterlab >=4.0.8,<5.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyterlab-pygments?source=hash-mapping - size: 18711 - timestamp: 1733328194037 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - sha256: d03d0b7e23fa56d322993bc9786b3a43b88ccc26e58b77c756619a921ab30e86 - md5: 9dc4b2b0f41f0de41d27f3293e319357 - depends: - - babel >=2.10 - - importlib-metadata >=4.8.3 - - jinja2 >=3.0.3 - - json5 >=0.9.0 - - jsonschema >=4.18 - - jupyter_server >=1.21,<3 - - packaging >=21.3 - - python >=3.9 - - requests >=2.31 - constrains: - - openapi-core >=0.18.0,<0.19.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyterlab-server?source=hash-mapping - size: 49449 - timestamp: 1733599666357 -- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - sha256: 0960d06048a7185d3542d850986d807c6e37ca2e644342dd0c72feefcf26c2a4 - md5: b38117a3c920364aff79f870c984b4a3 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - license: LGPL-2.1-or-later - purls: [] - size: 134088 - timestamp: 1754905959823 + - build ; extra == 'dev' + - bump2version ; extra == 'dev' + - coverage ; extra == 'dev' + - hatch ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - ruff==0.11.4 ; extra == 'dev' + - jsx-lexer ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme>=0.13.0 ; extra == 'docs' + - pytest ; extra == 'docs' + - pytest-check-links ; extra == 'docs' + - pytest-jupyter ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx>=1.8,<8.2.0 ; extra == 'docs' + - altair==5.5.0 ; extra == 'docs-screenshots' + - ipython==8.16.1 ; extra == 'docs-screenshots' + - ipywidgets==8.1.5 ; extra == 'docs-screenshots' + - jupyterlab-geojson==3.4.0 ; extra == 'docs-screenshots' + - jupyterlab-language-pack-zh-cn==4.3.post1 ; extra == 'docs-screenshots' + - matplotlib==3.10.0 ; extra == 'docs-screenshots' + - nbconvert>=7.0.0 ; extra == 'docs-screenshots' + - pandas==2.2.3 ; extra == 'docs-screenshots' + - scipy==1.15.1 ; extra == 'docs-screenshots' + - vega-datasets==0.9.0 ; extra == 'docs-screenshots' + - coverage ; extra == 'test' + - pytest-check-links>=0.7 ; extra == 'test' + - pytest-console-scripts ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-jupyter>=0.5.3 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-tornasync ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - requests ; extra == 'test' + - requests-cache ; extra == 'test' + - virtualenv ; extra == 'test' + - copier>=9,<10 ; extra == 'upgrade-extension' + - jinja2-time<0.3 ; extra == 'upgrade-extension' + - pydantic<3.0 ; extra == 'upgrade-extension' + - pyyaml-include<3.0 ; extra == 'upgrade-extension' + - tomli-w<2.0 ; extra == 'upgrade-extension' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + name: jupyterlab-pygments + version: 0.3.0 + sha256: 841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + name: jupyterlab-server + version: 2.27.3 + sha256: e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4 + requires_dist: + - babel>=2.10 + - importlib-metadata>=4.8.3 ; python_full_version < '3.10' + - jinja2>=3.0.3 + - json5>=0.9.0 + - jsonschema>=4.18.0 + - jupyter-server>=1.21,<3 + - packaging>=21.3 + - requests>=2.31 + - autodoc-traits ; extra == 'docs' + - jinja2<3.2.0 ; extra == 'docs' + - mistune<4 ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinxcontrib-openapi>0.8 ; extra == 'docs' + - openapi-core~=0.18.0 ; extra == 'openapi' + - ruamel-yaml ; extra == 'openapi' + - hatch ; extra == 'test' + - ipykernel ; extra == 'test' + - openapi-core~=0.18.0 ; extra == 'test' + - openapi-spec-validator>=0.6.0,<0.8.0 ; extra == 'test' + - pytest-console-scripts ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-jupyter[server]>=0.6.2 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest>=7.0,<8 ; extra == 'test' + - requests-mock ; extra == 'test' + - ruamel-yaml ; extra == 'test' + - sphinxcontrib-spelling ; extra == 'test' + - strict-rfc3339 ; extra == 'test' + - werkzeug ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + name: jupyterquiz + version: 2.9.6.2 + sha256: f60f358c809d06a38c423c804740c1113c8840e130227aef50694a9201474bef +- pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl + name: jupytext + version: 1.17.3 + sha256: 09b0a94cd904416e823a5ba9f41bd181031215b6fc682d2b5c18e68354feb17c + requires_dist: + - markdown-it-py>=1.0 + - mdit-py-plugins + - nbformat + - packaging + - pyyaml + - tomli ; python_full_version < '3.11' + - autopep8 ; extra == 'dev' + - black ; extra == 'dev' + - flake8 ; extra == 'dev' + - gitpython ; extra == 'dev' + - ipykernel ; extra == 'dev' + - isort ; extra == 'dev' + - jupyter-fs[fs]>=1.0 ; extra == 'dev' + - jupyter-server!=2.11 ; extra == 'dev' + - nbconvert ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-asyncio ; extra == 'dev' + - pytest-cov>=2.6.1 ; extra == 'dev' + - pytest-randomly ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-gallery>=0.8 ; extra == 'dev' + - myst-parser ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - pytest ; extra == 'test' + - pytest-asyncio ; extra == 'test' + - pytest-randomly ; extra == 'test' + - pytest-xdist ; extra == 'test' + - black ; extra == 'test-cov' + - ipykernel ; extra == 'test-cov' + - jupyter-server!=2.11 ; extra == 'test-cov' + - nbconvert ; extra == 'test-cov' + - pytest ; extra == 'test-cov' + - pytest-asyncio ; extra == 'test-cov' + - pytest-cov>=2.6.1 ; extra == 'test-cov' + - pytest-randomly ; extra == 'test-cov' + - pytest-xdist ; extra == 'test-cov' + - autopep8 ; extra == 'test-external' + - black ; extra == 'test-external' + - flake8 ; extra == 'test-external' + - gitpython ; extra == 'test-external' + - ipykernel ; extra == 'test-external' + - isort ; extra == 'test-external' + - jupyter-fs[fs]>=1.0 ; extra == 'test-external' + - jupyter-server!=2.11 ; extra == 'test-external' + - nbconvert ; extra == 'test-external' + - pre-commit ; extra == 'test-external' + - pytest ; extra == 'test-external' + - pytest-asyncio ; extra == 'test-external' + - pytest-randomly ; extra == 'test-external' + - pytest-xdist ; extra == 'test-external' + - sphinx ; extra == 'test-external' + - sphinx-gallery>=0.8 ; extra == 'test-external' + - black ; extra == 'test-functional' + - pytest ; extra == 'test-functional' + - pytest-asyncio ; extra == 'test-functional' + - pytest-randomly ; extra == 'test-functional' + - pytest-xdist ; extra == 'test-functional' + - black ; extra == 'test-integration' + - ipykernel ; extra == 'test-integration' + - jupyter-server!=2.11 ; extra == 'test-integration' + - nbconvert ; extra == 'test-integration' + - pytest ; extra == 'test-integration' + - pytest-asyncio ; extra == 'test-integration' + - pytest-randomly ; extra == 'test-integration' + - pytest-xdist ; extra == 'test-integration' + - bash-kernel ; extra == 'test-ui' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl name: kiwisolver version: 1.4.9 @@ -5228,76 +5271,19 @@ packages: version: 1.4.9 sha256: b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098 requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - sha256: 99df692f7a8a5c27cd14b5fb1374ee55e756631b9c3d659ed3ee60830249b238 - md5: 3f43953b7d3fb3aaa1d0d0723d91e368 - depends: - - keyutils >=1.6.1,<2.0a0 - - libedit >=3.1.20191231,<3.2.0a0 - - libedit >=3.1.20191231,<4.0a0 - - libgcc-ng >=12 - - libstdcxx-ng >=12 - - openssl >=3.3.1,<4.0a0 - license: MIT - license_family: MIT - purls: [] - size: 1370023 - timestamp: 1719463201255 -- conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - sha256: 83b52685a4ce542772f0892a0f05764ac69d57187975579a0835ff255ae3ef9c - md5: d4765c524b1d91567886bde656fb514b - depends: - - __osx >=10.13 - - libcxx >=16 - - libedit >=3.1.20191231,<3.2.0a0 - - libedit >=3.1.20191231,<4.0a0 - - openssl >=3.3.1,<4.0a0 - license: MIT - license_family: MIT - purls: [] - size: 1185323 - timestamp: 1719463492984 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - sha256: 4442f957c3c77d69d9da3521268cad5d54c9033f1a73f99cde0a3658937b159b - md5: c6dc8a0fdec13a0565936655c33069a1 - depends: - - __osx >=11.0 - - libcxx >=16 - - libedit >=3.1.20191231,<3.2.0a0 - - libedit >=3.1.20191231,<4.0a0 - - openssl >=3.3.1,<4.0a0 - license: MIT - license_family: MIT - purls: [] - size: 1155530 - timestamp: 1719463474401 -- conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.21.3-hdf4eb48_0.conda - sha256: 18e8b3430d7d232dad132f574268f56b3eb1a19431d6d5de8c53c29e6c18fa81 - md5: 31aec030344e962fbd7dbbbbd68e60a9 - depends: - - openssl >=3.3.1,<4.0a0 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - license: MIT - license_family: MIT - purls: [] - size: 712034 - timestamp: 1719463874284 -- conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - sha256: 637a9c32e15a4333f1f9c91e0a506dbab4a6dab7ee83e126951159c916c81c99 - md5: 3a8063b25e603999188ed4bbf3485404 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/lark?source=hash-mapping - size: 92093 - timestamp: 1734709450256 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1423503_1.conda - sha256: 1a620f27d79217c1295049ba214c2f80372062fd251b569e9873d4a953d27554 - md5: 0be7c6e070c19105f966d3758448d018 +- pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl + name: lark + version: 1.3.0 + sha256: 80661f261fb2584a9828a097a2432efd575af27d20be0fd35d17f0fe37253831 + requires_dist: + - regex ; extra == 'regex' + - js2py ; extra == 'nearley' + - atomicwrites ; extra == 'atomic-cache' + - interegular>=0.3.1,<0.4.0 ; extra == 'interegular' + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda + sha256: 707dfb8d55d7a5c6f95c772d778ef07a7ca85417d9971796f7d3daad0b615de8 + md5: 14bae321b8127b63cba276bd53fac237 depends: - __glibc >=2.17,<3.0.a0 constrains: @@ -5305,8 +5291,8 @@ packages: license: GPL-3.0-only license_family: GPL purls: [] - size: 676044 - timestamp: 1752032747103 + size: 747158 + timestamp: 1758810907507 - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-35_h5709861_mkl.conda build_number: 35 sha256: 4180e7ab27ed03ddf01d7e599002fcba1b32dcb68214ee25da823bac371ed362 @@ -5338,63 +5324,26 @@ packages: purls: [] size: 66398 timestamp: 1757003514529 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.1-h3d58e20_0.conda - sha256: dd207d8882854f22072b7fd4f03726e0e182e0666986ec880168f1753f7415dc - md5: 7f5b7dfca71a5c165ce57f46e9e48480 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda + sha256: 9bba2ce10e1c390a4091ca48fab0c71c010f6526c27ac2da53399940ad4c113f + md5: 432d125a340932454d777b66b09c32a1 depends: - - __osx >=10.13 - license: Apache-2.0 WITH LLVM-exception - license_family: Apache - purls: [] - size: 571163 - timestamp: 1757525814844 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.1-hf598326_0.conda - sha256: 6af03355967b7b097d5820dde05e0c709945fdb01f4bc56d11499d8bf7435239 - md5: d5790f3769fedeea4e021483272bdc53 - depends: - - __osx >=11.0 - license: Apache-2.0 WITH LLVM-exception - license_family: Apache - purls: [] - size: 568291 - timestamp: 1757525671408 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - sha256: d789471216e7aba3c184cd054ed61ce3f6dac6f87a50ec69291b9297f8c18724 - md5: c277e0a4d549b03ac1e9d6cbbe3d017b - depends: - - ncurses - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - ncurses >=6.5,<7.0a0 - license: BSD-2-Clause - license_family: BSD - purls: [] - size: 134676 - timestamp: 1738479519902 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda - sha256: 6cc49785940a99e6a6b8c6edbb15f44c2dd6c789d9c283e5ee7bdfedd50b4cd6 - md5: 1f4ed31220402fcddc083b4bff406868 - depends: - - ncurses - - __osx >=10.13 - - ncurses >=6.5,<7.0a0 - license: BSD-2-Clause - license_family: BSD + - __osx >=10.13 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache purls: [] - size: 115563 - timestamp: 1738479554273 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - sha256: 66aa216a403de0bb0c1340a88d1a06adaff66bae2cfd196731aa24db9859d631 - md5: 44083d2d2c2025afca315c7a172eab2b + size: 571632 + timestamp: 1760166417842 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.3-hf598326_0.conda + sha256: b9bad452e3e1d0cc597d907681461341209cb7576178d5c1933026a650b381d1 + md5: e976227574dfcd0048324576adf8d60d depends: - - ncurses - __osx >=11.0 - - ncurses >=6.5,<7.0a0 - license: BSD-2-Clause - license_family: BSD + license: Apache-2.0 WITH LLVM-exception + license_family: Apache purls: [] - size: 107691 - timestamp: 1738479560845 + size: 568715 + timestamp: 1760166479630 - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda sha256: da2080da8f0288b95dd86765c801c6e166c4619b910b11f9a8446fb852438dc2 md5: 4211416ecba1866fab0c6470986c22d6 @@ -5489,40 +5438,40 @@ packages: purls: [] size: 44978 timestamp: 1743435053850 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_5.conda - sha256: 0caed73aac3966bfbf5710e06c728a24c6c138605121a3dacb2e03440e8baa6a - md5: 264fbfba7fb20acf3b29cde153e345ce +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda + sha256: 08f9b87578ab981c7713e4e6a7d935e40766e10691732bba376d4964562bcb45 + md5: c0374badb3a5d4b1372db28d19462c53 depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 constrains: - - libgomp 15.1.0 h767d61c_5 - - libgcc-ng ==15.1.0=*_5 + - libgomp 15.2.0 h767d61c_7 + - libgcc-ng ==15.2.0=*_7 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 824191 - timestamp: 1757042543820 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_5.conda - sha256: f54bb9c3be12b24be327f4c1afccc2969712e0b091cdfbd1d763fb3e61cda03f - md5: 069afdf8ea72504e48d23ae1171d951c + size: 822552 + timestamp: 1759968052178 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_7.conda + sha256: 2045066dd8e6e58aaf5ae2b722fb6dfdbb57c862b5f34ac7bfb58c40ef39b6ad + md5: 280ea6eee9e2ddefde25ff799c4f0363 depends: - - libgcc 15.1.0 h767d61c_5 + - libgcc 15.2.0 h767d61c_7 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 29187 - timestamp: 1757042549554 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_5.conda - sha256: 125051d51a8c04694d0830f6343af78b556dd88cc249dfec5a97703ebfb1832d - md5: dcd5ff1940cd38f6df777cac86819d60 + size: 29313 + timestamp: 1759968065504 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda + sha256: e9fb1c258c8e66ee278397b5822692527c5f5786d372fe7a869b900853f3f5ca + md5: f7b4d76975aac7e5d9e6ad13845f92fe depends: - __glibc >=2.17,<3.0.a0 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 447215 - timestamp: 1757042483384 + size: 447919 + timestamp: 1759967942498 - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.1-default_h64bd3f2_1002.conda sha256: 266dfe151066c34695dbdc824ba1246b99f016115ef79339cbcf005ac50527c1 md5: b0cac6e5b06ca5eeb14b4f7cf908619f @@ -5534,6 +5483,7 @@ packages: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: BSD-3-Clause + license_family: BSD purls: [] size: 2414731 timestamp: 1757624335056 @@ -5649,44 +5599,6 @@ packages: purls: [] size: 33731 timestamp: 1750274110928 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda - sha256: 0105bd108f19ea8e6a78d2d994a6d4a8db16d19a41212070d2d1d48a63c34161 - md5: a587892d3c13b6621a6091be690dbca2 - depends: - - libgcc-ng >=12 - license: ISC - purls: [] - size: 205978 - timestamp: 1716828628198 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.20-hfdf4475_0.conda - sha256: d3975cfe60e81072666da8c76b993af018cf2e73fe55acba2b5ba0928efaccf5 - md5: 6af4b059e26492da6013e79cbcb4d069 - depends: - - __osx >=10.13 - license: ISC - purls: [] - size: 210249 - timestamp: 1716828641383 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda - sha256: fade8223e1e1004367d7101dd17261003b60aa576df6d7802191f8972f7470b1 - md5: a7ce36e284c5faaf93c220dfc39e3abd - depends: - - __osx >=11.0 - license: ISC - purls: [] - size: 164972 - timestamp: 1716828607917 -- conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.20-hc70643c_0.conda - sha256: 7bcb3edccea30f711b6be9601e083ecf4f435b9407d70fc48fbcf9e5d69a0fc6 - md5: 198bb594f202b205c7d18b936fa4524f - depends: - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - license: ISC - purls: [] - size: 202344 - timestamp: 1716828757533 - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda sha256: 6d9c32fc369af5a84875725f7ddfbfc2ace795c28f246dc70055a79f9b2003da md5: 0b367fad34931cb79e0d6b7e5c06bb1c @@ -5730,38 +5642,40 @@ packages: purls: [] size: 1288499 timestamp: 1753948889360 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_5.conda - sha256: 0f5f61cab229b6043541c13538d75ce11bd96fb2db76f94ecf81997b1fde6408 - md5: 4e02a49aaa9d5190cb630fa43528fbe6 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda + sha256: 1b981647d9775e1cdeb2fab0a4dd9cd75a6b0de2963f6c3953dbd712f78334b3 + md5: 5b767048b1b3ee9a954b06f4084f93dc depends: - __glibc >=2.17,<3.0.a0 - - libgcc 15.1.0 h767d61c_5 + - libgcc 15.2.0 h767d61c_7 + constrains: + - libstdcxx-ng ==15.2.0=*_7 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 3896432 - timestamp: 1757042571458 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_5.conda - sha256: 7b8cabbf0ab4fe3581ca28fe8ca319f964078578a51dd2ca3f703c1d21ba23ff - md5: 8bba50c7f4679f08c861b597ad2bda6b + size: 3898269 + timestamp: 1759968103436 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_7.conda + sha256: 024fd46ac3ea8032a5ec3ea7b91c4c235701a8bf0e6520fe5e6539992a6bd05f + md5: f627678cf829bd70bccf141a19c3ad3e depends: - - libstdcxx 15.1.0 h8f9b012_5 + - libstdcxx 15.2.0 h8f9b012_7 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 29233 - timestamp: 1757042603319 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.1-he9a06e4_0.conda - sha256: 776e28735cee84b97e4d05dd5d67b95221a3e2c09b8b13e3d6dbe6494337d527 - md5: af930c65e9a79a3423d6d36e265cef65 + size: 29343 + timestamp: 1759968157195 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda + sha256: e5ec6d2ad7eef538ddcb9ea62ad4346fde70a4736342c4ad87bd713641eb9808 + md5: 80c07c68d2f6870250959dcc95b209d1 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 license: BSD-3-Clause license_family: BSD purls: [] - size: 37087 - timestamp: 1757334557450 + size: 37135 + timestamp: 1758626800002 - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda sha256: c180f4124a889ac343fc59d15558e93667d894a966ec6fdb61da1604481be26b md5: 0f03292cc56bf91a077a134ea8747118 @@ -5793,9 +5707,9 @@ packages: purls: [] size: 421195 timestamp: 1753948426421 -- conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_9.conda - sha256: 373f2973b8a358528b22be5e8d84322c165b4c5577d24d94fd67ad1bb0a0f261 - md5: 08bfa5da6e242025304b206d152479ef +- conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda + sha256: 0fccf2d17026255b6e10ace1f191d0a2a18f2d65088fd02430be17c701f8ffe0 + md5: 8a86073cf3b343b87d03f41790d8b4e5 depends: - ucrt constrains: @@ -5803,8 +5717,8 @@ packages: - msys2-conda-epoch <0.0a0 license: MIT AND BSD-3-Clause-Clear purls: [] - size: 35794 - timestamp: 1737099561703 + size: 36621 + timestamp: 1759768399557 - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c md5: 5aa797f8787fe7a17d1b0821485b5adc @@ -5814,14 +5728,14 @@ packages: purls: [] size: 100393 timestamp: 1702724383534 -- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_0.conda - sha256: c3c2c74bd917d83b26c102b18bde97759c23f24e0260beb962acf7385627fc38 - md5: 5262552eb2f0d0b443adcfa265d97f0a +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_1.conda + sha256: 8890c03908a407649ac99257b63176b61d10dfa3468aa3db1994ac0973dc2803 + md5: 1d6e5fbbe84eebcd62e7cdccec799ce8 depends: - icu >=75.1,<76.0a0 - libiconv >=1.18,<2.0a0 - liblzma >=5.8.1,<6.0a0 - - libxml2-16 2.15.0 h06f855e_0 + - libxml2-16 2.15.0 h06f855e_1 - libzlib >=1.3.1,<2.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 @@ -5829,11 +5743,11 @@ packages: license: MIT license_family: MIT purls: [] - size: 42985 - timestamp: 1757953736703 -- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.0-h06f855e_0.conda - sha256: 15337581264464842ff28f616422b786161bee0169610ff292e0ea75fa78dba8 - md5: a1071825a90769083fce8dbcefcccd65 + size: 43274 + timestamp: 1758641414853 +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.0-h06f855e_1.conda + sha256: f29159eef5af2adffe2fef2d89ff2f6feda07e194883f47a4cf366e9608fb91b + md5: a5d1a1f8745fcd93f39a4b80f389962f depends: - icu >=75.1,<76.0a0 - libiconv >=1.18,<2.0a0 @@ -5847,8 +5761,8 @@ packages: license: MIT license_family: MIT purls: [] - size: 512772 - timestamp: 1757953703099 + size: 518883 + timestamp: 1758641386772 - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 md5: edb0dca6bc32e4f4789199455a1dbeb8 @@ -5900,21 +5814,21 @@ packages: purls: [] size: 55476 timestamp: 1727963768015 -- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-20.1.8-hfa2b4ca_2.conda - sha256: 8970b7f9057a1c2c18bfd743c6f5ce73b86197d7724423de4fa3d03911d5874b - md5: 2dc2edf349464c8b83a576175fc2ad42 +- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.3-hfa2b4ca_0.conda + sha256: 54826ea90c80ca04640b0fc1a0b3aabfd0f4e60e03c270b2a919a3655f21bc78 + md5: b1dd38bdf96540a6dedf0d196108c9a1 depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: - intel-openmp <0.0a0 - - openmp 20.1.8|20.1.8.* + - openmp 21.1.3|21.1.3.* license: Apache-2.0 WITH LLVM-exception license_family: APACHE purls: [] - size: 344490 - timestamp: 1756145011384 + size: 347945 + timestamp: 1760282911326 - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl name: lmfit version: 1.3.4 @@ -5950,6 +5864,31 @@ packages: - pytest-cov ; extra == 'test' - lmfit[dev,doc,test] ; extra == 'all' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + name: mando + version: 0.7.1 + sha256: 26ef1d70928b6057ee3ca12583d73c63e05c49de8972d620c278a7b206581a8a + requires_dist: + - six + - argparse ; python_full_version < '2.7' + - funcsigs ; python_full_version < '3.3' + - rst2ansi ; extra == 'restructuredtext' +- pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl + name: markdown + version: '3.9' + sha256: 9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280 + requires_dist: + - importlib-metadata>=4.4 ; python_full_version < '3.10' + - coverage ; extra == 'testing' + - pyyaml ; extra == 'testing' + - mkdocs>=1.6 ; extra == 'docs' + - mkdocs-nature>=0.6 ; extra == 'docs' + - mdx-gh-links>=0.2 ; extra == 'docs' + - mkdocstrings[python] ; extra == 'docs' + - mkdocs-gen-files ; extra == 'docs' + - mkdocs-section-index ; extra == 'docs' + - mkdocs-literate-nav ; extra == 'docs' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl name: markdown-it-py version: 4.0.0 @@ -5983,138 +5922,50 @@ packages: - pytest-regressions ; extra == 'testing' - requests ; extra == 'testing' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py311h2dc5d0c_1.conda - sha256: 0291d90706ac6d3eea73e66cd290ef6d805da3fad388d1d476b8536ec92ca9a8 - md5: 6565a715337ae279e351d0abd8ffe88a - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 25354 - timestamp: 1733219879408 -- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py313h8060acc_1.conda - sha256: d812caf52efcea7c9fd0eafb21d45dadfd0516812f667b928bee50e87634fae5 - md5: 21b62c55924f01b6eef6827167b46acb - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 24856 - timestamp: 1733219782830 -- conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.2-py311ha3cf9ac_1.conda - sha256: e9965b5d4c29b17b1512035b24a7c126ed7bdb6b39103b52cae099d5bb4194a9 - md5: 1d6596ca7c7b66215c5c0d58b3cb0dd3 - depends: - - __osx >=10.13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 24688 - timestamp: 1733219887972 -- conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.2-py313h717bdf5_1.conda - sha256: 297242943522a907c270bc2f191d16142707d970541b9a093640801b767d7aa7 - md5: a6fbde71416d6eb9898fcabf505a85c5 - depends: - - __osx >=10.13 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 24363 - timestamp: 1733219815199 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py311h4921393_1.conda - sha256: 4f738a7c80e34e5e5d558e946b06d08e7c40e3cc4bdf08140bf782c359845501 - md5: 249e2f6f5393bb6b36b3d3a3eebdcdf9 - depends: - - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 24976 - timestamp: 1733219849253 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py313ha9b7d5b_1.conda - sha256: 81759af8a9872c8926af3aa59dc4986eee90a0956d1ec820b42ac4f949a71211 - md5: 3acf05d8e42ff0d99820d2d889776fff - depends: - - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 24757 - timestamp: 1733219916634 -- conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.2-py311h5082efb_1.conda - sha256: 6f756e13ccf1a521d3960bd3cadddf564e013e210eaeced411c5259f070da08e - md5: c1f2ddad665323278952a453912dc3bd - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 28238 - timestamp: 1733220208800 -- conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.2-py313hb4c8b1a_1.conda - sha256: f16cb398915f52d582bcea69a16cf69a56dab6ea2fab6f069da9c2c10f09534c - md5: ec9ecf6ee4cceb73a0c9a8cdfdf58bed - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 27930 - timestamp: 1733220059655 -- pypi: https://files.pythonhosted.org/packages/80/d6/5d3665aa44c49005aaacaa68ddea6fcb27345961cd538a98bb0177934ede/matplotlib-3.10.6-cp311-cp311-macosx_10_12_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl + name: markupsafe + version: 3.0.3 + sha256: 9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl + name: markupsafe + version: 3.0.3 + sha256: 1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: markupsafe + version: 3.0.3 + sha256: 0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl + name: markupsafe + version: 3.0.3 + sha256: e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl + name: markupsafe + version: 3.0.3 + sha256: de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl + name: markupsafe + version: 3.0.3 + sha256: 116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: markupsafe + version: 3.0.3 + sha256: ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl + name: markupsafe + version: 3.0.3 + sha256: 4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/02/9c/207547916a02c78f6bdd83448d9b21afbc42f6379ed887ecf610984f3b4e/matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl name: matplotlib - version: 3.10.6 - sha256: 905b60d1cb0ee604ce65b297b61cf8be9f4e6cfecf95a3fe1c388b5266bc8f4f + version: 3.10.7 + sha256: 1d9d3713a237970569156cfb4de7533b7c4eacdd61789726f444f96a0d28f57f requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6123,17 +5974,17 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/8c/af/30ddefe19ca67eebd70047dabf50f899eaff6f3c5e6a1a7edaecaf63f794/matplotlib-3.10.6-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/10/b7/4aa196155b4d846bd749cf82aa5a4c300cf55a8b5e0dfa5b722a63c0f8a0/matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: matplotlib - version: 3.10.6 - sha256: 7bac38d816637343e53d7185d0c66677ff30ffb131044a81898b5792c956ba76 + version: 3.10.7 + sha256: 2222c7ba2cbde7fe63032769f6eb7e83ab3227f47d997a8453377709b7fe3a5a requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6142,17 +5993,17 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/a0/db/18380e788bb837e724358287b08e223b32bc8dccb3b0c12fa8ca20bc7f3b/matplotlib-3.10.6-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/22/ff/6425bf5c20d79aa5b959d1ce9e65f599632345391381c9a104133fe0b171/matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: matplotlib - version: 3.10.6 - sha256: 819e409653c1106c8deaf62e6de6b8611449c2cd9939acb0d7d4e57a3d95cc7a + version: 3.10.7 + sha256: b3c4ea4948d93c9c29dc01c0c23eef66f2101bf75158c291b88de6525c55c3d1 requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6161,17 +6012,17 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/d3/0f/38dd49445b297e0d4f12a322c30779df0d43cb5873c7847df8a82e82ec67/matplotlib-3.10.6-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/33/cd/b145f9797126f3f809d177ca378de57c45413c5099c5990de2658760594a/matplotlib-3.10.7-cp311-cp311-win_amd64.whl name: matplotlib - version: 3.10.6 - sha256: 59c8ac8382fefb9cb71308dde16a7c487432f5255d8f1fd32473523abecfecdf + version: 3.10.7 + sha256: 6516ce375109c60ceec579e699524e9d504cd7578506f01150f7a6bc174a775e requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6180,17 +6031,17 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/d3/29/4a8650a3dcae97fa4f375d46efcb25920d67b512186f8a6788b896062a81/matplotlib-3.10.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl name: matplotlib - version: 3.10.6 - sha256: 942a8de2b5bfff1de31d95722f702e2966b8a7e31f4e68f7cd963c7cd8861cf6 + version: 3.10.7 + sha256: 37a1fea41153dd6ee061d21ab69c9cf2cf543160b1b85d89cd3d2e2a7902ca4c requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6199,17 +6050,17 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e5/b8/9eea6630198cb303d131d95d285a024b3b8645b1763a2916fddb44ca8760/matplotlib-3.10.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/e1/b6/23064a96308b9aeceeffa65e96bcde459a2ea4934d311dee20afde7407a0/matplotlib-3.10.7-cp313-cp313-win_amd64.whl name: matplotlib - version: 3.10.6 - sha256: 84e82d9e0fd70c70bc55739defbd8055c54300750cbacf4740c9673a24d6933a + version: 3.10.7 + sha256: 744991e0cc863dd669c8dc9136ca4e6e0082be2070b9d793cbd64bec872a6815 requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6218,17 +6069,17 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/fc/8e/0a18d6d7d2d0a2e66585032a760d13662e5250c784d53ad50434e9560991/matplotlib-3.10.6-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/e2/6a/d42588ad895279ff6708924645b5d2ed54a7fb2dc045c8a804e955aeace1/matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl name: matplotlib - version: 3.10.6 - sha256: abb5d9478625dd9c9eb51a06d39aae71eda749ae9b3138afb23eb38824026c7e + version: 3.10.7 + sha256: d9749313deb729f08207718d29c86246beb2ea3fdba753595b55901dee5d2fd6 requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6237,17 +6088,17 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/ff/1d/70c28528794f6410ee2856cd729fa1f1756498b8d3126443b0a94e1a8695/matplotlib-3.10.6-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/fc/bc/0fb489005669127ec13f51be0c6adc074d7cf191075dab1da9fe3b7a3cfc/matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl name: matplotlib - version: 3.10.6 - sha256: 1b53bd6337eba483e2e7d29c5ab10eee644bc3a2491ec67cc55f7b44583ffb18 + version: 3.10.7 + sha256: 53b492410a6cd66c7a471de6c924f6ede976e963c0f3097a3b7abfadddc67d0a requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6256,43 +6107,190 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - sha256: 69b7dc7131703d3d60da9b0faa6dd8acbf6f6c396224cf6aef3e855b8c0c41c6 - md5: af6ab708897df59bd6e7283ceab1b56b - depends: - - python >=3.9 +- pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + name: matplotlib-inline + version: 0.1.7 + sha256: df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca + requires_dist: - traitlets - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/matplotlib-inline?source=hash-mapping - size: 14467 - timestamp: 1733417051523 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl + name: mdit-py-plugins + version: 0.5.0 + sha256: 07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f + requires_dist: + - markdown-it-py>=2.0.0,<5.0.0 + - pre-commit ; extra == 'code-style' + - myst-parser ; extra == 'rtd' + - sphinx-book-theme ; extra == 'rtd' + - coverage ; extra == 'testing' + - pytest ; extra == 'testing' + - pytest-cov ; extra == 'testing' + - pytest-regressions ; extra == 'testing' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl name: mdurl version: 0.1.2 sha256: 84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - sha256: 609ea628ace5c6cdbdce772704e6cb159ead26969bb2f386ca1757632b0f74c6 - md5: f5a4d548d1d3bdd517260409fc21e205 - depends: - - python >=3.10 - - typing_extensions - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/mistune?source=hash-mapping - size: 72996 - timestamp: 1756495311698 +- pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + name: mergedeep + version: 1.3.4 + sha256: 70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307 + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + name: mistune + version: 3.1.4 + sha256: 93691da911e5d9d2e23bc54472892aff676df27a75274962ff9edc210364266d + requires_dist: + - typing-extensions ; python_full_version < '3.11' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl + name: mkdocs + version: 1.6.1 + sha256: db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e + requires_dist: + - click>=7.0 + - colorama>=0.4 ; sys_platform == 'win32' + - ghp-import>=1.0 + - importlib-metadata>=4.4 ; python_full_version < '3.10' + - jinja2>=2.11.1 + - markdown>=3.3.6 + - markupsafe>=2.0.1 + - mergedeep>=1.3.4 + - mkdocs-get-deps>=0.2.0 + - packaging>=20.5 + - pathspec>=0.11.1 + - pyyaml-env-tag>=0.1 + - pyyaml>=5.1 + - watchdog>=2.0 + - babel>=2.9.0 ; extra == 'i18n' + - babel==2.9.0 ; extra == 'min-versions' + - click==7.0 ; extra == 'min-versions' + - colorama==0.4 ; sys_platform == 'win32' and extra == 'min-versions' + - ghp-import==1.0 ; extra == 'min-versions' + - importlib-metadata==4.4 ; python_full_version < '3.10' and extra == 'min-versions' + - jinja2==2.11.1 ; extra == 'min-versions' + - markdown==3.3.6 ; extra == 'min-versions' + - markupsafe==2.0.1 ; extra == 'min-versions' + - mergedeep==1.3.4 ; extra == 'min-versions' + - mkdocs-get-deps==0.2.0 ; extra == 'min-versions' + - packaging==20.5 ; extra == 'min-versions' + - pathspec==0.11.1 ; extra == 'min-versions' + - pyyaml-env-tag==0.1 ; extra == 'min-versions' + - pyyaml==5.1 ; extra == 'min-versions' + - watchdog==2.0 ; extra == 'min-versions' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + name: mkdocs-autorefs + version: 1.2.0 + sha256: d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f + requires_dist: + - markdown>=3.3 + - markupsafe>=2.0.1 + - mkdocs>=1.1 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl + name: mkdocs-get-deps + version: 0.2.0 + sha256: 2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134 + 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.8' +- pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + name: mkdocs-jupyter + version: 0.25.1 + sha256: 3f679a857609885d322880e72533ef5255561bbfdb13cfee2a1e92ef4d4ad8d8 + requires_dist: + - ipykernel>6.0.0,<7.0.0 + - jupytext>1.13.8,<2 + - mkdocs-material>9.0.0 + - mkdocs>=1.4.0,<2 + - nbconvert>=7.2.9,<8 + - pygments>2.12.0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + name: mkdocs-markdownextradata-plugin + version: 0.2.6 + sha256: 34dd40870781784c75809596b2d8d879da783815b075336d541de1f150c94242 + requires_dist: + - mkdocs + - pyyaml + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl + name: mkdocs-material + version: 9.6.22 + sha256: 14ac5f72d38898b2f98ac75a5531aaca9366eaa427b0f49fc2ecf04d99b7ad84 + requires_dist: + - babel~=2.10 + - backrefs~=5.7.post1 + - colorama~=0.4 + - jinja2~=3.1 + - markdown~=3.2 + - mkdocs-material-extensions~=1.3 + - mkdocs~=1.6 + - paginate~=0.5 + - pygments~=2.16 + - pymdown-extensions~=10.2 + - requests~=2.26 + - mkdocs-git-committers-plugin-2>=1.1,<3 ; extra == 'git' + - mkdocs-git-revision-date-localized-plugin~=1.2,>=1.2.4 ; extra == 'git' + - cairosvg~=2.6 ; extra == 'imaging' + - pillow>=10.2,<12.0 ; extra == 'imaging' + - mkdocs-minify-plugin~=0.7 ; extra == 'recommended' + - mkdocs-redirects~=1.2 ; extra == 'recommended' + - mkdocs-rss-plugin~=1.6 ; extra == 'recommended' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + name: mkdocs-material-extensions + version: 1.3.1 + sha256: adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + name: mkdocs-plugin-inline-svg + version: 0.1.0 + sha256: a5aab2d98a19b24019f8e650f54fc647c2f31e7d0e36fc5cf2d2161acc0ea49a + requires_dist: + - mkdocs + requires_python: '>=3.5' +- pypi: https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + name: mkdocstrings + version: 0.27.0 + sha256: 6ceaa7ea830770959b55a16203ac63da24badd71325b96af950e59fd37366332 + requires_dist: + - click>=7.0 + - jinja2>=2.11.1 + - markdown>=3.6 + - markupsafe>=1.1 + - mkdocs>=1.4 + - mkdocs-autorefs>=1.2 + - platformdirs>=2.2 + - pymdown-extensions>=6.3 + - importlib-metadata>=4.6 ; python_full_version < '3.10' + - typing-extensions>=4.1 ; python_full_version < '3.10' + - mkdocstrings-crystal>=0.3.4 ; extra == 'crystal' + - mkdocstrings-python-legacy>=0.2.1 ; extra == 'python-legacy' + - mkdocstrings-python>=0.5.2 ; extra == 'python' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl + name: mkdocstrings-python + version: 1.13.0 + sha256: b88bbb207bab4086434743849f8e796788b373bd32e7bfefbf8560ac45d88f97 + requires_dist: + - mkdocstrings>=0.26 + - mkdocs-autorefs>=1.2 + - griffe>=0.49 + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2024.2.2-h57928b3_16.conda sha256: ce841e7c3898764154a9293c0f92283c1eb28cdacf7a164c94b632a6af675d91 md5: 5cddc979c74b90cf5e5cda4f97d5d8bb @@ -6324,6 +6322,46 @@ packages: - sphinx ; extra == 'docs' - gmpy2>=2.1.0a4 ; platform_python_implementation != 'PyPy' and extra == 'gmpy' - pytest>=4.6 ; extra == 'tests' +- pypi: https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl + name: msgpack + version: 1.1.2 + sha256: d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl + name: msgpack + version: 1.1.2 + sha256: 2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c + requires_python: '>=3.9' +- 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 + name: msgpack + version: 1.1.2 + sha256: fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl + name: msgpack + version: 1.1.2 + sha256: 4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl + name: msgpack + version: 1.1.2 + sha256: a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl + name: msgpack + version: 1.1.2 + sha256: 283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl + name: msgpack + version: 1.1.2 + sha256: 42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7 + requires_python: '>=3.9' +- 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 + name: msgpack + version: 1.1.2 + sha256: 454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/23/d8/f15b40611c2d5753d1abb0ca0da0c75348daf1252220e5dda2867bd81062/msgspec-0.19.0-cp313-cp313-win_amd64.whl name: msgspec version: 0.19.0 @@ -6620,70 +6658,70 @@ packages: - tomli ; python_full_version < '3.11' and extra == 'dev' - tomli-w ; extra == 'dev' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/00/6e/fac58b1072a6fc59af5e7acb245e8754d3e1f97f4f808a6559951f72a0d4/multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl name: multidict - version: 6.6.4 - sha256: e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52 + version: 6.7.0 + sha256: 14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl name: multidict - version: 6.6.4 - sha256: 22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657 + version: 6.7.0 + sha256: 95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/54/a3/bed07bc9e2bb302ce752f1dabc69e884cd6a676da44fb0e501b246031fdd/multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl name: multidict - version: 6.6.4 - sha256: 6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb + version: 6.7.0 + sha256: 30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/74/7d/36b045c23a1ab98507aefd44fd8b264ee1dd5e5010543c6fccf82141ccef/multidict-6.6.4-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl name: multidict - version: 6.6.4 - sha256: d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e + version: 6.7.0 + sha256: 4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl name: multidict - version: 6.6.4 - sha256: 497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f + version: 6.7.0 + sha256: 7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: multidict - version: 6.6.4 - sha256: 40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf + version: 6.7.0 + sha256: 3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/a7/4b/ceeb4f8f33cf81277da464307afeaf164fb0297947642585884f5cad4f28/multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: multidict - version: 6.6.4 - sha256: 66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e + version: 6.7.0 + sha256: 9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl name: multidict - version: 6.6.4 - sha256: 5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da + version: 6.7.0 + sha256: 9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl name: narwhals - version: 2.5.0 - sha256: 7e213f9ca7db3f8bf6f7eff35eaee6a1cf80902997e1b78d49b7755775d8f423 + version: 2.8.0 + sha256: 6304856676ba4a79fd34148bda63aed8060dd6edb1227edf3659ce5e091de73c requires_dist: - cudf>=24.10.0 ; extra == 'cudf' - dask[dataframe]>=2024.8 ; extra == 'dask' - - duckdb>=1.0 ; extra == 'duckdb' + - duckdb>=1.1 ; extra == 'duckdb' - ibis-framework>=6.0.0 ; extra == 'ibis' - packaging ; extra == 'ibis' - pyarrow-hotfix ; extra == 'ibis' @@ -6696,66 +6734,149 @@ packages: - pyspark[connect]>=3.5.0 ; extra == 'pyspark-connect' - sqlframe>=3.22.0,!=3.39.3 ; extra == 'sqlframe' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - sha256: a20cff739d66c2f89f413e4ba4c6f6b59c50d5c30b5f0d840c13e8c9c2df9135 - md5: 6bb0d77277061742744176ab555b723c - depends: - - jupyter_client >=6.1.12 - - jupyter_core >=4.12,!=5.0.* - - nbformat >=5.1 - - python >=3.8 - - traitlets >=5.4 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/nbclient?source=hash-mapping - size: 28045 - timestamp: 1734628936013 -- conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - sha256: dcccb07c5a1acb7dc8be94330e62d54754c0e9c9cb2bb6865c8e3cfe44cf5a58 - md5: d24beda1d30748afcc87c429454ece1b - depends: +- pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + name: nbclient + version: 0.10.2 + sha256: 4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d + requires_dist: + - jupyter-client>=6.1.12 + - jupyter-core>=4.12,!=5.0.* + - nbformat>=5.1 + - traitlets>=5.4 + - pre-commit ; extra == 'dev' + - autodoc-traits ; extra == 'docs' + - flaky ; extra == 'docs' + - ipykernel>=6.19.3 ; extra == 'docs' + - ipython ; extra == 'docs' + - ipywidgets ; extra == 'docs' + - mock ; extra == 'docs' + - moto ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbconvert>=7.1.0 ; extra == 'docs' + - pytest-asyncio ; extra == 'docs' + - pytest-cov>=4.0 ; extra == 'docs' + - pytest>=7.0,<8 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx>=1.7 ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - testpath ; extra == 'docs' + - xmltodict ; extra == 'docs' + - flaky ; extra == 'test' + - ipykernel>=6.19.3 ; extra == 'test' + - ipython ; extra == 'test' + - ipywidgets ; extra == 'test' + - nbconvert>=7.1.0 ; extra == 'test' + - pytest-asyncio ; extra == 'test' + - pytest-cov>=4.0 ; extra == 'test' + - pytest>=7.0,<8 ; extra == 'test' + - testpath ; extra == 'test' + - xmltodict ; extra == 'test' + requires_python: '>=3.9.0' +- pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + name: nbconvert + version: 7.16.6 + sha256: 1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b + requires_dist: - beautifulsoup4 - - bleach-with-css !=5.0.0 + - bleach[css]!=5.0.0 - defusedxml - - importlib-metadata >=3.6 - - jinja2 >=3.0 - - jupyter_core >=4.7 - - jupyterlab_pygments - - markupsafe >=2.0 - - mistune >=2.0.3,<4 - - nbclient >=0.5.0 - - nbformat >=5.7 + - importlib-metadata>=3.6 ; python_full_version < '3.10' + - jinja2>=3.0 + - jupyter-core>=4.7 + - jupyterlab-pygments + - markupsafe>=2.0 + - mistune>=2.0.3,<4 + - nbclient>=0.5.0 + - nbformat>=5.7 - packaging - - pandocfilters >=1.4.1 - - pygments >=2.4.1 - - python >=3.9 - - traitlets >=5.1 - - python - constrains: - - pandoc >=2.9.2,<4.0.0 - - nbconvert ==7.16.6 *_0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/nbconvert?source=hash-mapping - size: 200601 - timestamp: 1738067871724 -- conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - sha256: 7a5bd30a2e7ddd7b85031a5e2e14f290898098dc85bea5b3a5bf147c25122838 - md5: bbe1963f1e47f594070ffe87cdf612ea - depends: - - jsonschema >=2.6 - - jupyter_core >=4.12,!=5.0.* - - python >=3.9 - - python-fastjsonschema >=2.15 - - traitlets >=5.1 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/nbformat?source=hash-mapping - size: 100945 - timestamp: 1733402844974 + - pandocfilters>=1.4.1 + - pygments>=2.4.1 + - traitlets>=5.1 + - flaky ; extra == 'all' + - ipykernel ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets>=7.5 ; extra == 'all' + - myst-parser ; extra == 'all' + - nbsphinx>=0.2.12 ; extra == 'all' + - playwright ; extra == 'all' + - pydata-sphinx-theme ; extra == 'all' + - pyqtwebengine>=5.15 ; extra == 'all' + - pytest>=7 ; extra == 'all' + - sphinx==5.0.2 ; extra == 'all' + - sphinxcontrib-spelling ; extra == 'all' + - tornado>=6.1 ; extra == 'all' + - ipykernel ; extra == 'docs' + - ipython ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbsphinx>=0.2.12 ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx==5.0.2 ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - pyqtwebengine>=5.15 ; extra == 'qtpdf' + - pyqtwebengine>=5.15 ; extra == 'qtpng' + - tornado>=6.1 ; extra == 'serve' + - flaky ; extra == 'test' + - ipykernel ; extra == 'test' + - ipywidgets>=7.5 ; extra == 'test' + - pytest>=7 ; extra == 'test' + - playwright ; extra == 'webpdf' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + name: nbformat + version: 5.10.4 + sha256: 3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b + requires_dist: + - fastjsonschema>=2.15 + - jsonschema>=2.6 + - jupyter-core>=4.12,!=5.0.* + - traitlets>=5.1 + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinxcontrib-github-alt ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - pep440 ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest ; extra == 'test' + - testpath ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + name: nbmake + version: 1.5.5 + sha256: c6fbe6e48b60cacac14af40b38bf338a3b88f47f085c54ac5b8639ff0babaf4b + requires_dist: + - ipykernel>=5.4.0 + - nbclient>=0.6.6 + - nbformat>=5.0.4 + - pygments>=2.7.3 + - pytest>=6.1.0 + requires_python: '>=3.8.0' +- pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + name: nbqa + version: 1.9.1 + sha256: 95552d2f6c2c038136252a805aa78d85018aef922586270c3a074332737282e5 + requires_dist: + - autopep8>=1.5 + - ipython>=7.8.0 + - tokenize-rt>=3.2.0 + - tomli + - black ; extra == 'toolchain' + - blacken-docs ; extra == 'toolchain' + - flake8 ; extra == 'toolchain' + - isort ; extra == 'toolchain' + - jupytext ; extra == 'toolchain' + - mypy ; extra == 'toolchain' + - pylint ; extra == 'toolchain' + - pyupgrade ; extra == 'toolchain' + - ruff ; extra == 'toolchain' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + name: nbstripout + version: 0.8.1 + sha256: 79a8c8da488d98c54c112fa87185045f0271a97d84f1d46918d6a3ee561b30e7 + requires_dist: + - nbformat + requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 md5: 47e340acb35de30501a76c7c799c41d7 @@ -6784,49 +6905,47 @@ packages: purls: [] size: 797030 timestamp: 1738196177597 -- conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - sha256: bb7b21d7fd0445ddc0631f64e66d91a179de4ba920b8381f29b9d006a42788c0 - md5: 598fd7d4d0de2455fb74f56063969a97 - depends: - - python >=3.9 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/nest-asyncio?source=hash-mapping - size: 11543 - timestamp: 1733325673691 -- conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.4.1-heeeca48_0.conda - sha256: 1239ba36ea69eefcc55f107fe186810b59488923544667175f6976fa4903c8c9 - md5: d629b201c3fbc0c203ca0ad7b03f22ce +- pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + name: nest-asyncio + version: 1.6.0 + sha256: 87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c + requires_python: '>=3.5' +- pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + name: nodeenv + version: 1.9.1 + sha256: ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9 + 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-24.9.0-heeeca48_0.conda + sha256: 6abb823fd4d28e6474f40dfcf38e772e5869ee755be855cf5d2c0d49f888c75e + md5: 8a2a73951c1ea275e76fb1b92d97ff3e depends: - - libgcc >=14 - __glibc >=2.28,<3.0.a0 - libstdcxx >=14 - libgcc >=14 - libuv >=1.51.0,<2.0a0 + - openssl >=3.5.3,<4.0a0 - icu >=75.1,<76.0a0 - - openssl >=3.5.1,<4.0a0 - libzlib >=1.3.1,<2.0a0 license: MIT license_family: MIT purls: [] - size: 25669735 - timestamp: 1752839464718 -- conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.4.1-h2e7699b_0.conda - sha256: 1c9571726b5b5e85acfba50dda7ae9b22d2b29e590159a581bafde5bf2e04621 - md5: 9993063cfe84cf1fa928c7d021bd01a0 + size: 25557455 + timestamp: 1759064044872 +- conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.9.0-h09bb5a9_0.conda + sha256: 2819269de6183a7187ba8bb7b56bb3c8d0a5b68a017ba7d1f77aed88eef523f2 + md5: 6ef8bcc1b996eb3c8a23ef9a808044a3 depends: - - __osx >=10.15 - libcxx >=19 - - openssl >=3.5.1,<4.0a0 - - libuv >=1.51.0,<2.0a0 + - __osx >=10.15 - libzlib >=1.3.1,<2.0a0 - icu >=75.1,<76.0a0 + - libuv >=1.51.0,<2.0a0 + - openssl >=3.5.3,<4.0a0 license: MIT license_family: MIT purls: [] - size: 18918546 - timestamp: 1752839437994 + size: 18750023 + timestamp: 1759063982956 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-24.4.1-hab9d20b_0.conda sha256: c79d2c81f80a9adedc77362f2e8b10879ed0f9806deb6ba2464c1287a05f0b9b md5: 463a537de602f8558604f27395b323d0 @@ -6842,69 +6961,68 @@ packages: purls: [] size: 17949155 timestamp: 1752839389217 -- conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.4.1-he453025_0.conda - sha256: 1bb0d9e370bb0ffa2071ccfdd0ef3cb90bd183b07c67b646d1aa5c743004d233 - md5: cde0d5793a73ab343b5764fa6c002771 +- conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.9.0-he453025_0.conda + sha256: 8d60a2e7a49cc9db7e8032db60333609ba9e9b8e7081843ea4e6a05d7ef12bdb + md5: da14fa3bcb863b34888a9c35991c3c81 license: MIT license_family: MIT purls: [] - size: 29967122 - timestamp: 1752839409586 -- conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - sha256: 7b920e46b9f7a2d2aa6434222e5c8d739021dbc5cc75f32d124a8191d86f9056 - md5: e7f89ea5f7ea9401642758ff50a2d9c1 - depends: - - jupyter_server >=1.8,<3 - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/notebook-shim?source=hash-mapping - size: 16817 - timestamp: 1733408419340 -- pypi: https://files.pythonhosted.org/packages/0a/a2/a4f78cb2241fe5664a22a10332f2be886dcdea8784c9f6a01c272da9b426/numpy-2.3.3-cp311-cp311-win_amd64.whl + size: 30246134 + timestamp: 1759064073124 +- 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 + sha256: 411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef + requires_dist: + - jupyter-server>=1.8,<3 + - pytest ; extra == 'test' + - pytest-console-scripts ; extra == 'test' + - pytest-jupyter ; extra == 'test' + - pytest-tornasync ; extra == 'test' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl name: numpy - version: 2.3.3 - sha256: ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa + version: 2.3.4 + sha256: 56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/10/a2/010b0e27ddeacab7839957d7a8f00e91206e0c2c47abbb5f35a2630e5387/numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl name: numpy - version: 2.3.3 - sha256: bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6 + version: 2.3.4 + sha256: a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3 requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/1b/b5/263ebbbbcede85028f30047eab3d58028d7ebe389d6493fc95ae66c636ab/numpy-2.3.3-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl name: numpy - version: 2.3.3 - sha256: f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf + version: 2.3.4 + sha256: c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966 requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/52/18/cf2c648fccf339e59302e00e5f2bc87725a3ce1992f30f3f78c9044d7c43/numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/60/e7/0e07379944aa8afb49a556a2b54587b828eb41dc9adc56fb7615b678ca53/numpy-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl name: numpy - version: 2.3.3 - sha256: e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569 + version: 2.3.4 + sha256: e78aecd2800b32e8347ce49316d3eaf04aed849cd5b38e0af39f829a4e59f5eb requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/7a/45/e80d203ef6b267aa29b22714fb558930b27960a0c5ce3c19c999232bb3eb/numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/7d/10/f8850982021cb90e2ec31990291f9e830ce7d94eef432b15066e7cbe0bec/numpy-2.3.4-cp311-cp311-win_amd64.whl name: numpy - version: 2.3.3 - sha256: 0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d + version: 2.3.4 + sha256: faba246fb30ea2a526c2e9645f61612341de1a83fb1e0c5edf4ddda5a9c10996 requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/7d/b9/984c2b1ee61a8b803bf63582b4ac4242cf76e2dbd663efeafcb620cc0ccb/numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: numpy - version: 2.3.3 - sha256: f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf + version: 2.3.4 + sha256: fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953 requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/bb/32/67e3b0f07b0aba57a078c4ab777a9e8e6bc62f24fb53a2337f75f9691699/numpy-2.3.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: numpy - version: 2.3.3 - sha256: 5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93 + version: 2.3.4 + sha256: a7b2f9a18b5ff9824a6af80de4f37f4ec3c2aab05ef08f51c77a093f5b89adda requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/a6/e4/07970e3bed0b1384d22af1e9912527ecbeb47d3b26e9b6a3bced068b3bea/numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/d0/cb/5a69293561e8819b09e34ed9e873b9a82b5f2ade23dce4c51dc507f6cfe1/numpy-2.3.4-cp311-cp311-macosx_11_0_arm64.whl name: numpy - version: 2.3.3 - sha256: d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7 + version: 2.3.4 + sha256: 7fd09cc5d65bda1e79432859c40978010622112e9194e581e3415a3eccc7f43f requires_python: '>=3.11' -- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda - sha256: c9f54d4e8212f313be7b02eb962d0cb13a8dae015683a403d3accd4add3e520e - md5: ffffb341206dd0dab0c36053c048d621 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda + sha256: e807f3bad09bdf4075dbb4168619e14b0c0360bacb2e12ef18641a834c8c5549 + md5: 14edad12b59ccbfa3910d42c72adc2a0 depends: - __glibc >=2.17,<3.0.a0 - ca-certificates @@ -6912,33 +7030,33 @@ packages: license: Apache-2.0 license_family: Apache purls: [] - size: 3128847 - timestamp: 1754465526100 -- conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.2-h6e31bce_0.conda - sha256: 8be57a11019666aa481122c54e29afd604405b481330f37f918e9fbcd145ef89 - md5: 22f5d63e672b7ba467969e9f8b740ecd + size: 3119624 + timestamp: 1759324353651 +- conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda + sha256: 3ce8467773b2472b2919412fd936413f05a9b10c42e52c27bbddc923ef5da78a + md5: 075eaad78f96bbf5835952afbe44466e depends: - __osx >=10.13 - ca-certificates license: Apache-2.0 license_family: Apache purls: [] - size: 2743708 - timestamp: 1754466962243 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.2-he92f556_0.conda - sha256: f6d1c87dbcf7b39fad24347570166dade1c533ae2d53c60a70fa4dc874ef0056 - md5: bcb0d87dfbc199d0a461d2c7ca30b3d8 + size: 2747108 + timestamp: 1759326402264 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda + sha256: f0512629f9589392c2fb9733d11e753d0eab8fc7602f96e4d7f3bd95c783eb07 + md5: 71118318f37f717eefe55841adb172fd depends: - __osx >=11.0 - ca-certificates license: Apache-2.0 license_family: Apache purls: [] - size: 3074848 - timestamp: 1754465710470 -- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.2-h725018a_0.conda - sha256: 2413f3b4606018aea23acfa2af3c4c46af786739ab4020422e9f0c2aec75321b - md5: 150d3920b420a27c0848acca158f94dc + size: 3067808 + timestamp: 1759324763146 +- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda + sha256: 5ddc1e39e2a8b72db2431620ad1124016f3df135f87ebde450d235c212a61994 + md5: f28ffa510fe055ab518cbd9d6ddfea23 depends: - ca-certificates - ucrt >=10.0.20348.0 @@ -6947,36 +7065,32 @@ packages: license: Apache-2.0 license_family: Apache purls: [] - size: 9275175 - timestamp: 1754467904482 -- conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - sha256: 1840bd90d25d4930d60f57b4f38d4e0ae3f5b8db2819638709c36098c6ba770c - md5: e51f1e4089cad105b6cac64bd8166587 - depends: - - python >=3.9 - - typing_utils - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/overrides?source=hash-mapping - size: 30139 - timestamp: 1734587755455 -- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - sha256: 289861ed0c13a15d7bbb408796af4de72c2fe67e2bcb0de98f4c3fce259d7991 - md5: 58335b26c38bf4a20f399384c33cbcf9 - depends: - - python >=3.8 - - python - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/packaging?source=hash-mapping - size: 62477 - timestamp: 1745345660407 -- pypi: https://files.pythonhosted.org/packages/22/3c/f2af1ce8840ef648584a6156489636b5692c162771918aa95707c165ad2b/pandas-2.3.2-cp313-cp313-win_amd64.whl + size: 9218823 + timestamp: 1759326176247 +- pypi: https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl + name: overrides + version: 7.7.0 + sha256: c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49 + requires_dist: + - typing ; python_full_version < '3.5' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + name: packaging + version: '25.0' + sha256: 29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + name: paginate + version: 0.5.7 + sha256: b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591 + requires_dist: + - pytest ; extra == 'dev' + - tox ; extra == 'dev' + - black ; extra == 'lint' +- pypi: https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl name: pandas - version: 2.3.2 - sha256: 12d039facec710f7ba305786837d0225a3444af7bbd9c15c32ca2d40d157ed8b + version: 2.3.3 + sha256: 318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7064,10 +7178,10 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/27/64/a2f7bf678af502e16b472527735d168b22b7824e45a4d7e96a4fbb634b59/pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl name: pandas - version: 2.3.2 - sha256: 0c6ecbac99a354a051ef21c5307601093cb9e0f4b1855984a084bfec9302699e + version: 2.3.3 + sha256: bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8 requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7155,10 +7269,10 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/38/18/48f10f1cc5c397af59571d638d211f494dba481f449c19adbd282aa8f4ca/pandas-2.3.2-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl name: pandas - version: 2.3.2 - sha256: 76972bcbd7de8e91ad5f0ca884a9f2c477a2125354af624e022c49e5bd0dfff4 + version: 2.3.3 + sha256: f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7246,10 +7360,10 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/54/4c/c3d21b2b7769ef2f4c2b9299fcadd601efa6729f1357a8dbce8dd949ed70/pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl name: pandas - version: 2.3.2 - sha256: c6f048aa0fd080d6a06cc7e7537c09b53be6642d330ac6f54a600c3ace857ee9 + version: 2.3.3 + sha256: f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7337,10 +7451,10 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/7a/59/f3e010879f118c2d400902d2d871c2226cef29b08c09fb8dc41111730400/pandas-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl name: pandas - version: 2.3.2 - sha256: 1333e9c299adcbb68ee89a9bb568fc3f20f9cbb419f1dd5225071e6cddb2a743 + version: 2.3.3 + sha256: 8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45 requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7428,10 +7542,10 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/8b/ef/0e2ffb30b1f7fbc9a588bd01e3c14a0d96854d09a887e15e30cc19961227/pandas-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl name: pandas - version: 2.3.2 - sha256: 1d81573b3f7db40d020983f78721e9bfc425f411e616ef019a10ebf597aedb2e + version: 2.3.3 + sha256: b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7519,10 +7633,10 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/8f/52/0634adaace9be2d8cac9ef78f05c47f3a675882e068438b9d7ec7ef0c13f/pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl name: pandas - version: 2.3.2 - sha256: 4ac8c320bded4718b298281339c1a50fb00a6ba78cb2a63521c39bec95b0209b + version: 2.3.3 + sha256: 602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523 requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7610,10 +7724,10 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/a7/e7/ae86261695b6c8a36d6a4c8d5f9b9ede8248510d689a2f379a18354b37d7/pandas-2.3.2-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl name: pandas - version: 2.3.2 - sha256: 9467697b8083f9667b212633ad6aa4ab32436dcbaf4cd57325debb0ddef2012f + version: 2.3.3 + sha256: 56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713 requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7701,55 +7815,37 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - sha256: 2bb9ba9857f4774b85900c2562f7e711d08dd48e2add9bee4e1612fbee27e16f - md5: 457c2c8c08e54905d6954e79cb5b5db9 - depends: - - python !=3.0,!=3.1,!=3.2,!=3.3 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pandocfilters?source=hash-mapping - size: 11627 - timestamp: 1631603397334 -- conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - sha256: 30de7b4d15fbe53ffe052feccde31223a236dae0495bab54ab2479de30b2990f - md5: a110716cdb11cf51482ff4000dc253d7 - depends: - - python >=3.10 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/parso?source=hash-mapping - size: 81562 - timestamp: 1755974222274 -- conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - sha256: 202af1de83b585d36445dc1fda94266697341994d1a3328fabde4989e1b3d07a - md5: d0d408b1f18883a944376da5cf8101ea - depends: - - ptyprocess >=0.5 - - python >=3.9 - license: ISC - purls: - - pkg:pypi/pexpect?source=hash-mapping - size: 53561 - timestamp: 1733302019362 -- conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda - sha256: e2ac3d66c367dada209fc6da43e645672364b9fd5f9d28b9f016e24b81af475b - md5: 11a9d1d09a3615fc07c3faf79bc0b943 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pickleshare?source=hash-mapping - size: 11748 - timestamp: 1733327448200 -- pypi: https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + name: pandocfilters + version: 1.5.1 + sha256: 93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*' +- pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + name: parso + version: 0.8.5 + sha256: 646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887 + requires_dist: + - pytest ; extra == 'testing' + - docopt ; extra == 'testing' + - flake8==5.0.4 ; extra == 'qa' + - mypy==0.971 ; extra == 'qa' + - types-setuptools==67.2.0.1 ; extra == 'qa' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + name: pathspec + version: 0.12.1 + sha256: a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl + name: pexpect + version: 4.9.0 + sha256: 7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523 + requires_dist: + - ptyprocess>=0.5 +- pypi: https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl name: pillow - version: 11.3.0 - sha256: ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c + version: 12.0.0 + sha256: 0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7760,6 +7856,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7767,19 +7866,18 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl name: pillow - version: 11.3.0 - sha256: 0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51 + version: 12.0.0 + sha256: a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7790,6 +7888,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7797,19 +7898,18 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: pillow - version: 11.3.0 - sha256: 9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288 + version: 12.0.0 + sha256: f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7820,6 +7920,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7827,19 +7930,18 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: pillow - version: 11.3.0 - sha256: 13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8 + version: 12.0.0 + sha256: bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7850,6 +7952,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7857,19 +7962,18 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl name: pillow - version: 11.3.0 - sha256: 1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722 + version: 12.0.0 + sha256: b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7880,6 +7984,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7887,19 +7994,18 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl name: pillow - version: 11.3.0 - sha256: 1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac + version: 12.0.0 + sha256: 4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7910,6 +8016,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7917,19 +8026,18 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl name: pillow - version: 11.3.0 - sha256: 106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f + version: 12.0.0 + sha256: 5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7940,6 +8048,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7947,19 +8058,18 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl name: pillow - version: 11.3.0 - sha256: 7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd + version: 12.0.0 + sha256: c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7970,6 +8080,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7977,15 +8090,14 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda sha256: 20fe420bb29c7e655988fd0b654888e6d7755c1d380f82ca2f1bd2493b95d650 md5: e7ab34d5a93e0819b62563c78635d937 @@ -7994,7 +8106,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/pip?source=compressed-mapping + - pkg:pypi/pip?source=hash-mapping size: 1179951 timestamp: 1753925011027 - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda @@ -8022,22 +8134,26 @@ packages: - returns>=0.23 - tomli>=2 ; python_full_version < '3.11' requires_python: '>=3.9,<4.0' -- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - sha256: dfe0fa6e351d2b0cef95ac1a1533d4f960d3992f9e0f82aeb5ec3623a699896b - md5: cc9d9a3929503785403dbfad9f707145 - depends: - - python >=3.10 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/platformdirs?source=compressed-mapping - size: 23653 - timestamp: 1756227402815 -- pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + name: platformdirs + version: 4.5.0 + sha256: e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3 + requires_dist: + - furo>=2025.9.25 ; extra == 'docs' + - proselint>=0.14 ; extra == 'docs' + - sphinx-autodoc-typehints>=3.2 ; extra == 'docs' + - sphinx>=8.2.3 ; extra == 'docs' + - appdirs==1.4.4 ; extra == 'test' + - covdefaults>=2.3 ; extra == 'test' + - pytest-cov>=7 ; extra == 'test' + - pytest-mock>=3.15.1 ; extra == 'test' + - pytest>=8.4.2 ; extra == 'test' + - mypy>=1.18.2 ; extra == 'type' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl name: plotly - version: 6.3.0 - sha256: 7ad806edce9d3cdd882eaebaf97c0c9e252043ed1ed3d382c3e3520ec07806d4 + version: 6.3.1 + sha256: 8b4420d1dcf2b040f5983eed433f95732ed24930e496d36eb70d211923532e64 requires_dist: - narwhals>=1.15.1 - packaging @@ -8074,6 +8190,17 @@ packages: - xarray ; extra == 'dev-optional' - plotly[dev-optional] ; extra == 'dev' requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + name: pluggy + version: 1.6.0 + sha256: e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + requires_dist: + - pre-commit ; extra == 'dev' + - tox ; extra == 'dev' + - pytest ; extra == 'testing' + - pytest-benchmark ; extra == 'testing' + - coverage ; extra == 'testing' + 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' @@ -8090,6 +8217,17 @@ packages: - paramiko>=2.7.0 ; extra == 'sftp' - xxhash>=1.4.3 ; extra == 'xxhash' requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl + name: pre-commit + version: 4.3.0 + sha256: 2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8 + requires_dist: + - cfgv>=2.0.0 + - identify>=1.0.0 + - nodeenv>=0.11.1 + - pyyaml>=5.1 + - virtualenv>=20.10.0 + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl name: prettytable version: 3.16.0 @@ -8100,208 +8238,247 @@ packages: - pytest-cov ; extra == 'tests' - pytest-lazy-fixtures ; extra == 'tests' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - sha256: 454e2c0ef14accc888dd2cd2e8adb8c6a3a607d2d3c2f93962698b5718e6176d - md5: c64b77ccab10b822722904d889fa83b5 - depends: - - python >=3.9 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/prometheus-client?source=hash-mapping - size: 52641 - timestamp: 1748896836631 -- conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae - md5: edb16f14d920fb3faf17f5ce582942d6 - depends: - - python >=3.10 +- pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + name: prometheus-client + version: 0.23.1 + sha256: dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99 + requires_dist: + - twisted ; extra == 'twisted' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + name: prompt-toolkit + version: 3.0.52 + sha256: 9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955 + requires_dist: - wcwidth - constrains: - - prompt_toolkit 3.0.52 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/prompt-toolkit?source=hash-mapping - size: 273927 - timestamp: 1756321848365 -- pypi: https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: propcache - version: 0.3.2 - sha256: a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9 + version: 0.4.1 + sha256: fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl name: propcache - version: 0.3.2 - sha256: c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df + version: 0.4.1 + sha256: cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl name: propcache - version: 0.3.2 - sha256: 9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252 + version: 0.4.1 + sha256: d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl name: propcache - version: 0.3.2 - sha256: 7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f + version: 0.4.1 + sha256: c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl name: propcache - version: 0.3.2 - sha256: 4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3 + version: 0.4.1 + sha256: 6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: propcache - version: 0.3.2 - sha256: e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05 + version: 0.4.1 + sha256: d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl name: propcache - version: 0.3.2 - sha256: 06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f + version: 0.4.1 + sha256: 364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl name: propcache - version: 0.3.2 - sha256: e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39 + version: 0.4.1 + sha256: 381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.0.0-py311h49ec1c0_1.conda - sha256: 729720d777b14329af411220fd305f78e8914356f963af0053420e1cf5e58a53 - md5: d30c3f3b089100634f93e97e5ee3aa85 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 483612 - timestamp: 1755851438911 -- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.0.0-py313h07c4f96_1.conda - sha256: 9182273778a10b2a82343c5c1c8b57f4551dd07d9a639585d468f4a7fe5ff1e8 - md5: 5a7c24c9dc49128731ae565cf598cde4 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 474571 - timestamp: 1755851494108 -- conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.0.0-py311h13e5629_1.conda - sha256: ce1b788a4bae81bd2246c7284d620152832b899394259f2f938755e13f3afc6c - md5: d69888db150233f54b39919c12070de5 - depends: - - __osx >=10.13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 490770 - timestamp: 1755851533700 -- conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.0.0-py313h585f44e_1.conda - sha256: df943fa46f030b043ca28bd939d7e4110273aa41197080a598da467cbd300c6b - md5: a1457ea8cfd6104cea63410320772abc - depends: - - __osx >=10.13 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 480270 - timestamp: 1755851507696 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.0.0-py311h3696347_1.conda - sha256: c21cd67c4037f232ba539f221839d1bcc7dbcc416d51f821fd319d91b5b61c3b - md5: c449b450f0c81bc09e6a59a07adf95a1 - depends: - - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 493127 - timestamp: 1755851546773 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.0.0-py313hcdf3177_1.conda - sha256: 4964c94067fdf290d4790095ead992b2a3afb438bff8bd9b51c444d97fb63914 - md5: 1ce8cf644e210b54665d8e46850d7567 - depends: - - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 484934 - timestamp: 1755851718841 -- conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.0.0-py311h3485c13_1.conda - sha256: f48c2e47fda7259235f8abb55d219c419df3cc52e2e15ee9ee17da20b86393e5 - md5: cd66a378835a5da422201faac2c114c7 - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 499413 - timestamp: 1755851559633 -- conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.0.0-py313h5ea7bf4_1.conda - sha256: 9e63542ffd8ac4104cff34e722019fc3eb6eef274c77740eef1d73056c56cade - md5: 00c2580acce9c51004818c6981c586d9 - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 490305 - timestamp: 1755851561624 -- conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - sha256: a7713dfe30faf17508ec359e0bc7e0983f5d94682492469bd462cdaae9c64d83 - md5: 7d9daffbb8d8e0af0f769dbbcd173a54 - depends: - - python >=3.9 - license: ISC - purls: - - pkg:pypi/ptyprocess?source=hash-mapping - size: 19457 - timestamp: 1733302371990 -- conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - sha256: 71bd24600d14bb171a6321d523486f6a06f855e75e547fa0cb2a0953b02047f0 - md5: 3bfdfb8dbcdc4af1ae3f9a8eb3948f04 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pure-eval?source=hash-mapping - size: 16668 - timestamp: 1733569518868 -- pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl + name: psutil + version: 7.1.0 + sha256: 5d007560c8c372efdff9e4579c2846d71de737e4605f611437255e81efcca2c5 + requires_dist: + - pytest ; extra == 'dev' + - pytest-instafail ; extra == 'dev' + - pytest-subtests ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - setuptools ; extra == 'dev' + - abi3audit ; extra == 'dev' + - black ; extra == 'dev' + - check-manifest ; extra == 'dev' + - coverage ; extra == 'dev' + - packaging ; extra == 'dev' + - pylint ; extra == 'dev' + - pyperf ; extra == 'dev' + - pypinfo ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - requests ; extra == 'dev' + - rstcheck ; extra == 'dev' + - ruff ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - toml-sort ; extra == 'dev' + - twine ; extra == 'dev' + - virtualenv ; extra == 'dev' + - vulture ; extra == 'dev' + - wheel ; extra == 'dev' + - pyreadline ; os_name == 'nt' and extra == 'dev' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - pytest ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-subtests ; extra == 'test' + - pytest-xdist ; extra == 'test' + - setuptools ; extra == 'test' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl + name: psutil + version: 7.1.0 + sha256: 76168cef4397494250e9f4e73eb3752b146de1dd950040b29186d0cce1d5ca13 + requires_dist: + - pytest ; extra == 'dev' + - pytest-instafail ; extra == 'dev' + - pytest-subtests ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - setuptools ; extra == 'dev' + - abi3audit ; extra == 'dev' + - black ; extra == 'dev' + - check-manifest ; extra == 'dev' + - coverage ; extra == 'dev' + - packaging ; extra == 'dev' + - pylint ; extra == 'dev' + - pyperf ; extra == 'dev' + - pypinfo ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - requests ; extra == 'dev' + - rstcheck ; extra == 'dev' + - ruff ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - toml-sort ; extra == 'dev' + - twine ; extra == 'dev' + - virtualenv ; extra == 'dev' + - vulture ; extra == 'dev' + - wheel ; extra == 'dev' + - pyreadline ; os_name == 'nt' and extra == 'dev' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - pytest ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-subtests ; extra == 'test' + - pytest-xdist ; extra == 'test' + - setuptools ; extra == 'test' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: psutil + version: 7.1.0 + sha256: 8c70e113920d51e89f212dd7be06219a9b88014e63a4cec69b684c327bc474e3 + requires_dist: + - pytest ; extra == 'dev' + - pytest-instafail ; extra == 'dev' + - pytest-subtests ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - setuptools ; extra == 'dev' + - abi3audit ; extra == 'dev' + - black ; extra == 'dev' + - check-manifest ; extra == 'dev' + - coverage ; extra == 'dev' + - packaging ; extra == 'dev' + - pylint ; extra == 'dev' + - pyperf ; extra == 'dev' + - pypinfo ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - requests ; extra == 'dev' + - rstcheck ; extra == 'dev' + - ruff ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - toml-sort ; extra == 'dev' + - twine ; extra == 'dev' + - virtualenv ; extra == 'dev' + - vulture ; extra == 'dev' + - wheel ; extra == 'dev' + - pyreadline ; os_name == 'nt' and extra == 'dev' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - pytest ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-subtests ; extra == 'test' + - pytest-xdist ; extra == 'test' + - setuptools ; extra == 'test' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl + name: psutil + version: 7.1.0 + sha256: 57f5e987c36d3146c0dd2528cd42151cf96cd359b9d67cfff836995cc5df9a3d + requires_dist: + - pytest ; extra == 'dev' + - pytest-instafail ; extra == 'dev' + - pytest-subtests ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - setuptools ; extra == 'dev' + - abi3audit ; extra == 'dev' + - black ; extra == 'dev' + - check-manifest ; extra == 'dev' + - coverage ; extra == 'dev' + - packaging ; extra == 'dev' + - pylint ; extra == 'dev' + - pyperf ; extra == 'dev' + - pypinfo ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - requests ; extra == 'dev' + - rstcheck ; extra == 'dev' + - ruff ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - toml-sort ; extra == 'dev' + - twine ; extra == 'dev' + - virtualenv ; extra == 'dev' + - vulture ; extra == 'dev' + - wheel ; extra == 'dev' + - pyreadline ; os_name == 'nt' and extra == 'dev' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - pytest ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-subtests ; extra == 'test' + - pytest-xdist ; extra == 'test' + - setuptools ; extra == 'test' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + name: ptyprocess + version: 0.7.0 + sha256: 4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35 +- pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + name: pure-eval + version: 0.2.3 + sha256: 1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0 + requires_dist: + - pytest ; extra == 'tests' +- pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + name: py + version: 1.11.0 + sha256: 607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' +- pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl name: py3dmol - version: 2.5.2 - sha256: b921940ff046cf7ca008a249cbd5debec561dcf337f1e5f3df7ac5d4a1954e8e + version: 2.5.3 + sha256: 5c1c9ee40bda82b91978e75f3c144be5b90cdf472e765bcef4890db76cc8f843 requires_dist: - ipython ; extra == 'ipython' - pypi: https://files.pythonhosted.org/packages/5c/a5/a8c7562ec39f2647245b52ea4aeb13b5b125b3f48c0c152e9ebce7047a0a/pycifrw-5.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -8355,221 +8532,124 @@ packages: - pypi: https://files.pythonhosted.org/packages/9f/9b/50835e8fd86073fa7aa921df61b4cebc1f0ff400e4338541675cb72b5507/pycifrw-5.0.1-cp313-cp313-win_amd64.whl name: pycifrw version: 5.0.1 - sha256: 8632df76b99932408ab432e09b30aa9a6c390df884a5f34f1e1f76201e625156 - requires_dist: - - prettytable - - ply - - numpy -- pypi: https://files.pythonhosted.org/packages/f5/5c/b999ea3e64981018d52846b9b69193fa581a70cd255912cb6962a33a666a/pycifrw-5.0.1-cp311-cp311-macosx_11_0_arm64.whl - name: pycifrw - version: 5.0.1 - sha256: 9f200aae9d7cf9c8857326587ee949dad9de8480f38d98ac6d6ee2dff5ab2308 - requires_dist: - - prettytable - - ply - - numpy -- pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - name: pycifstar - version: 0.3.0 - sha256: 5892fdf16c83372ee5f32557127d5f36e14b0bbe520883a4e2e70365382f70ed -- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - sha256: 79db7928d13fab2d892592223d7570f5061c192f27b9febd1a418427b719acc6 - md5: 12c566707c80111f9799308d9e265aef - depends: - - python >=3.9 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pycparser?source=hash-mapping - size: 110100 - timestamp: 1733195786147 -- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - sha256: 5577623b9f6685ece2697c6eb7511b4c9ac5fb607c9babc2646c811b428fd46a - md5: 6b6ece66ebcae2d5f326c77ef2c5a066 - depends: - - python >=3.9 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/pygments?source=hash-mapping - size: 889287 - timestamp: 1750615908735 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-11.1-py311h2f44256_1.conda - sha256: d0800d251981e3d124bd960e799dd0c56e8b16cd1c6fb99a32990d62f492d754 - md5: 840b246b6b86c37321e79ca56518273f - depends: - - __osx >=10.13 - - libffi >=3.4.6,<3.5.0a0 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - setuptools - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-core?source=hash-mapping - size: 486295 - timestamp: 1756813198835 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-11.1-py313h6971d95_1.conda - sha256: b71f6977e3dab2220530b15c8567da173db739be24badc2144d7da4d1755ca9c - md5: c6a63b6715af2ba52cc730a77f79e946 - depends: - - __osx >=10.13 - - libffi >=3.4.6,<3.5.0a0 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - setuptools - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-core?source=hash-mapping - size: 488771 - timestamp: 1756813420666 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-11.1-py311hf0763de_1.conda - sha256: c5cbb88710d690ae2f571037708026abb8f15de1a4d764e4b825aaad6c4549c8 - md5: 8c42827190081e9c29a1007454890067 - depends: - - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - - setuptools - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-core?source=hash-mapping - size: 477833 - timestamp: 1756813465248 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-11.1-py313had225c5_1.conda - sha256: 9d4d32942bb2b11141836dadcebd7f54b60837447bc7eef3a9ad01a8adc11547 - md5: 60277e90c6cd9972a9e53f812e5ce83f - depends: - - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - - setuptools - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-core?source=hash-mapping - size: 478509 - timestamp: 1756813300773 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-11.1-py311hbc8e8a3_1.conda - sha256: 71394c6e3f77898e856dab863f283e92f65b03e02e12fcf228bd3715a3459431 - md5: ab2c871073a077d149d6da6d03d10e44 - depends: - - __osx >=10.13 - - libffi >=3.4.6,<3.5.0a0 - - pyobjc-core 11.1.* - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping - size: 385837 - timestamp: 1756824131805 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-11.1-py313h55ae1d7_1.conda - sha256: 1ae4eb045d62e94e842b803d7d1c1da0ac08cc0b14aeda96196b2f820fcba511 - md5: 15f9b82c8a6cbbf05e136adca38864b0 - depends: - - __osx >=10.13 - - libffi >=3.4.6,<3.5.0a0 - - pyobjc-core 11.1.* - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping - size: 382649 - timestamp: 1756824068318 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-11.1-py311hc5b188e_1.conda - sha256: 62db0aa2d3a23ae2ca2c28f2c462e63c0911903169c72a139a9e659e3ed02b19 - md5: ee9a511c6ffa04ac806bd2987b2b093d - depends: - - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 - - pyobjc-core 11.1.* - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping - size: 387176 - timestamp: 1756824223349 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-11.1-py313h4e140e3_1.conda - sha256: 139322c875d35199836802fc545b814ffb2001af2cee956b9da4d2fc7d3aec45 - md5: 1057463a9f16501716f978534aa2d838 - depends: - - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 - - pyobjc-core 11.1.* - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping - size: 381682 - timestamp: 1756824258635 -- pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl + sha256: 8632df76b99932408ab432e09b30aa9a6c390df884a5f34f1e1f76201e625156 + requires_dist: + - prettytable + - ply + - numpy +- pypi: https://files.pythonhosted.org/packages/f5/5c/b999ea3e64981018d52846b9b69193fa581a70cd255912cb6962a33a666a/pycifrw-5.0.1-cp311-cp311-macosx_11_0_arm64.whl + name: pycifrw + version: 5.0.1 + sha256: 9f200aae9d7cf9c8857326587ee949dad9de8480f38d98ac6d6ee2dff5ab2308 + requires_dist: + - prettytable + - ply + - numpy +- pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz + name: pycifstar + version: 0.3.0 + sha256: 5892fdf16c83372ee5f32557127d5f36e14b0bbe520883a4e2e70365382f70ed +- pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + name: pycodestyle + version: 2.14.0 + sha256: dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + name: pycparser + version: '2.23' + sha256: e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + name: pygments + version: 2.19.2 + sha256: 86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b + requires_dist: + - colorama>=0.4.6 ; extra == 'windows-terminal' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + name: pymdown-extensions + version: 10.16.1 + sha256: d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d + requires_dist: + - markdown>=3.6 + - pyyaml + - pygments>=2.19.1 ; extra == 'extra' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl name: pyparsing - version: 3.2.4 - sha256: 91d0fcde680d42cd031daf3a6ba20da3107e08a75de50da58360e7d94ab24d36 + version: 3.2.5 + sha256: e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e requires_dist: - railroad-diagrams ; extra == 'diagrams' - jinja2 ; extra == 'diagrams' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - sha256: d016e04b0e12063fbee4a2d5fbb9b39a8d191b5a0042f0b8459188aedeabb0ca - md5: e2fd202833c4a981ce8a65974fe4abd1 - depends: - - __win - - python >=3.9 - - win_inet_pton - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pysocks?source=hash-mapping - size: 21784 - timestamp: 1733217448189 -- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 - md5: 461219d1a5bd61342293efa2c0c90eac - depends: - - __unix - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pysocks?source=hash-mapping - size: 21085 - timestamp: 1733217331982 -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.13-h9e4cc4f_0_cpython.conda - sha256: 9979a7d4621049388892489267139f1aa629b10c26601ba5dce96afc2b1551d4 - md5: 8c399445b6dc73eab839659e6c7b5ad1 +- pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + name: pyproject-hooks + version: 1.2.0 + sha256: 9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl + name: pytest + version: 8.4.2 + sha256: 872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79 + requires_dist: + - colorama>=0.4 ; sys_platform == 'win32' + - exceptiongroup>=1 ; python_full_version < '3.11' + - iniconfig>=1 + - packaging>=20 + - pluggy>=1.5,<2 + - pygments>=2.7.2 + - tomli>=1 ; python_full_version < '3.11' + - argcomplete ; extra == 'dev' + - attrs>=19.2 ; extra == 'dev' + - hypothesis>=3.56 ; extra == 'dev' + - mock ; extra == 'dev' + - requests ; extra == 'dev' + - setuptools ; extra == 'dev' + - xmlschema ; extra == 'dev' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + name: pytest-cov + version: 7.0.0 + sha256: 3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861 + requires_dist: + - coverage[toml]>=7.10.6 + - pluggy>=1.2 + - pytest>=7 + - process-tests ; extra == 'testing' + - pytest-xdist ; extra == 'testing' + - virtualenv ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + name: pytest-xdist + version: 3.8.0 + sha256: 202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88 + requires_dist: + - execnet>=2.1 + - pytest>=7.0.0 + - filelock ; extra == 'testing' + - psutil>=3.0 ; extra == 'psutil' + - setproctitle ; extra == 'setproctitle' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.14-hfe2f287_1_cpython.conda + build_number: 1 + sha256: 6515ef4018fda2826570f6f5c068e26dbd3e41a8b642f052c346812b3af28789 + md5: e87c753e04bffcda4cbfde7d052c1f7a depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.7.0,<3.0a0 + - libexpat >=2.7.1,<3.0a0 - libffi >=3.4.6,<3.5.0a0 - - libgcc >=13 + - libgcc >=14 - liblzma >=5.8.1,<6.0a0 - libnsl >=2.0.1,<2.1.0a0 - - libsqlite >=3.50.0,<4.0a0 - - libuuid >=2.38.1,<3.0a0 + - libsqlite >=3.50.4,<4.0a0 + - libuuid >=2.41.2,<3.0a0 - libxcrypt >=4.4.36 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.0,<4.0a0 + - openssl >=3.5.4,<4.0a0 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata @@ -8577,12 +8657,12 @@ packages: - python_abi 3.11.* *_cp311 license: Python-2.0 purls: [] - size: 30629559 - timestamp: 1749050021812 -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.7-h2b335a9_100_cp313.conda - build_number: 100 - sha256: 16cc30a5854f31ca6c3688337d34e37a79cdc518a06375fe3482ea8e2d6b34c8 - md5: 724dcf9960e933838247971da07fe5cf + size: 30812188 + timestamp: 1760365816536 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.8-h2b335a9_101_cp313.conda + build_number: 101 + sha256: b429867f0faf5b9b71e2ebdbe8fedd6f84f4ba53fd2010a1f1458e1e1a038b98 + md5: ae8cf86b9140c7b6a6593a582a8eab8a depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 @@ -8593,32 +8673,33 @@ packages: - liblzma >=5.8.1,<6.0a0 - libmpdec >=4.0.0,<5.0a0 - libsqlite >=3.50.4,<4.0a0 - - libuuid >=2.38.1,<3.0a0 + - libuuid >=2.41.2,<3.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.4,<4.0a0 - python_abi 3.13.* *_cp313 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata license: Python-2.0 purls: [] - size: 33583088 - timestamp: 1756911465277 + size: 37149783 + timestamp: 1760366432739 python_site_packages_path: lib/python3.13/site-packages -- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.13-h9ccd52b_0_cpython.conda - sha256: d8e15db837c10242658979bc475298059bd6615524f2f71365ab8e54fbfea43c - md5: 6e28c31688c6f1fdea3dc3d48d33e1c0 +- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.14-h3999593_1_cpython.conda + build_number: 1 + sha256: 42a5106ae9f2a745f258ca187f811da1736e5d2f39ce33df200b00a34cfb47e6 + md5: 30383eebcafb2bdb3f1f039f7d79fb6e depends: - __osx >=10.13 - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.0,<3.0a0 + - libexpat >=2.7.1,<3.0a0 - libffi >=3.4.6,<3.5.0a0 - liblzma >=5.8.1,<6.0a0 - - libsqlite >=3.50.0,<4.0a0 + - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.0,<4.0a0 + - openssl >=3.5.4,<4.0a0 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata @@ -8626,12 +8707,12 @@ packages: - python_abi 3.11.* *_cp311 license: Python-2.0 purls: [] - size: 15423460 - timestamp: 1749049420299 -- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.7-h5eba815_100_cp313.conda - build_number: 100 - sha256: 581e4db7462c383fbb64d295a99a3db73217f8c24781cbe7ab583ff9d0305968 - md5: 1759e1c9591755521bd50489756a599d + size: 15565919 + timestamp: 1760366149530 +- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.8-h2bd861f_101_cp313.conda + build_number: 101 + sha256: 1e127a5a1b35e4f8187d9810f7eaf00993cc6854b7cf6b38245d4f5c63f7522a + md5: 90ec169b37f0e65c5ccceeb7a00d5696 depends: - __osx >=10.13 - bzip2 >=1.0.8,<2.0a0 @@ -8642,29 +8723,30 @@ packages: - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.4,<4.0a0 - python_abi 3.13.* *_cp313 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata license: Python-2.0 purls: [] - size: 12575616 - timestamp: 1756911460182 + size: 17462902 + timestamp: 1760366920956 python_site_packages_path: lib/python3.13/site-packages -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.13-hc22306f_0_cpython.conda - sha256: 2c966293ef9e97e66b55747c7a97bc95ba0311ac1cf0d04be4a51aafac60dcb1 - md5: 95facc4683b7b3b9cf8ae0ed10f30dce +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.14-hec0b533_1_cpython.conda + build_number: 1 + sha256: f1fc90c0929f744d0db11d1247cd97632134494cea2a99fa24996ad928e904a8 + md5: 64d46fd57bf5b2793f75fceb6f3b6189 depends: - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.0,<3.0a0 + - libexpat >=2.7.1,<3.0a0 - libffi >=3.4.6,<3.5.0a0 - liblzma >=5.8.1,<6.0a0 - - libsqlite >=3.50.0,<4.0a0 + - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.0,<4.0a0 + - openssl >=3.5.4,<4.0a0 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata @@ -8672,12 +8754,12 @@ packages: - python_abi 3.11.* *_cp311 license: Python-2.0 purls: [] - size: 14573820 - timestamp: 1749048947732 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.7-h5c937ed_100_cp313.conda - build_number: 100 - sha256: b9776cc330fa4836171a42e0e9d9d3da145d7702ba6ef9fad45e94f0f016eaef - md5: 445d057271904b0e21e14b1fa1d07ba5 + size: 14794480 + timestamp: 1760366123572 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.8-h09175d0_101_cp313.conda + build_number: 101 + sha256: 5ff88415341058814a035375a6d9a0616769e280eebf72cddf8a8a426f572cec + md5: 71824735260cf57df846eb88aeb4fd99 depends: - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 @@ -8688,42 +8770,43 @@ packages: - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.4,<4.0a0 - python_abi 3.13.* *_cp313 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata license: Python-2.0 purls: [] - size: 11926240 - timestamp: 1756909724811 + size: 12026833 + timestamp: 1760366828517 python_site_packages_path: lib/python3.13/site-packages -- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.13-h3f84c4b_0_cpython.conda - sha256: 723dbca1384f30bd2070f77dd83eefd0e8d7e4dda96ac3332fbf8fe5573a8abb - md5: bedbb6f7bb654839719cd528f9b298ad +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.14-h30ce641_1_cpython.conda + build_number: 1 + sha256: a2e3daeccf06a6271d32d99e851a56fd938d913d2bc7a21eaa8f8219f20cd5a6 + md5: 0213e004a0cddcdc9618fa141294db39 depends: - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.0,<3.0a0 + - libexpat >=2.7.1,<3.0a0 - libffi >=3.4.6,<3.5.0a0 - liblzma >=5.8.1,<6.0a0 - - libsqlite >=3.50.0,<4.0a0 + - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.0,<4.0a0 + - openssl >=3.5.4,<4.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 constrains: - python_abi 3.11.* *_cp311 license: Python-2.0 purls: [] - size: 18242669 - timestamp: 1749048351218 -- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.7-hdf00ec1_100_cp313.conda - build_number: 100 - sha256: b86b5b3a960de2fff0bb7e0932b50846b22b75659576a257b1872177aab444cd - md5: 7cd6ebd1a32d4a5d99f8f8300c2029d5 + size: 18568514 + timestamp: 1760364337404 +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.8-hdf00ec1_101_cp313.conda + build_number: 101 + sha256: 944fcdc88b452972a829a70f115e24c120bc573d6a34810e0a418c1ddcd73553 + md5: ce6c7617eccf6671a93c867c37cd8fa4 depends: - bzip2 >=1.0.8,<2.0a0 - libexpat >=2.7.1,<3.0a0 @@ -8732,7 +8815,7 @@ packages: - libmpdec >=4.0.0,<5.0a0 - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.4,<4.0a0 - python_abi 3.13.* *_cp313 - tk >=8.6.13,<8.7.0a0 - tzdata @@ -8741,26 +8824,20 @@ packages: - vc14_runtime >=14.44.35208 license: Python-2.0 purls: [] - size: 16386672 - timestamp: 1756909324921 + size: 16639232 + timestamp: 1760364470278 python_site_packages_path: Lib/site-packages -- conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - sha256: d6a17ece93bbd5139e02d2bd7dbfa80bee1a4261dced63f65f679121686bf664 - md5: 5b8d21249ff20967101ffa321cab24e8 - depends: - - python >=3.9 - - six >=1.5 - - python - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/python-dateutil?source=hash-mapping - size: 233310 - timestamp: 1751104122689 -- pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + name: python-dateutil + version: 2.9.0.post0 + sha256: a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + requires_dist: + - six>=1.5 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' +- pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl name: python-engineio - version: 4.12.2 - sha256: 8218ab66950e179dfec4b4bbb30aecf3f5d86f5e58e6fc1aa7fde2c698b2804f + version: 4.12.3 + sha256: 7c099abb2a27ea7ab429c04da86ab2d82698cdd6c52406cb73766fe454feb7e1 requires_dist: - simple-websocket>=0.10.0 - requests>=2.21.0 ; extra == 'client' @@ -8768,62 +8845,45 @@ packages: - aiohttp>=3.4 ; extra == 'asyncio-client' - sphinx ; extra == 'docs' requires_python: '>=3.6' -- conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - sha256: df9aa74e9e28e8d1309274648aac08ec447a92512c33f61a8de0afa9ce32ebe8 - md5: 23029aae904a2ba587daba708208012f - depends: - - python >=3.9 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/fastjsonschema?source=hash-mapping - size: 244628 - timestamp: 1755304154927 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - sha256: 109794a80cf31450903522e2613b6d760ae4655e65d6fff68467934fbe297ea1 - md5: 47a123ca8e727d886a2c6d0c71658f8c - depends: - - cpython 3.13.7.* - - python_abi * *_cp313 - license: Python-2.0 - purls: [] - size: 48178 - timestamp: 1756909461701 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - sha256: 4790787fe1f4e8da616edca4acf6a4f8ed4e7c6967aa31b920208fc8f95efcca - md5: a61bf9ec79426938ff785eb69dbb1960 - depends: - - python >=3.6 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/python-json-logger?source=hash-mapping - size: 13383 - timestamp: 1677079727691 -- pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + name: python-json-logger + version: 4.0.0 + sha256: af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2 + requires_dist: + - typing-extensions ; python_full_version < '3.10' + - orjson ; implementation_name != 'pypy' and extra == 'dev' + - msgspec ; implementation_name != 'pypy' and extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - black ; extra == 'dev' + - pylint ; extra == 'dev' + - mypy ; extra == 'dev' + - pytest ; extra == 'dev' + - freezegun ; extra == 'dev' + - backports-zoneinfo ; python_full_version < '3.9' and extra == 'dev' + - tzdata ; extra == 'dev' + - build ; extra == 'dev' + - mkdocs ; extra == 'dev' + - mkdocs-material>=8.5 ; extra == 'dev' + - mkdocs-awesome-pages-plugin ; extra == 'dev' + - mdx-truly-sane-lists ; extra == 'dev' + - mkdocstrings[python] ; extra == 'dev' + - mkdocs-gen-files ; extra == 'dev' + - mkdocs-literate-nav ; extra == 'dev' + - mike ; extra == 'dev' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl name: python-socketio - version: 5.13.0 - sha256: 51f68d6499f2df8524668c24bcec13ba1414117cfb3a90115c559b601ab10caf + version: 5.14.2 + sha256: d7133f040ac7ba540f9a907cb4c36d6c4d1d54eb35e482aad9d45c22fca10654 requires_dist: - bidict>=0.21.0 - python-engineio>=4.11.0 - requests>=2.21.0 ; extra == 'client' - websocket-client>=0.54.0 ; extra == 'client' - aiohttp>=3.4 ; extra == 'asyncio-client' + - tox ; extra == 'dev' - sphinx ; extra == 'docs' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - build_number: 8 - sha256: fddf123692aa4b1fc48f0471e346400d9852d96eeed77dbfdd746fa50a8ff894 - md5: 8fcb6b0e2161850556231336dae58358 - constrains: - - python 3.11.* *_cpython - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 7003 - timestamp: 1752805919375 - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda build_number: 8 sha256: 210bffe7b121e651419cb196a2a63687b087497595c9be9d20ebe97dd06060a7 @@ -8835,344 +8895,126 @@ packages: purls: [] size: 7002 timestamp: 1752805902938 -- conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - sha256: 8d2a8bf110cc1fc3df6904091dead158ba3e614d8402a83e51ed3a8aa93cdeb0 - md5: bc8e3267d44011051f2eb14d22fb0960 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pytz?source=hash-mapping - size: 189015 - timestamp: 1742920947249 -- conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py311hefeebc8_1.conda - sha256: e3ef7e0cc53111ab81b8a9dd3eabc1374d7420d4c9fce3c8631e73310203ad55 - md5: c1cfe9f5d8e278cc4d2d4c7b0126634d - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.11.* *_cp311 - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/pywin32?source=hash-mapping - size: 6729388 - timestamp: 1756487145061 -- conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda - sha256: 87eaeb79b5961e0f216aa840bc35d5f0b9b123acffaecc4fda4de48891901f20 - md5: 1ce4f826332dca56c76a5b0cc89fb19e - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.13.* *_cp313 - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/pywin32?source=hash-mapping - size: 6695114 - timestamp: 1756487139550 -- conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py311hda3d55a_0.conda - sha256: fbf3e3f2d5596e755bd4b83b5007fa629b184349781f46e137a4e80b6754c7c0 - md5: 8a142e0fcd43513c2e876d97ba98c0fa - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - - winpty - license: MIT - license_family: MIT - purls: - - pkg:pypi/pywinpty?source=hash-mapping - size: 217009 - timestamp: 1738661736085 -- conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_0.conda - sha256: 4210038442e3f34d67de9aeab2691fa2a6f80dc8c16ab77d5ecbb2b756e04ff0 - md5: cd1fadcdf82a423c2441a95435eeab3c - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - - winpty - license: MIT - license_family: MIT - purls: - - pkg:pypi/pywinpty?source=hash-mapping - size: 217133 - timestamp: 1738661059040 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py311h2dc5d0c_2.conda - sha256: d107ad62ed5c62764fba9400f2c423d89adf917d687c7f2e56c3bfed605fb5b3 - md5: 014417753f948da1f70d132b2de573be - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 213136 - timestamp: 1737454846598 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py313h8060acc_2.conda - sha256: 6826217690cfe92d6d49cdeedb6d63ab32f51107105d6a459d30052a467037a0 - md5: 50992ba61a8a1f8c2d346168ae1c86df - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 205919 - timestamp: 1737454783637 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py311ha3cf9ac_2.conda - sha256: 4855c51eedcde05f3d9666a0766050c7cbdff29b150d63c1adc4071637ba61d7 - md5: f49b0da3b1e172263f4f1e2f261a490d - depends: - - __osx >=10.13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 197287 - timestamp: 1737454852180 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py313h717bdf5_2.conda - sha256: 27501e9b3b5c6bfabb3068189fd40c650356a258e4a82b0cfe31c60f568dcb85 - md5: b7f2984724531d2233b77c89c54be594 - depends: - - __osx >=10.13 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 196573 - timestamp: 1737455046063 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.2-py311h4921393_2.conda - sha256: 2af6006c9f692742181f4aa2e0656eb112981ccb0b420b899d3dd42c881bd72f - md5: 250b2ee8777221153fd2de9c279a7efa - depends: - - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 196951 - timestamp: 1737454935552 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.2-py313ha9b7d5b_2.conda - sha256: 58c41b86ff2dabcf9ccd9010973b5763ec28b14030f9e1d9b371d22b538bce73 - md5: 03a7926e244802f570f25401c25c13bc - depends: - - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 194243 - timestamp: 1737454911892 -- conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.2-py311h5082efb_2.conda - sha256: 6095e1d58c666f6a06c55338df09485eac34c76e43d92121d5786794e195aa4d - md5: e474ba674d780f0fa3b979ae9e81ba91 - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 187430 - timestamp: 1737454904007 -- conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.2-py313hb4c8b1a_2.conda - sha256: 5b496c96e48f495de41525cb1b603d0147f2079f88a8cf061aaf9e17a2fe1992 - md5: d14f685b5d204b023c641b188a8d0d7c - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 182783 - timestamp: 1737455202579 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py311h2315fbb_0.conda - sha256: 719104f31c414166a20281c973b6e29d1a2ab35e7930327368949895b8bc5629 - md5: 6c87a0f4566469af3585b11d89163fd7 - depends: - - python - - __glibc >=2.17,<3.0.a0 - - libstdcxx >=14 - - libgcc >=14 - - zeromq >=4.3.5,<4.4.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 386618 - timestamp: 1757387012835 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hfb55c3c_0.conda - noarch: python - sha256: a00a41b66c12d9c60e66b391e9a4832b7e28743348cf4b48b410b91927cd7819 - md5: 3399d43f564c905250c1aea268ebb935 - depends: - - python - - __glibc >=2.17,<3.0.a0 - - libstdcxx >=14 - - libgcc >=14 - - _python_abi3_support 1.* - - cpython >=3.12 - - zeromq >=4.3.5,<4.4.0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=compressed-mapping - size: 212218 - timestamp: 1757387023399 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py311h0ab6910_0.conda - sha256: a0200b5e781c22a5d7b64fd0edf5e9ab881772f7a15a6bc2359db8d0ac437bb3 - md5: 840bdfbb93e35d650205af10883ff8a0 - depends: - - python - - libcxx >=19 - - __osx >=10.13 - - zeromq >=4.3.5,<4.4.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 362304 - timestamp: 1757387126324 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py312hb7d603e_0.conda - noarch: python - sha256: 4e052fa3c4ed319e7bcc441fca09dee4ee4006ac6eb3d036a8d683fceda9304b - md5: 81511d0be03be793c622c408c909d6f9 - depends: - - python - - __osx >=10.13 - - libcxx >=19 - - _python_abi3_support 1.* - - cpython >=3.12 - - zeromq >=4.3.5,<4.4.0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 191697 - timestamp: 1757387104297 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py311h13abfa4_0.conda - sha256: 5a213744d267241e23f849c7671dc97eb98d7789fb559bf5d423ae1884294c7e - md5: 0d16883d4ab2d3fcb38460d018d6762f - depends: - - python - - __osx >=11.0 - - libcxx >=19 - - python 3.11.* *_cpython - - zeromq >=4.3.5,<4.4.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 359600 - timestamp: 1757387159663 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312hd65ceae_0.conda - noarch: python - sha256: ef33812c71eccf62ea171906c3e7fc1c8921f31e9cc1fbc3f079f3f074702061 - md5: bbd22b0f0454a5972f68a5f200643050 - depends: - - python - - __osx >=11.0 - - libcxx >=19 - - _python_abi3_support 1.* - - cpython >=3.12 - - zeromq >=4.3.5,<4.4.0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 191115 - timestamp: 1757387128258 -- conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py311hb77b9c8_0.conda - sha256: 1f146a62329093139fbe7fc109b595f19ca2b44beb921d0e1c6e61d2cb5ebef1 - md5: 96460f14570e237d27b475ef8238fdf3 - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - zeromq >=4.3.5,<4.3.6.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 363690 - timestamp: 1757387035331 -- conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312hbb5da91_0.conda - noarch: python - sha256: fd46b30e6a1e4c129045e3174446de3ca90da917a595037d28595532ab915c5d - md5: 808d263ec97bbd93b41ca01552b5fbd4 - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - zeromq >=4.3.5,<4.3.6.0a0 - - _python_abi3_support 1.* - - cpython >=3.12 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 185711 - timestamp: 1757387025899 +- pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + name: pytz + version: '2025.2' + sha256: 5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00 +- 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/a6/a1/409c1651c9f874d598c10f51ff586c416625601df4bca315d08baec4c3e3/pywinpty-3.0.2-cp311-cp311-win_amd64.whl + name: pywinpty + version: 3.0.2 + sha256: 327790d70e4c841ebd9d0f295a780177149aeb405bca44c7115a3de5c2054b23 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl + name: pywinpty + version: 3.0.2 + sha256: 18f78b81e4cfee6aabe7ea8688441d30247b73e52cd9657138015c5f4ee13a51 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl + name: pyyaml + version: 6.0.3 + sha256: 652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl + name: pyyaml + version: 6.0.3 + sha256: 44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e + requires_python: '>=3.8' +- 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 + name: pyyaml + version: 6.0.3 + sha256: b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d + requires_python: '>=3.8' +- 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 + name: pyyaml + version: 6.0.3 + sha256: 0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl + name: pyyaml + version: 6.0.3 + sha256: 79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl + name: pyyaml + version: 6.0.3 + sha256: 2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl + name: pyyaml + version: 6.0.3 + sha256: 8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl + name: pyyaml + version: 6.0.3 + sha256: 9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + name: pyyaml-env-tag + version: '1.1' + sha256: 17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04 + requires_dist: + - pyyaml + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl + name: pyzmq + version: 27.1.0 + sha256: 226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86 + requires_dist: + - cffi ; implementation_name == 'pypy' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl + name: pyzmq + version: 27.1.0 + sha256: 190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97 + requires_dist: + - cffi ; implementation_name == 'pypy' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + name: pyzmq + version: 27.1.0 + sha256: 452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc + requires_dist: + - cffi ; implementation_name == 'pypy' + requires_python: '>=3.8' +- 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 + name: pyzmq + version: 27.1.0 + sha256: 5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e + requires_dist: + - cffi ; implementation_name == 'pypy' + requires_python: '>=3.8' +- 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 + name: pyzmq + version: 27.1.0 + sha256: 43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31 + requires_dist: + - cffi ; implementation_name == 'pypy' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl + name: pyzmq + version: 27.1.0 + sha256: 9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf + requires_dist: + - cffi ; implementation_name == 'pypy' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + name: radon + version: 6.0.1 + sha256: 632cc032364a6f8bb1010a2f6a12d0f14bc7e5ede76585ef29dc0cecf4cd8859 + requires_dist: + - mando>=0.6,<0.8 + - colorama==0.4.1 ; python_full_version < '3.5' + - colorama>=0.4.1 ; python_full_version >= '3.5' + - tomli>=2.0.1 ; extra == 'toml' - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda sha256: 2d6d0c026902561ed77cd646b5021aef2d4db22e57a5b0178dfc669231e06d2c md5: 283b96675859b20a825f8fa30f311446 @@ -9204,38 +9046,27 @@ packages: purls: [] size: 252359 timestamp: 1740379663071 -- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - sha256: e20909f474a6cece176dfc0dc1addac265deb5fa92ea90e975fbca48085b20c3 - md5: 9140f1c09dd5489549c6a33931b943c7 - depends: - - attrs >=22.2.0 - - python >=3.9 - - rpds-py >=0.7.0 - - typing_extensions >=4.4.0 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/referencing?source=hash-mapping - size: 51668 - timestamp: 1737836872415 -- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - sha256: 8dc54e94721e9ab545d7234aa5192b74102263d3e704e6d0c8aa7008f2da2a7b - md5: db0c6b99149880c8ba515cf4abe93ee4 - depends: - - certifi >=2017.4.17 - - charset-normalizer >=2,<4 - - idna >=2.5,<4 - - python >=3.9 - - urllib3 >=1.21.1,<3 - constrains: - - chardet >=3.0.2,<6 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/requests?source=compressed-mapping - size: 59263 - timestamp: 1755614348400 +- pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + name: referencing + version: 0.37.0 + sha256: 381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231 + requires_dist: + - attrs>=22.2.0 + - 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 + name: requests + version: 2.32.5 + sha256: 2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 + requires_dist: + - charset-normalizer>=2,<4 + - idna>=2.5,<4 + - urllib3>=1.21.1,<3 + - certifi>=2017.4.17 + - pysocks>=1.5.6,!=1.5.7 ; extra == 'socks' + - chardet>=3.0.2,<6 ; extra == 'use-chardet-on-py3' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl name: returns version: 0.26.0 @@ -9246,181 +9077,95 @@ packages: - pytest>=8.0,<9.0 ; extra == 'check-laws' - typing-extensions>=4.0,<5.0 requires_python: '>=3.10,<4.0' -- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - sha256: 2e4372f600490a6e0b3bac60717278448e323cab1c0fecd5f43f7c56535a99c5 - md5: 36de09a8d3e5d5e6f4ee63af49e59706 - depends: - - python >=3.9 +- pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + name: rfc3339-validator + version: 0.1.4 + sha256: 24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa + requires_dist: - six - license: MIT - license_family: MIT - purls: - - pkg:pypi/rfc3339-validator?source=hash-mapping - size: 10209 - timestamp: 1733600040800 -- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - sha256: 2a5b495a1de0f60f24d8a74578ebc23b24aa53279b1ad583755f223097c41c37 - md5: 912a71cc01012ee38e6b90ddd561e36f - depends: - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/rfc3986-validator?source=hash-mapping - size: 7818 - timestamp: 1598024297745 -- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - sha256: 70001ac24ee62058557783d9c5a7bbcfd97bd4911ef5440e3f7a576f9e43bc92 - md5: 7234f99325263a5af6d4cd195035e8f2 - depends: - - python >=3.9 - - lark >=1.2.2 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/rfc3987-syntax?source=hash-mapping - size: 22913 - timestamp: 1752876729969 -- pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' +- pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + name: rfc3986-validator + version: 0.1.1 + sha256: 2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' +- pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + name: rfc3987-syntax + version: 1.1.0 + sha256: 6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f + requires_dist: + - lark>=1.2.2 + - pytest>=8.3.5 ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl name: rich - version: 14.1.0 - sha256: 536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f + version: 14.2.0 + sha256: 76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd requires_dist: - ipywidgets>=7.5.1,<9 ; extra == 'jupyter' - markdown-it-py>=2.2.0 - pygments>=2.13.0,<3.0.0 requires_python: '>=3.8.0' -- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py311h902ca64_1.conda - sha256: d9bc1564949ede4abd32aea34cf1997d704b6091e547f255dc0168996f5d5ec8 - md5: 622c389c080689ba1575a0750eb0209d - depends: - - python - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - python_abi 3.11.* *_cp311 - constrains: - - __glibc >=2.17 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 387057 - timestamp: 1756737832651 -- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py313h843e2db_1.conda - sha256: a976e90dbd229f7dcd357b0f9a5b637bf85243f3a3e844c1e266472cae53e359 - md5: 06c117e49934b564ef9ff6e61f279301 - depends: - - python - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python_abi 3.13.* *_cp313 - constrains: - - __glibc >=2.17 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 389189 - timestamp: 1756737629819 -- conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.1-py311hd3d88a1_1.conda - sha256: 85357c87af076680c071a8ea843bea554d58694d011104b721cc13bbf9ad0e75 - md5: 4b9839b15de18289ee5289a6dbcb8a45 - depends: - - python - - __osx >=10.13 - - python_abi 3.11.* *_cp311 - constrains: - - __osx >=10.13 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 376118 - timestamp: 1756737583772 -- conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.1-py313h66e1e84_1.conda - sha256: a4f887801670167d92152bfe240e9c5c0ed2431c3576241058bc6724e691c486 - md5: 5525368b99f51b2593de0f0b335fec8f - depends: - - python - - __osx >=10.13 - - python_abi 3.13.* *_cp313 - constrains: - - __osx >=10.13 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 368896 - timestamp: 1756737495945 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py311h1c3fc1a_1.conda - sha256: 95714a24265b6b4d4b218e303dcb075ba435826cb1d5927792ec94a8196c3e72 - md5: 5236ffaff99e6421aa4431b4c00ca47a - depends: - - python - - python 3.11.* *_cpython - - __osx >=11.0 - - python_abi 3.11.* *_cp311 - constrains: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 362213 - timestamp: 1756737586989 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py313h80e0809_1.conda - sha256: 26a8e509a1fc87986c24534bc3eddfa25ed3bbcea32ed64663f380b5b28e8c94 - md5: 22c8096afd85182d01d95f5a411ef804 - depends: - - python - - python 3.13.* *_cp313 - - __osx >=11.0 - - python_abi 3.13.* *_cp313 - constrains: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 354648 - timestamp: 1756737507794 -- conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.27.1-py311hf51aa87_1.conda - sha256: e61607627213b70e7be73570e7ef5e2d36b583512def108aaf78a6ab16f0cdd9 - md5: 3c5b42969dae70e100154750d29d43cc - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 247101 - timestamp: 1756737437304 -- conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.27.1-py313hfbe8231_1.conda - sha256: 0a300df3466cb98834d60f1863db60fc8d4d0cbbd732b153d3a656654f307fa2 - md5: 9fbdb4338b80392e5d59529712f30763 - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 250236 - timestamp: 1756737484957 +- pypi: https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: rpds-py + version: 0.27.1 + sha256: 2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl + name: rpds-py + version: 0.27.1 + sha256: 62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl + name: rpds-py + version: 0.27.1 + sha256: b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl + name: rpds-py + version: 0.27.1 + sha256: 1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl + name: rpds-py + version: 0.27.1 + sha256: be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl + name: rpds-py + version: 0.27.1 + sha256: e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl + name: rpds-py + version: 0.27.1 + sha256: f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: rpds-py + version: 0.27.1 + sha256: cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: ruff + version: 0.14.0 + sha256: eb732d17db2e945cfcbbc52af0143eda1da36ca8ae25083dd4f66f1542fdf82e + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl + name: ruff + version: 0.14.0 + sha256: ea95da28cd874c4d9c922b39381cbd69cb7e7b49c21b8152b014bd4f52acddc2 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl + name: ruff + version: 0.14.0 + sha256: 703799d059ba50f745605b04638fa7e9682cc3da084b2092feee63500ff3d9b8 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl + name: ruff + version: 0.14.0 + sha256: 838d1b065f4df676b7c9957992f2304e41ead7a50a568185efd404297d5701e8 + requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/0b/ef/37ed4b213d64b48422df92560af7300e10fe30b5d665dd79932baebee0c6/scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl name: scipy version: 1.16.2 @@ -9773,44 +9518,73 @@ packages: - doit>=0.36.0 ; extra == 'dev' - pydevtool ; extra == 'dev' requires_python: '>=3.11' -- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh0d859eb_1.conda - sha256: 00926652bbb8924e265caefdb1db100f86a479e8f1066efe395d5552dde54d02 - md5: 938c8de6b9de091997145b3bf25cdbf9 - depends: - - __linux - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/send2trash?source=hash-mapping - size: 22736 - timestamp: 1733322148326 -- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - sha256: 5282eb5b462502c38df8cb37cd1542c5bbe26af2453a18a0a0602d084ca39f53 - md5: e67b1b1fa7a79ff9e8e326d0caf55854 - depends: - - __osx - - pyobjc-framework-cocoa - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/send2trash?source=hash-mapping - size: 23100 - timestamp: 1733322309409 -- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh5737063_1.conda - sha256: ba8b93df52e0d625177907852340d735026c81118ac197f61f1f5baea19071ad - md5: e6a4e906051565caf5fdae5b0415b654 - depends: - - __win - - python >=3.9 - - pywin32 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/send2trash?source=hash-mapping - size: 23359 - timestamp: 1733322590167 +- pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + name: send2trash + version: 1.8.3 + sha256: 0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9 + requires_dist: + - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'nativelib' + - pywin32 ; sys_platform == 'win32' and extra == 'nativelib' + - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'objc' + - pywin32 ; sys_platform == 'win32' and extra == 'win32' + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*' +- pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl + name: setuptools + version: 80.9.0 + sha256: 062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 + requires_dist: + - pytest>=6,!=8.1.* ; extra == 'test' + - virtualenv>=13.0.0 ; extra == 'test' + - wheel>=0.44.0 ; extra == 'test' + - pip>=19.1 ; extra == 'test' + - packaging>=24.2 ; extra == 'test' + - jaraco-envs>=2.2 ; extra == 'test' + - pytest-xdist>=3 ; extra == 'test' + - jaraco-path>=3.7.2 ; extra == 'test' + - build[virtualenv]>=1.0.3 ; extra == 'test' + - filelock>=3.4.0 ; extra == 'test' + - ini2toml[lite]>=0.14 ; extra == 'test' + - tomli-w>=1.0.0 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-perf ; sys_platform != 'cygwin' and extra == 'test' + - jaraco-develop>=7.21 ; python_full_version >= '3.9' and sys_platform != 'cygwin' and extra == 'test' + - pytest-home>=0.5 ; extra == 'test' + - pytest-subprocess ; extra == 'test' + - pyproject-hooks!=1.1 ; extra == 'test' + - jaraco-test>=5.5 ; extra == 'test' + - sphinx>=3.5 ; extra == 'doc' + - jaraco-packaging>=9.3 ; extra == 'doc' + - rst-linker>=1.9 ; extra == 'doc' + - furo ; extra == 'doc' + - sphinx-lint ; extra == 'doc' + - jaraco-tidelift>=1.4 ; extra == 'doc' + - pygments-github-lexers==0.0.5 ; extra == 'doc' + - sphinx-favicon ; extra == 'doc' + - sphinx-inline-tabs ; extra == 'doc' + - sphinx-reredirects ; extra == 'doc' + - sphinxcontrib-towncrier ; extra == 'doc' + - sphinx-notfound-page>=1,<2 ; extra == 'doc' + - pyproject-hooks!=1.1 ; extra == 'doc' + - towncrier<24.7 ; extra == 'doc' + - packaging>=24.2 ; extra == 'core' + - more-itertools>=8.8 ; extra == 'core' + - jaraco-text>=3.7 ; extra == 'core' + - importlib-metadata>=6 ; python_full_version < '3.10' and extra == 'core' + - tomli>=2.0.1 ; python_full_version < '3.11' and extra == 'core' + - wheel>=0.43.0 ; extra == 'core' + - platformdirs>=4.2.2 ; extra == 'core' + - jaraco-functools>=4 ; extra == 'core' + - more-itertools ; extra == 'core' + - pytest-checkdocs>=2.4 ; extra == 'check' + - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' + - ruff>=0.8.0 ; sys_platform != 'cygwin' and extra == 'check' + - pytest-cov ; extra == 'cover' + - pytest-enabler>=2.2 ; extra == 'enabler' + - pytest-mypy ; extra == 'type' + - mypy==1.14.* ; extra == 'type' + - importlib-metadata>=7.0.2 ; python_full_version < '3.10' and extra == 'type' + - jaraco-develop>=7.21 ; sys_platform != 'cygwin' and extra == 'type' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda sha256: 972560fcf9657058e3e1f97186cc94389144b46dbdf58c807ce62e83f977e863 md5: 4de79c071274a53dcaf2a8c749d1499e @@ -9839,47 +9613,28 @@ packages: - pytest-cov ; extra == 'dev' - sphinx ; extra == 'docs' requires_python: '>=3.6' -- conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d - md5: 3339e3b65d58accf4ca4fb8748ab16b3 - depends: - - python >=3.9 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/six?source=hash-mapping - size: 18455 - timestamp: 1753199211006 -- conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - sha256: c2248418c310bdd1719b186796ae50a8a77ce555228b6acd32768e2543a15012 - md5: bf7a226e58dfb8346c70df36065d86c9 - depends: - - python >=3.9 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/sniffio?source=hash-mapping - size: 15019 - timestamp: 1733244175724 -- conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - sha256: c978576cf9366ba576349b93be1cfd9311c00537622a2f9e14ba2b90c97cae9c - md5: 18c019ccf43769d211f2cf78e9ad46c2 - depends: - - python >=3.10 - license: MIT - license_family: MIT - purls: - - pkg:pypi/soupsieve?source=compressed-mapping - size: 37803 - timestamp: 1756330614547 -- pypi: https://files.pythonhosted.org/packages/03/b5/cacf432e6f1fc9d156eca0560ac61d4355d2181e751ba8c0cd9cb232c8c1/sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + name: six + version: 1.17.0 + sha256: 4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' +- pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + name: sniffio + version: 1.3.1 + sha256: 2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + name: soupsieve + version: '2.8' + sha256: 0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl name: sqlalchemy - version: 2.0.43 - sha256: db691fa174e8f7036afefe3061bc40ac2b770718be2862bfb03aabae09051aca + version: 2.0.44 + sha256: c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -9911,13 +9666,13 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/41/1c/a7260bd47a6fae7e03768bf66451437b36451143f36b285522b865987ced/sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl name: sqlalchemy - version: 2.0.43 - sha256: e7c08f57f75a2bb62d7ee80a89686a5e5669f199235c6d1dac75cd59374091c3 + version: 2.0.44 + sha256: 0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45 requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -9949,13 +9704,13 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/8e/84/8a337454e82388283830b3586ad7847aa9c76fdd4f1df09cdd1f94591873/sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl name: sqlalchemy - version: 2.0.43 - sha256: 14111d22c29efad445cd5021a70a8b42f7d9152d8ba7f73304c4d82460946aaa + version: 2.0.44 + sha256: ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1 requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -9987,13 +9742,13 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/99/ea/92ac27f2fbc2e6c1766bb807084ca455265707e041ba027c09c17d697867/sqlalchemy-2.0.43-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/94/2d/fdb9246d9d32518bda5d90f4b65030b9bf403a935cfe4c36a474846517cb/sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: sqlalchemy - version: 2.0.43 - sha256: f42f23e152e4545157fa367b2435a1ace7571cab016ca26038867eb7df2c3631 + version: 2.0.44 + sha256: 3cf6872a23601672d61a68f390e44703442639a12ee9dd5a88bbce52a695e46e requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -10025,13 +9780,13 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/9d/77/fa7189fe44114658002566c6fe443d3ed0ec1fa782feb72af6ef7fbe98e7/sqlalchemy-2.0.43-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: sqlalchemy - version: 2.0.43 - sha256: 52d9b73b8fb3e9da34c2b31e6d99d60f5f99fd8c1225c9dad24aeb74a91e1d29 + version: 2.0.44 + sha256: b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -10063,13 +9818,13 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/ac/a5/ca2f07a2a201f9497de1928f787926613db6307992fe5cda97624eb07c2f/sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/c7/23/907193c2f4d680aedbfbdf7bf24c13925e3c7c292e813326c1b84a0b878e/sqlalchemy-2.0.44-cp311-cp311-win_amd64.whl name: sqlalchemy - version: 2.0.43 - sha256: 971ba928fcde01869361f504fcff3b7143b47d30de188b11c6357c0505824197 + version: 2.0.44 + sha256: 7a8694107eb4308a13b425ca8c0e67112f8134c846b6e1f722698708741215d5 requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -10101,13 +9856,13 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/dc/29/11ae2c2b981de60187f7cbc84277d9d21f101093d1b2e945c63774477aba/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/d4/d5/4abd13b245c7d91bdf131d4916fd9e96a584dac74215f8b5bc945206a974/sqlalchemy-2.0.44-cp311-cp311-macosx_11_0_arm64.whl name: sqlalchemy - version: 2.0.43 - sha256: 9c5a9da957c56e43d72126a3f5845603da00e0293720b03bde0aacffcf2dc04f + version: 2.0.44 + sha256: de4387a354ff230bc979b46b2207af841dc8bf29847b6c7dbe60af186d97aefa requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -10139,13 +9894,13 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/ee/13/744a32ebe3b4a7a9c7ea4e57babae7aa22070d47acf330d8e5a1359607f1/sqlalchemy-2.0.43-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/e3/81/15d7c161c9ddf0900b076b55345872ed04ff1ed6a0666e5e94ab44b0163c/sqlalchemy-2.0.44-cp311-cp311-macosx_10_9_x86_64.whl name: sqlalchemy - version: 2.0.43 - sha256: c5d1730b25d9a07727d20ad74bc1039bbbb0a6ca24e6769861c1aa5bf2c4c4a8 + version: 2.0.44 + sha256: 0fe3917059c7ab2ee3f35e77757062b1bea10a0b6ca633c58391e3f3c6c488dd requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -10177,20 +9932,19 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - sha256: 570da295d421661af487f1595045760526964f41471021056e993e73089e9c41 - md5: b1b505328da7a6b246787df4b5a49fbc - depends: - - asttokens - - executing - - pure_eval - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/stack-data?source=hash-mapping - size: 26988 - timestamp: 1733569565672 +- pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl + name: stack-data + version: 0.6.3 + sha256: d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695 + requires_dist: + - executing>=1.2.0 + - asttokens>=2.1.0 + - pure-eval + - pytest ; extra == 'tests' + - typeguard ; extra == 'tests' + - pygments ; extra == 'tests' + - littleutils ; extra == 'tests' + - cython ; extra == 'tests' - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl name: sympy version: 1.14.0 @@ -10220,60 +9974,34 @@ packages: purls: [] size: 150266 timestamp: 1755776172092 -- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh0d859eb_0.conda - sha256: b300557c0382478cf661ddb520263508e4b3b5871b471410450ef2846e8c352c - md5: efba281bbdae5f6b0a1d53c6d4a97c93 - depends: - - __linux - - ptyprocess - - python >=3.8 - - tornado >=6.1.0 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/terminado?source=hash-mapping - size: 22452 - timestamp: 1710262728753 -- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - sha256: 4daae56fc8da17784578fbdd064f17e3b3076b394730a14119e571707568dc8a - md5: 00b54981b923f5aefcd5e8547de056d5 - depends: - - __osx - - ptyprocess - - python >=3.8 - - tornado >=6.1.0 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/terminado?source=hash-mapping - size: 22717 - timestamp: 1710265922593 -- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh5737063_0.conda - sha256: 8cb078291fd7882904e3de594d299c8de16dd3af7405787fce6919a385cfc238 - md5: 4abd500577430a942a995fd0d09b76a2 - depends: - - __win - - python >=3.8 - - pywinpty >=1.1.0 - - tornado >=6.1.0 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/terminado?source=hash-mapping - size: 22883 - timestamp: 1710262943966 -- conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - sha256: cad582d6f978276522f84bd209a5ddac824742fe2d452af6acf900f8650a73a2 - md5: f1acf5fdefa8300de697982bcb1761c9 - depends: - - python >=3.5 - - webencodings >=0.4 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/tinycss2?source=hash-mapping - size: 28285 - timestamp: 1729802975370 +- pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + name: terminado + version: 0.18.1 + sha256: a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0 + requires_dist: + - ptyprocess ; os_name != 'nt' + - pywinpty>=1.1.0 ; os_name == 'nt' + - tornado>=6.1.0 + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx ; extra == 'docs' + - pre-commit ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - mypy~=1.6 ; extra == 'typing' + - traitlets>=5.11.1 ; extra == 'typing' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + name: tinycss2 + version: 1.4.0 + sha256: 3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289 + requires_dist: + - webencodings>=0.4 + - sphinx ; extra == 'doc' + - sphinx-rtd-theme ; extra == 'doc' + - pytest ; extra == 'test' + - ruff ; extra == 'test' + requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda sha256: a84ff687119e6d8752346d1d408d5cf360dee0badd487a472aa8ddedfdc219e1 md5: a0116df4f4ed05c303811a837d5b39d8 @@ -10304,210 +10032,126 @@ packages: - __osx >=11.0 - libzlib >=1.3.1,<2.0a0 license: TCL - license_family: BSD - purls: [] - size: 3125538 - timestamp: 1748388189063 -- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda - sha256: e3614b0eb4abcc70d98eae159db59d9b4059ed743ef402081151a948dce95896 - md5: ebd0e761de9aa879a51d22cc721bd095 - depends: - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - license: TCL - license_family: BSD - purls: [] - size: 3466348 - timestamp: 1748388121356 -- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - sha256: 040a5a05c487647c089ad5e05ad5aff5942830db2a4e656f1e300d73436436f1 - md5: 30a0a26c8abccf4b7991d590fe17c699 - depends: - - python >=3.9 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/tomli?source=compressed-mapping - size: 21238 - timestamp: 1753796677376 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py311h49ec1c0_1.conda - sha256: b1d686806d6b913e42aadb052b12d9cc91aae295640df3acfef645142fc33b3d - md5: 18a98f4444036100d78b230c94453ff4 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 868049 - timestamp: 1756855060036 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py313h07c4f96_1.conda - sha256: c8bfe883aa2d5b59cb1d962729a12b3191518f7decbe9e3505c2aacccb218692 - md5: 45821154b9cb2fb63c2b354c76086954 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 877215 - timestamp: 1756855010312 -- conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.2-py311h13e5629_1.conda - sha256: 397226b6314aec2b076294816c941c1e9b7e3bbcf7a2e9dc9ba08f3ac10b0590 - md5: 06fd6a712ee16b3e7998e8ee2e7fc8b1 - depends: - - __osx >=10.13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 869912 - timestamp: 1756855230920 -- conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.2-py313h585f44e_1.conda - sha256: 530ac0f1864dcdb81a3c802d2b01cd83ec9a288f01dc6404b3f538b2ec84c3b6 - md5: 3fa5548d42d026657a1cd8e4305cee9d - depends: - - __osx >=10.13 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 873301 - timestamp: 1756855040989 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py311h3696347_1.conda - sha256: 4941963a1f0046b2813bfbe4c2ded15cb0b0048436fe62237d69467e8c0e1692 - md5: 25833dd6cb94341239aec42dd5370c33 - depends: - - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 870380 - timestamp: 1756855179889 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py313hcdf3177_1.conda - sha256: 30fbb92cc119595e4ac7691789d45d367f5d6850103b97ca4a130d98e8ec27f0 - md5: 728311ebaa740a1efa6fab80bbcdf335 - depends: - - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 874955 - timestamp: 1756855212446 -- conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.2-py311h3485c13_1.conda - sha256: 87527996d1297442bbc432369a5791af740762c1dda642d52cd55d32d5577937 - md5: ec9179a7226659bd15d8085c8de15360 - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=compressed-mapping - size: 871179 - timestamp: 1756855104869 -- conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.2-py313h5ea7bf4_1.conda - sha256: ec4d9ef994018acc59d83e1024f7b08a3e8f2d917ec42d6db873aaf6102fec3c - md5: ae40ad307ecb3181694714a40002af6c + license_family: BSD + purls: [] + size: 3125538 + timestamp: 1748388189063 +- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda + sha256: e3614b0eb4abcc70d98eae159db59d9b4059ed743ef402081151a948dce95896 + md5: ebd0e761de9aa879a51d22cc721bd095 depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=compressed-mapping - size: 876511 - timestamp: 1756855260509 -- conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - sha256: f39a5620c6e8e9e98357507262a7869de2ae8cc07da8b7f84e517c9fd6c2b959 - md5: 019a7385be9af33791c989871317e1ed - depends: - - python >=3.9 - license: BSD-3-Clause + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: TCL license_family: BSD - purls: - - pkg:pypi/traitlets?source=hash-mapping - size: 110051 - timestamp: 1733367480074 -- pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl + purls: [] + size: 3466348 + timestamp: 1748388121356 +- pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + name: tokenize-rt + version: 6.2.0 + sha256: a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl + name: tomli + version: 2.3.0 + sha256: 4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl + name: tomli + version: 2.3.0 + sha256: 883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl + name: tomli + version: 2.3.0 + sha256: 5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: tomli + version: 2.3.0 + sha256: a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl + name: tomli + version: 2.3.0 + sha256: 0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl + name: tomli + version: 2.3.0 + sha256: 88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: tomli + version: 2.3.0 + sha256: 4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl + name: tomli + version: 2.3.0 + sha256: be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl + name: tornado + version: 6.5.2 + sha256: e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl + name: tornado + version: 6.5.2 + sha256: 583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl + name: tornado + version: 6.5.2 + sha256: 2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: tornado + version: 6.5.2 + sha256: e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + name: traitlets + version: 5.14.3 + sha256: b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f + requires_dist: + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx ; extra == 'docs' + - argcomplete>=3.0.3 ; extra == 'test' + - mypy>=1.7.0 ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-mypy-testing ; extra == 'test' + - pytest>=7.0,<8.2 ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + name: trove-classifiers + version: 2025.9.11.17 + sha256: 5d392f2d244deb1866556457d6f3516792124a23d1c3a463a2e8668a5d1c15dd +- pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl name: typer - version: 0.17.4 - sha256: 015534a6edaa450e7007eba705d5c18c3349dcea50a6ad79a5ed530967575824 + version: 0.19.2 + sha256: 755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9 requires_dist: - click>=8.0.0 - typing-extensions>=3.7.4.3 - shellingham>=1.3.0 - rich>=10.11.0 - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - sha256: dfdf6e3dea87c873a86cfa47f7cba6ffb500bad576d083b3de6ad1b17e1a59c3 - md5: 5e9220c892fe069da8de2b9c63663319 - depends: - - python >=3.10 - license: Apache-2.0 AND MIT - purls: - - pkg:pypi/types-python-dateutil?source=hash-mapping - size: 24939 - timestamp: 1755865615651 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c - md5: edd329d7d3a4ab45dcf905899a7a6115 - depends: - - typing_extensions ==4.15.0 pyhcf101f3_0 - license: PSF-2.0 - license_family: PSF - purls: [] - size: 91383 - timestamp: 1756220668932 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 - md5: 0caa1af407ecff61170c9437a808404d - depends: - - python >=3.10 - - python - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/typing-extensions?source=compressed-mapping - size: 51692 - timestamp: 1756220668932 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - sha256: 3088d5d873411a56bf988eee774559335749aed6f6c28e07bf933256afb9eb6c - md5: f6d7aa696c67756a650e91e15e88223c - depends: - - python >=3.9 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/typing-utils?source=hash-mapping - size: 15183 - timestamp: 1733331395943 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + name: types-python-dateutil + version: 2.9.0.20251008 + sha256: b9a5232c8921cf7661b29c163ccc56055c418ab2c6eabe8f917cbcc73a4c4157 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + name: typing-extensions + version: 4.15.0 + sha256: f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl name: tzdata version: '2025.2' @@ -10545,51 +10189,77 @@ packages: - python-docs-theme ; extra == 'doc' - uncertainties[arrays,doc,test] ; extra == 'all' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - sha256: e0eb6c8daf892b3056f08416a96d68b0a358b7c46b99c8a50481b22631a4dfc0 - md5: e7cb0f5745e4c5035a460248334af7eb - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/uri-template?source=hash-mapping - size: 23990 - timestamp: 1733323714454 -- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - sha256: 4fb9789154bd666ca74e428d973df81087a697dbb987775bc3198d2215f240f8 - md5: 436c165519e140cb08d246a4472a9d6a - depends: - - brotli-python >=1.0.9 - - h2 >=4,<5 - - pysocks >=1.5.6,<2.0,!=1.5.7 - - python >=3.9 - - zstandard >=0.18.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/urllib3?source=hash-mapping - size: 101735 - timestamp: 1750271478254 -- pypi: https://files.pythonhosted.org/packages/1a/c4/0082f437bac162ab95e5a3a389a184c122d45eb5593960aab92fdf80374b/uv-0.8.17-py3-none-win_amd64.whl +- 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 + sha256: a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363 + requires_dist: + - types-pyyaml ; extra == 'dev' + - mypy ; extra == 'dev' + - flake8 ; extra == 'dev' + - flake8-annotations ; extra == 'dev' + - flake8-bandit ; extra == 'dev' + - flake8-bugbear ; extra == 'dev' + - flake8-commas ; extra == 'dev' + - flake8-comprehensions ; extra == 'dev' + - flake8-continuation ; extra == 'dev' + - flake8-datetimez ; extra == 'dev' + - flake8-docstrings ; extra == 'dev' + - flake8-import-order ; extra == 'dev' + - flake8-literal ; extra == 'dev' + - flake8-modern-annotations ; extra == 'dev' + - flake8-noqa ; extra == 'dev' + - flake8-pyproject ; extra == 'dev' + - flake8-requirements ; extra == 'dev' + - flake8-typechecking-import ; extra == 'dev' + - flake8-use-fstring ; extra == 'dev' + - pep8-naming ; extra == 'dev' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + name: urllib3 + version: 2.5.0 + sha256: e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc + requires_dist: + - brotli>=1.0.9 ; platform_python_implementation == 'CPython' and extra == 'brotli' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'brotli' + - h2>=4,<5 ; extra == 'h2' + - pysocks>=1.5.6,!=1.5.7,<2.0 ; extra == 'socks' + - zstandard>=0.18.0 ; extra == 'zstd' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/16/25/6df8be6cd549200e80d19374579689fda39b18735afde841345284fb113d/uv-0.9.3-py3-none-macosx_11_0_arm64.whl name: uv - version: 0.8.17 - sha256: cf85b84b81b41d57a9b6eeded8473ec06ace8ee959ad0bb57e102b5ad023bd34 + version: 0.9.3 + sha256: 741e80c4230e1b9a5d0869aca2fb082b3832b251ef61537bc9278364b8e74df2 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/65/34/609b72034df0c62bcfb0c0ad4b11e2b55e537c0f0817588b5337d3dcca71/uv-0.8.17-py3-none-macosx_10_12_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/82/45/488417c6c0127c00bcdfac3556ae2ea0597df8245fe5f9bcfda35ebdbe85/uv-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: uv - version: 0.8.17 - sha256: c28fba6d7bb5c34ade2c8da5000faebe8425a287f42a043ca01ceb24ebc81590 + version: 0.9.3 + sha256: 73ae4bbc7d555ba1738da08c64b55f21ab0ea0ff85636708cebaf460d98a440d requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/8f/35/cb47d2d07a383c07b0e5043c6fe5555f0fd79683c6d7f9760222987c8be9/uv-0.8.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/87/cd/667f6249a9a3a8d2d7ba1aa72db6b1fc6cdaf7b0d7aeda43478702e2a13e/uv-0.9.3-py3-none-win_amd64.whl name: uv - version: 0.8.17 - sha256: b6d30d02fb65193309fc12a20f9e1a9fab67f469d3e487a254ca1145fd06788f + version: 0.9.3 + sha256: 55516bf85c44a00b948472ddda80d7c5cd9990e9b5c085dc5005da93f40266a7 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/b6/bc/9417df48f0c18a9d54c2444096e03f2f56a3534c5b869f50ac620729cbc8/uv-0.8.17-py3-none-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/d0/1a/8e68d0020c29f6f329a265773c23b0c01e002794ea884b8bdbd594c7ea97/uv-0.9.3-py3-none-macosx_10_12_x86_64.whl name: uv - version: 0.8.17 - sha256: b009f1ec9e28de00f76814ad66e35aaae82c98a0f24015de51943dcd1c2a1895 + version: 0.9.3 + sha256: 596a982c5a061d58412824a2ebe2960b52db23f1b1658083ba9c0e7ae390308a + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl + name: validate-pyproject + version: 0.24.1 + sha256: b7b05fa9117204c9c4606ab317acd095b18d1bfc78fb7dc8cc06f77d0582ca2d + requires_dist: + - fastjsonschema>=2.16.2,<=3 + - packaging>=24.2 ; extra == 'all' + - trove-classifiers>=2021.10.20 ; extra == 'all' + - tomli>=1.2.1 ; python_full_version < '3.11' and extra == 'all' + - validate-pyproject-schema-store ; extra == 'store' requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl name: varname @@ -10601,87 +10271,151 @@ packages: - pure-eval==0.* ; extra == 'all' - typing-extensions>=4.13,<5.0 ; python_full_version < '3.10' requires_python: '>=3.8,<4.0' -- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_31.conda - sha256: cb357591d069a1e6cb74199a8a43a7e3611f72a6caed9faa49dbb3d7a0a98e0b - md5: 28f4ca1e0337d0f27afb8602663c5723 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h2b53caa_32.conda + sha256: 82250af59af9ff3c6a635dd4c4764c631d854feb334d6747d356d949af44d7cf + md5: ef02bbe151253a72b8eda264a935db66 depends: - - vc14_runtime >=14.44.35208 + - vc14_runtime >=14.42.34433 track_features: - vc14 license: BSD-3-Clause license_family: BSD purls: [] - size: 18249 - timestamp: 1753739241465 -- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_31.conda - sha256: af4b4b354b87a9a8d05b8064ff1ea0b47083274f7c30b4eb96bc2312c9b5f08f - md5: 603e41da40a765fd47995faa021da946 + size: 18861 + timestamp: 1760418772353 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda + sha256: e3a3656b70d1202e0d042811ceb743bd0d9f7e00e2acdf824d231b044ef6c0fd + md5: 378d5dcec45eaea8d303da6f00447ac0 depends: - ucrt >=10.0.20348.0 - - vcomp14 14.44.35208 h818238b_31 + - vcomp14 14.44.35208 h818238b_32 constrains: - - vs2015_runtime 14.44.35208.* *_31 + - vs2015_runtime 14.44.35208.* *_32 license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime license_family: Proprietary purls: [] - size: 682424 - timestamp: 1753739239305 -- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_31.conda - sha256: 67b317b64f47635415776718d25170a9a6f9a1218c0f5a6202bfd687e07b6ea4 - md5: a6b1d5c1fc3cb89f88f7179ee6a9afe3 + size: 682706 + timestamp: 1760418629729 +- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda + sha256: f3790c88fbbdc55874f41de81a4237b1b91eab75e05d0e58661518ff04d2a8a1 + md5: 58f67b437acbf2764317ba273d731f1d depends: - ucrt >=10.0.20348.0 constrains: - - vs2015_runtime 14.44.35208.* *_31 + - vs2015_runtime 14.44.35208.* *_32 license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime license_family: Proprietary purls: [] - size: 113963 - timestamp: 1753739198723 -- conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - sha256: f21e63e8f7346f9074fd00ca3b079bd3d2fa4d71f1f89d5b6934bf31446dc2a5 - md5: b68980f2495d096e71c7fd9d7ccf63e6 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/wcwidth?source=hash-mapping - size: 32581 - timestamp: 1733231433877 -- conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - sha256: 08315dc2e61766a39219b2d82685fc25a56b2817acf84d5b390176080eaacf99 - md5: b49f7b291e15494aafb0a7d74806f337 - depends: - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/webcolors?source=hash-mapping - size: 18431 - timestamp: 1733359823938 -- conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - sha256: 19ff205e138bb056a46f9e3839935a2e60bd1cf01c8241a5e172a422fed4f9c6 - md5: 2841eb5bfc75ce15e9a0054b98dcd64d - depends: - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/webencodings?source=hash-mapping - size: 15496 - timestamp: 1733236131358 -- conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - sha256: 1dd84764424ffc82030c19ad70607e6f9e3b9cb8e633970766d697185652053e - md5: 84f8f77f0a9c6ef401ee96611745da8f - depends: - - python >=3.9 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/websocket-client?source=hash-mapping - size: 46718 - timestamp: 1733157432924 + size: 114846 + timestamp: 1760418593847 +- pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + name: versioningit + version: 3.3.0 + sha256: 23b1db3c4756cded9bd6b0ddec6643c261e3d0c471707da3e0b230b81ce53e4b + requires_dist: + - importlib-metadata>=3.6 ; python_full_version < '3.10' + - packaging>=17.1 + - tomli>=1.2,<3.0 ; python_full_version < '3.11' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + name: virtualenv + version: 20.35.3 + sha256: 63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a + requires_dist: + - distlib>=0.3.7,<1 + - filelock>=3.12.2,<4 + - importlib-metadata>=6.6 ; python_full_version < '3.8' + - platformdirs>=3.9.1,<5 + - typing-extensions>=4.13.2 ; python_full_version < '3.11' + - furo>=2023.7.26 ; extra == 'docs' + - proselint>=0.13 ; extra == 'docs' + - sphinx>=7.1.2,!=7.3 ; extra == 'docs' + - sphinx-argparse>=0.4 ; extra == 'docs' + - sphinxcontrib-towncrier>=0.2.1a0 ; extra == 'docs' + - towncrier>=23.6 ; extra == 'docs' + - covdefaults>=2.3 ; extra == 'test' + - coverage-enable-subprocess>=1 ; extra == 'test' + - coverage>=7.2.7 ; extra == 'test' + - flaky>=3.7 ; extra == 'test' + - packaging>=23.1 ; extra == 'test' + - pytest-env>=0.8.2 ; extra == 'test' + - pytest-freezer>=0.4.8 ; (python_full_version >= '3.13' and platform_python_implementation == 'CPython' and sys_platform == 'win32' and extra == 'test') or (platform_python_implementation == 'GraalVM' and extra == 'test') or (platform_python_implementation == 'PyPy' and extra == 'test') + - pytest-mock>=3.11.1 ; extra == 'test' + - pytest-randomly>=3.12 ; extra == 'test' + - pytest-timeout>=2.1 ; extra == 'test' + - pytest>=7.4 ; extra == 'test' + - setuptools>=68 ; extra == 'test' + - time-machine>=2.10 ; platform_python_implementation == 'CPython' and extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl + name: watchdog + version: 6.0.0 + sha256: ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2 + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl + name: watchdog + version: 6.0.0 + sha256: 76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134 + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl + name: watchdog + version: 6.0.0 + sha256: 20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2 + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl + name: watchdog + version: 6.0.0 + sha256: afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl + name: watchdog + version: 6.0.0 + sha256: cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680 + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl + name: watchdog + version: 6.0.0 + sha256: a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + name: wcwidth + version: 0.2.14 + sha256: a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1 + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + name: webcolors + version: 24.11.1 + sha256: 515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + name: webencodings + version: 0.5.1 + sha256: a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 +- pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl + name: websocket-client + version: 1.9.0 + sha256: af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef + requires_dist: + - pytest ; extra == 'test' + - websockets ; extra == 'test' + - python-socks ; extra == 'optional' + - wsaccel ; extra == 'optional' + - sphinx>=6.0 ; extra == 'docs' + - sphinx-rtd-theme>=1.1.0 ; extra == 'docs' + - myst-parser>=2.0.0 ; extra == 'docs' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda sha256: 1b34021e815ff89a4d902d879c3bd2040bc1bd6169b32e9427497fa05c55f1ce md5: 75cb7132eb58d97896e173ef12ac9986 @@ -10693,24 +10427,6 @@ packages: - pkg:pypi/wheel?source=hash-mapping size: 62931 timestamp: 1733130309598 -- conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - sha256: 93807369ab91f230cf9e6e2a237eaa812492fe00face5b38068735858fba954f - md5: 46e441ba871f524e2b067929da3051c2 - depends: - - __win - - python >=3.9 - license: LicenseRef-Public-Domain - purls: - - pkg:pypi/win-inet-pton?source=hash-mapping - size: 9555 - timestamp: 1733130678956 -- conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 - sha256: 9df10c5b607dd30e05ba08cbd940009305c75db242476f4e845ea06008b0a283 - md5: 1cee351bf20b830d991dbe0bc8cd7dfe - license: MIT - license_family: MIT - purls: [] - size: 1176306 - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl name: wsproto version: 1.2.0 @@ -10735,372 +10451,75 @@ packages: - coverage ; extra == 'test' - xraydb[dev,doc,test] ; extra == 'all' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - sha256: 6d9ea2f731e284e9316d95fa61869fe7bbba33df7929f82693c121022810f4ad - md5: a77f85f77be52ff59391544bfe73390a - depends: - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - license: MIT - license_family: MIT - purls: [] - size: 85189 - timestamp: 1753484064210 -- conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - sha256: a335161bfa57b64e6794c3c354e7d49449b28b8d8a7c4ed02bf04c3f009953f9 - md5: a645bb90997d3fc2aea0adf6517059bd - depends: - - __osx >=10.13 - license: MIT - license_family: MIT - purls: [] - size: 79419 - timestamp: 1753484072608 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - sha256: b03433b13d89f5567e828ea9f1a7d5c5d697bf374c28a4168d71e9464f5dafac - md5: 78a0fe9e9c50d2c381e8ee47e3ea437d - depends: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: [] - size: 83386 - timestamp: 1753484079473 -- conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - sha256: 80ee68c1e7683a35295232ea79bcc87279d31ffeda04a1665efdb43cbd50a309 - md5: 433699cba6602098ae8957a323da2664 - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - license: MIT - license_family: MIT - purls: [] - size: 63944 - timestamp: 1753484092156 -- pypi: https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl name: yarl - version: 1.20.1 - sha256: 98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8 + version: 1.22.0 + sha256: 01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: yarl - version: 1.20.1 - sha256: d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b + version: 1.22.0 + sha256: bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl name: yarl - version: 1.20.1 - sha256: d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5 + version: 1.22.0 + sha256: 22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: yarl - version: 1.20.1 - sha256: 14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3 + version: 1.22.0 + sha256: 4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl name: yarl - version: 1.20.1 - sha256: 495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c + version: 1.22.0 + sha256: 669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6 requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl name: yarl - version: 1.20.1 - sha256: f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7 + version: 1.22.0 + sha256: 47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl name: yarl - version: 1.20.1 - sha256: 7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b + version: 1.22.0 + sha256: 078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl name: yarl - version: 1.20.1 - sha256: 26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e + version: 1.22.0 + sha256: 792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028 requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda - sha256: 47cfe31255b91b4a6fa0e9dbaf26baa60ac97e033402dbc8b90ba5fee5ffe184 - md5: 8035e5b54c08429354d5d64027041cad - depends: - - libstdcxx >=14 - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libsodium >=1.0.20,<1.0.21.0a0 - - krb5 >=1.21.3,<1.22.0a0 - license: MPL-2.0 - license_family: MOZILLA - purls: [] - size: 310648 - timestamp: 1757370847287 -- conda: https://conda.anaconda.org/conda-forge/osx-64/zeromq-4.3.5-h6c33b1e_9.conda - sha256: 30aa5a2e9c7b8dbf6659a2ccd8b74a9994cdf6f87591fcc592970daa6e7d3f3c - md5: d940d809c42fbf85b05814c3290660f5 - depends: - - __osx >=10.13 - - libcxx >=19 - - libsodium >=1.0.20,<1.0.21.0a0 - - krb5 >=1.21.3,<1.22.0a0 - license: MPL-2.0 - license_family: MOZILLA - purls: [] - size: 259628 - timestamp: 1757371000392 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h888dc83_9.conda - sha256: b6f9c130646e5971f6cad708e1eee278f5c7eea3ca97ec2fdd36e7abb764a7b8 - md5: 26f39dfe38a2a65437c29d69906a0f68 - depends: - - __osx >=11.0 - - libcxx >=19 - - libsodium >=1.0.20,<1.0.21.0a0 - - krb5 >=1.21.3,<1.22.0a0 - license: MPL-2.0 - license_family: MOZILLA - purls: [] - size: 244772 - timestamp: 1757371008525 -- conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h5bddc39_9.conda - sha256: 690cf749692c8ea556646d1a47b5824ad41b2f6dfd949e4cdb6c44a352fcb1aa - md5: a6c8f8ee856f7c3c1576e14b86cd8038 - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - libsodium >=1.0.20,<1.0.21.0a0 - - krb5 >=1.21.3,<1.22.0a0 - license: MPL-2.0 - license_family: MOZILLA - purls: [] - size: 265212 - timestamp: 1757370864284 -- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - sha256: 7560d21e1b021fd40b65bfb72f67945a3fcb83d78ad7ccf37b8b3165ec3b68ad - md5: df5e78d904988eb55042c0c97446079f - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/zipp?source=hash-mapping - size: 22963 - timestamp: 1749421737203 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py311haee01d2_0.conda - sha256: ed149760ea78e038e6424d8a327ea95da351727536c0e9abedccf5a61fc19932 - md5: 0fd242142b0691eb9311dc32c1d4ab76 - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python_abi 3.11.* *_cp311 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 466651 - timestamp: 1757930101225 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py313h54dd161_0.conda - sha256: 9d79d176afe50361cc3fd4366bedff20852dbea1e5b03f358b55f12aca22d60d - md5: 1fe43bd1fc86e22ad3eb0edec637f8a2 - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 471152 - timestamp: 1757930114245 -- conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py311h62e9434_0.conda - sha256: be241ea3ca603d68654beeab4c991c225c9361378a107f72c2433ddfdff88132 - md5: 5425495af6b0b010230320d618022f20 - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - __osx >=10.13 - - python_abi 3.11.* *_cp311 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 462903 - timestamp: 1757930157317 -- conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py313hcb05632_0.conda - sha256: 1df6717571a177a135622399708878c84620be5bd9a72dc67814486595ecb509 - md5: 7fbc3b11a6c969de321061575594eba7 - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - __osx >=10.13 - - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 469089 - timestamp: 1757930140546 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py311h5bb9006_0.conda - sha256: fb1443a1479a6d709b4af7a2cdcea3ef2a5e859378de19814086fa86ca6f934e - md5: c7e0f1b714bd12d39899a4f0c296dd86 - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - __osx >=11.0 - - python 3.11.* *_cpython - - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 390089 - timestamp: 1757930124840 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py313h9734d34_0.conda - sha256: 8a1bf8a66c05f724e8a56cb1918eae70bcb467a7c5d43818e37e04d86332c513 - md5: ce17795bf104a29a2c7ed0bba7a804cb - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - __osx >=11.0 - - python 3.13.* *_cp313 - - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 396477 - timestamp: 1757930170468 -- conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py311hf893f09_0.conda - sha256: 3b66d3cb738a9993e8432d1a03402d207128166c4ef0c928e712958e51aff325 - md5: d26077d20b4bba54f4c718ed1313805f - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 375866 - timestamp: 1757930134099 -- conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py313h5fd188c_0.conda - sha256: d20a163a466621e41c3d06bc5dc3040603b8b0bfa09f493e2bfc0dbd5aa6e911 - md5: edfe43bb6e955d4d9b5e7470ea92dead - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.13.* *_cp313 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 380849 - timestamp: 1757930139118 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda - sha256: a4166e3d8ff4e35932510aaff7aa90772f84b4d07e9f6f83c614cba7ceefe0eb - md5: 6432cb5d4ac0046c3ac0a8a0f95842f9 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 567578 - timestamp: 1742433379869 -- conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda - sha256: c171c43d0c47eed45085112cb00c8c7d4f0caa5a32d47f2daca727e45fb98dca - md5: cd60a4a5a8d6a476b30d8aa4bb49251a - depends: - - __osx >=10.13 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 485754 - timestamp: 1742433356230 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda - sha256: 0d02046f57f7a1a3feae3e9d1aa2113788311f3cf37a3244c71e61a93177ba67 - md5: e6f69c7bcccdefa417f056fa593b40f0 - depends: - - __osx >=11.0 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 399979 - timestamp: 1742433432699 -- conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda - sha256: bc64864377d809b904e877a98d0584f43836c9f2ef27d3d2a1421fa6eae7ca04 - md5: 21f56217d6125fb30c3c3f10c786d751 - depends: - - libzlib >=1.3.1,<2.0a0 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 354697 - timestamp: 1742433568506 diff --git a/pixi.toml b/pixi.toml index bcc2feb8..da1fc721 100644 --- a/pixi.toml +++ b/pixi.toml @@ -2,7 +2,7 @@ # ENVIRONMENT VARIABLES ####################### -# Platform-independent +# Platform-independent [activation.env] PYTHONIOENCODING = "utf-8" @@ -16,11 +16,11 @@ PYTHONIOENCODING = "utf-8" # Unix/macOS [target.unix.activation.env] -PYTHONPATH = "src${PYTHONPATH:+:${PYTHONPATH}}" # remove ":" if needed +PYTHONPATH = "${PIXI_PROJECT_ROOT}/src${PYTHONPATH:+:${PYTHONPATH}}" # Windows [target.win.activation.env] -PYTHONPATH = "src;%PYTHONPATH%" +PYTHONPATH = "${PIXI_PROJECT_ROOT}/src;%PYTHONPATH%" ########### # WORKSPACE @@ -40,16 +40,19 @@ channels = ['conda-forge'] # Default feature configuration [dependencies] # == [feature.default.dependencies] -pip = '*' # Required to install from PyPI -jupyterlab = '*' # JupyterLab for notebooks +pip = '*' # Required to install from PyPI [target.win-64.dependencies] libcblas = '*' # CBLAS library for linear algebra; required for pdffit2. [pypi-dependencies] # == [feature.default.pypi-dependencies] -pixi-kernel = '*' # Pixi Jupyter kernel -uv = '*' # Python package manager -easydiffraction = { version = '*', extras = ['visualization'] } # Main package +jupyterlab = '*' # JupyterLab for notebooks +pixi-kernel = '*' # Pixi Jupyter kernel +uv = '*' # Python package manager +easydiffraction = { version = '*', extras = ['all'] } # Main package + +[target.osx.pypi-dependencies] +h5py = "==3.14.0" # macOS only: avoid broken 3.15.0 wheel # Features for specific Python versions @@ -93,6 +96,7 @@ unit-tests = 'python -m pytest tests/unit/ --color=yes -v' func-tests = 'python -m pytest tests/functional/ --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' test = { depends-on = ['unit-tests'] } @@ -125,7 +129,8 @@ check = { depends-on = [ ### 🛠️ 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 $(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' @@ -184,11 +189,12 @@ docs-setup = { depends-on = [ 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 = "uv pip install --editable '.[all]'" +dev-install = "python -m uv pip install --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', @@ -199,7 +205,5 @@ dev = { depends-on = [ wheel = { depends-on = ['npm-config', 'prettier-install'] } -## 🔗 Shortcuts +## 🔗 Main Package Shortcut easydiffraction = 'python -m easydiffraction' -tutorials-list = 'python -m easydiffraction list-tutorials' -tutorials-fetch = 'python -m easydiffraction fetch-tutorials' diff --git a/pyproject.toml b/pyproject.toml index 45b0beea..bfe639e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,8 @@ dependencies = [ 'cryspy', # Calculations of diffraction patterns 'diffpy.pdffit2', # Calculations of Pair Distribution Function (PDF), Python >=3.11,<3.14 'diffpy.utils', # Utilities for PDF calculations + 'uncertainties', # Propagation of uncertainties + 'typeguard', # Runtime type checking ] [project.optional-dependencies] @@ -65,7 +67,7 @@ docs = [ 'mkdocs', # Static site generator 'mkdocs-material', # Documentation framework on top of MkDocs 'mkdocs-autorefs<1.3.0', # MkDocs: Auto-references support. 1.3.0 => DeprecationWarning: Setting a fallback anchor function is deprecated and ... - 'mkdocs-jupyter', # MkDocs: Jupyter notebook support + 'mkdocs-jupyter', # MkDocs: Jupyter notebook support. 'mkdocs-plugin-inline-svg', # MkDocs: Inline SVG support 'mkdocs-markdownextradata-plugin', # MkDocs: Markdown extra data support, such as global variables 'mkdocstrings-python', # MkDocs: Python docstring support @@ -78,9 +80,9 @@ visualization = [ 'py3Dmol', # Visualisation of crystal structures ] all = [ - "easydiffraction[dev]", - "easydiffraction[docs]", - "easydiffraction[visualization]", + 'easydiffraction[dev]', + 'easydiffraction[docs]', + 'easydiffraction[visualization]', ] [project.urls] @@ -160,8 +162,9 @@ close-quotes-on-newline = true # https://interrogate.readthedocs.io/en/latest/ [tool.interrogate] -fail-under = 35 # Temporarily reduce to allow gradual improvement +fail-under = 35 # Temporarily reduce to allow gradual improvement verbose = 1 +exclude = ["src/**/__init__.py"] ####################################### # Configuration for coverage/pytest-cov @@ -169,12 +172,12 @@ verbose = 1 [tool.coverage.run] branch = true # Measure branch coverage as well -source = ["src/easydiffraction"] # Limit coverage to the source code directory +source = ['src/easydiffraction'] # 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 = 58 # Temporarily reduce to allow gradual improvement +fail_under = 65 # Temporarily reduce to allow gradual improvement ######################## # Configuration for ruff @@ -185,7 +188,7 @@ fail_under = 58 # Temporarily reduce to allow gradual improvement [tool.ruff] # Temporarily exclude some directories until we have improved the code quality there -exclude = ['tests', 'tutorials-drafts'] +exclude = ['tests', 'tmp'] indent-width = 4 line-length = 99 # Enable new rules that are not yet stable, like DOC @@ -247,10 +250,10 @@ ignore = [ 'DTZ005', # Ignore: `datetime.datetime.now()` called without a `tz` argument ] -# Temporarily increase McCabe complexity limit to 17 to allow +# Temporarily increase McCabe complexity limit to 19 to allow # refactoring in smaller steps. [tool.ruff.lint.mccabe] -max-complexity = 17 # default is 10 +max-complexity = 19 # default is 10 [tool.ruff.lint.flake8-tidy-imports] ban-relative-imports = 'all' @@ -262,11 +265,11 @@ force-single-line = true '*test_*.py' = ['S101'] # allow asserts in test files [tool.ruff.lint.pycodestyle] -max-line-length = 99 # https://peps.python.org/pep-0008/#maximum-line-length -max-doc-length = 72 # https://peps.python.org/pep-0008/#maximum-line-length +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 [tool.ruff.lint.pydocstyle] -convention = "google" +convention = 'google' [tool.ruff.format] docstring-code-format = true # Whether to format code snippets in docstrings diff --git a/pytest.ini b/pytest.ini index 9aae88b7..bb1f5ba8 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,10 @@ [pytest] +addopts = --import-mode=importlib markers = fast: mark test as fast (should be run on every push) + functional: mark test as functional (slow/integration; opt-in) +testpaths = + tests +filterwarnings = + ignore::DeprecationWarning:cryspy\. + ignore:.*scipy\.misc is deprecated.*:DeprecationWarning diff --git a/src/easydiffraction/__init__.py b/src/easydiffraction/__init__.py index c2ace01e..ac7a581c 100644 --- a/src/easydiffraction/__init__.py +++ b/src/easydiffraction/__init__.py @@ -1,128 +1,42 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause -from typing import TYPE_CHECKING - -# This is needed for static type checkers like mypy and IDEs to -# recognize the imports without actually importing them at runtime, -# which helps avoid circular dependencies and reduces initial load time. -if TYPE_CHECKING: - # Analysis - from easydiffraction.analysis.analysis import Analysis - - # Experiments - from easydiffraction.experiments.experiment import Experiment - from easydiffraction.experiments.experiments import Experiments - - # Project management - from easydiffraction.project import Project - from easydiffraction.project import ProjectInfo - - # Sample model - from easydiffraction.sample_models.sample_model import SampleModel - from easydiffraction.sample_models.sample_models import SampleModels - - # Summary - from easydiffraction.summary import Summary - - # Utils - from easydiffraction.utils.formatting import chapter - from easydiffraction.utils.formatting import paragraph - from easydiffraction.utils.formatting import section - from easydiffraction.utils.utils import download_from_repository - from easydiffraction.utils.utils import fetch_tutorials - from easydiffraction.utils.utils import get_value_from_xye_header - from easydiffraction.utils.utils import list_tutorials - from easydiffraction.utils.utils import show_version - - -# Lazy loading of submodules and classes -# This improves initial import time and reduces memory usage -# when only a subset of functionality is needed. -def __getattr__(name): - if name == 'Analysis': - from easydiffraction.analysis.analysis import Analysis - - return Analysis - elif name == 'Experiment': - from easydiffraction.experiments.experiment import Experiment - - return Experiment - elif name == 'Experiments': - from easydiffraction.experiments.experiments import Experiments - - return Experiments - elif name == 'Project': - from easydiffraction.project import Project - - return Project - elif name == 'ProjectInfo': - from easydiffraction.project import ProjectInfo - - return ProjectInfo - elif name == 'SampleModel': - from easydiffraction.sample_models.sample_model import SampleModel - - return SampleModel - elif name == 'SampleModels': - from easydiffraction.sample_models.sample_models import SampleModels - - return SampleModels - elif name == 'Summary': - from easydiffraction.summary import Summary - - return Summary - elif name == 'chapter': - from easydiffraction.utils.formatting import chapter - - return chapter - elif name == 'section': - from easydiffraction.utils.formatting import section - - return section - elif name == 'paragraph': - from easydiffraction.utils.formatting import paragraph - - return paragraph - elif name == 'download_from_repository': - from easydiffraction.utils.utils import download_from_repository - - return download_from_repository - elif name == 'fetch_tutorials': - from easydiffraction.utils.utils import fetch_tutorials - - return fetch_tutorials - elif name == 'list_tutorials': - from easydiffraction.utils.utils import list_tutorials - - return list_tutorials - elif name == 'get_value_from_xye_header': - from easydiffraction.utils.utils import get_value_from_xye_header - - return get_value_from_xye_header - elif name == 'show_version': - from easydiffraction.utils.utils import show_version - - return show_version - raise AttributeError(f"module 'easydiffraction' has no attribute {name}") +from importlib import import_module + +from easydiffraction.utils.formatting import chapter +from easydiffraction.utils.formatting import paragraph +from easydiffraction.utils.formatting import section +from easydiffraction.utils.logging import Logger +from easydiffraction.utils.logging import log + +Logger.configure() + +_LAZY_ENTRIES = [ + ('easydiffraction.project.project', 'Project'), + ('easydiffraction.experiments.experiment.factory', 'ExperimentFactory'), + ('easydiffraction.sample_models.sample_model.factory', 'SampleModelFactory'), + ('easydiffraction.utils.utils', 'download_from_repository'), + ('easydiffraction.utils.utils', 'fetch_tutorials'), + ('easydiffraction.utils.utils', 'list_tutorials'), + ('easydiffraction.utils.utils', 'get_value_from_xye_header'), + ('easydiffraction.utils.utils', 'show_version'), +] +_LAZY_MAP = {attr_name: module_name for module_name, attr_name in _LAZY_ENTRIES} -# Expose the public API -__all__ = [ - 'Project', - 'ProjectInfo', - 'SampleModel', - 'SampleModels', - 'Experiment', - 'Experiments', - 'Analysis', - 'Summary', +__all__ = list(_LAZY_MAP.keys()) + [ + 'Logger', + 'log', 'chapter', 'section', 'paragraph', - 'download_from_repository', - 'fetch_tutorials', - 'list_tutorials', - 'get_value_from_xye_header', - 'show_version', ] + + +def __getattr__(name): + if name not in _LAZY_MAP: + raise AttributeError() + module_name = _LAZY_MAP[name] + module = import_module(module_name) + attr = getattr(module, name) + return attr diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index 700f21b6..b0bbc732 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -7,14 +7,14 @@ import pandas as pd -from easydiffraction.analysis.calculators.calculator_factory import CalculatorFactory -from easydiffraction.analysis.collections.aliases import Aliases -from easydiffraction.analysis.collections.constraints import Constraints -from easydiffraction.analysis.collections.joint_fit_experiments import JointFitExperiments -from easydiffraction.analysis.minimization import DiffractionMinimizer -from easydiffraction.analysis.minimizers.minimizer_factory import MinimizerFactory -from easydiffraction.core.objects import Descriptor -from easydiffraction.core.objects import Parameter +from easydiffraction.analysis.calculators.factory import CalculatorFactory +from easydiffraction.analysis.categories.aliases import Aliases +from easydiffraction.analysis.categories.constraints import Constraints +from easydiffraction.analysis.categories.joint_fit_experiments import JointFitExperiments +from easydiffraction.analysis.fitting import Fitter +from easydiffraction.analysis.minimizers.factory import MinimizerFactory +from easydiffraction.core.parameters import NumericDescriptor +from easydiffraction.core.parameters import Parameter from easydiffraction.core.singletons import ConstraintsHandler from easydiffraction.experiments.experiments import Experiments from easydiffraction.utils.formatting import paragraph @@ -24,9 +24,34 @@ class Analysis: + """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 sample models 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. + """ + _calculator = CalculatorFactory.create_calculator('cryspy') def __init__(self, project) -> None: + """Create a new Analysis instance bound to a project. + + Args: + project: The project that owns models and experiments. + """ self.project = project self.aliases = Aliases() self.constraints = Constraints() @@ -34,16 +59,16 @@ def __init__(self, project) -> None: self.calculator = Analysis._calculator # Default calculator shared by project self._calculator_key: str = 'cryspy' # Added to track the current calculator self._fit_mode: str = 'single' - self.fitter = DiffractionMinimizer('lmfit (leastsq)') + self.fitter = Fitter('lmfit (leastsq)') def _get_params_as_dataframe( self, - params: List[Union[Descriptor, Parameter]], + params: List[Union[NumericDescriptor, Parameter]], ) -> pd.DataFrame: """Convert a list of parameters to a DataFrame. Args: - params: List of Descriptor or Parameter objects. + params: List of DescriptorFloat or Parameter objects. Returns: A pandas DataFrame containing parameter information. @@ -51,11 +76,11 @@ def _get_params_as_dataframe( rows = [] for param in params: common_attrs = {} - if isinstance(param, (Descriptor, Parameter)): + if isinstance(param, (NumericDescriptor, Parameter)): common_attrs = { - 'datablock': param.datablock_id, - 'category': param.category_key, - 'entry': param.collection_entry_id, + 'datablock': param._identity.datablock_entry_name, + 'category': param._identity.category_code, + 'entry': param._identity.category_entry_name, 'parameter': param.name, 'value': param.value, 'units': param.units, @@ -66,8 +91,8 @@ def _get_params_as_dataframe( param_attrs = { 'fittable': True, 'free': param.free, - 'min': param.min, - 'max': param.max, + 'min': param.fit_min, + 'max': param.fit_max, 'uncertainty': f'{param.uncertainty:.4f}' if param.uncertainty else '', 'value': f'{param.value:.4f}', 'units': param.units, @@ -79,8 +104,11 @@ def _get_params_as_dataframe( return dataframe def show_all_params(self) -> None: - sample_models_params = self.project.sample_models.get_all_params() - experiments_params = self.project.experiments.get_all_params() + """Print a table with all parameters for sample models and + experiments. + """ + sample_models_params = self.project.sample_models.parameters + experiments_params = self.project.experiments.parameters if not sample_models_params and not experiments_params: print(warning('No parameters found.')) @@ -126,8 +154,11 @@ def show_all_params(self) -> None: ) def show_fittable_params(self) -> None: - sample_models_params = self.project.sample_models.get_fittable_params() - experiments_params = self.project.experiments.get_fittable_params() + """Print a table with parameters that can be included in + fitting. + """ + sample_models_params = self.project.sample_models.fittable_parameters + experiments_params = self.project.experiments.fittable_parameters if not sample_models_params and not experiments_params: print(warning('No fittable parameters found.')) @@ -177,8 +208,11 @@ def show_fittable_params(self) -> None: ) def show_free_params(self) -> None: - sample_models_params = self.project.sample_models.get_free_params() - experiments_params = self.project.experiments.get_free_params() + """Print a table with only currently-free (varying) + parameters. + """ + sample_models_params = self.project.sample_models.free_parameters + experiments_params = self.project.experiments.free_parameters free_params = sample_models_params + experiments_params if not free_params: @@ -225,8 +259,15 @@ def show_free_params(self) -> None: ) def how_to_access_parameters(self) -> None: - sample_models_params = self.project.sample_models.get_all_params() - experiments_params = self.project.experiments.get_all_params() + """Show Python access paths and CIF unique IDs for all + parameters. + + The output explains how to reference specific parameters in code + and which unique identifiers are used when creating CIF-based + constraints. + """ + sample_models_params = self.project.sample_models.parameters + experiments_params = self.project.experiments.parameters all_params = { 'sample_models': sample_models_params, 'experiments': experiments_params, @@ -258,22 +299,23 @@ def how_to_access_parameters(self) -> None: project_varname = self.project._varname for datablock_type, params in all_params.items(): for param in params: - if isinstance(param, (Descriptor, Parameter)): - datablock_id = param.datablock_id - category_key = param.category_key - entry_id = param.collection_entry_id + if isinstance(param, (NumericDescriptor, Parameter)): + datablock_entry_name = param._identity.datablock_entry_name + category_code = param._identity.category_code + category_entry_name = param._identity.category_entry_name param_key = param.name code_variable = ( - f"{project_varname}.{datablock_type}['{datablock_id}'].{category_key}" + f'{project_varname}.{datablock_type}' + f"['{datablock_entry_name}'].{category_code}" ) - if entry_id: - code_variable += f"['{entry_id}']" + if category_entry_name: + code_variable += f"['{category_entry_name}']" code_variable += f'.{param_key}' - cif_uid = param._generate_human_readable_unique_id() + cif_uid = param._cif_handler.uid columns_data.append([ - datablock_id, - category_key, - entry_id, + datablock_entry_name, + category_code, + category_entry_name, param_key, code_variable, cif_uid, @@ -288,19 +330,31 @@ def how_to_access_parameters(self) -> None: ) def show_current_calculator(self) -> None: + """Print the name of the currently selected calculator + engine. + """ print(paragraph('Current calculator')) print(self.current_calculator) @staticmethod def show_supported_calculators() -> None: + """Print a table of available calculator backends on this + system. + """ CalculatorFactory.show_supported_calculators() @property def current_calculator(self) -> str: + """The key/name of the active calculator backend.""" return self._calculator_key @current_calculator.setter def current_calculator(self, calculator_name: str) -> None: + """Switch to a different calculator backend. + + Args: + calculator_name: Calculator key to use (e.g. 'cryspy'). + """ calculator = CalculatorFactory.create_calculator(calculator_name) if calculator is None: return @@ -310,41 +364,68 @@ def current_calculator(self, calculator_name: str) -> None: print(self.current_calculator) def show_current_minimizer(self) -> None: + """Print the name of the currently selected minimizer.""" print(paragraph('Current minimizer')) print(self.current_minimizer) @staticmethod def show_available_minimizers() -> None: + """Print a table of available minimizer drivers on this + system. + """ MinimizerFactory.show_available_minimizers() @property def current_minimizer(self) -> Optional[str]: + """The identifier of the active minimizer, if any.""" return self.fitter.selection if self.fitter else None @current_minimizer.setter def current_minimizer(self, selection: str) -> None: - self.fitter = DiffractionMinimizer(selection) + """Switch to a different minimizer implementation. + + Args: + selection: Minimizer selection string, e.g. + 'lmfit (leastsq)'. + """ + self.fitter = Fitter(selection) print(paragraph('Current minimizer changed to')) print(self.current_minimizer) @property def fit_mode(self) -> str: + """Current fitting strategy: either 'single' or 'joint'.""" return self._fit_mode @fit_mode.setter def fit_mode(self, strategy: str) -> None: + """Set the fitting strategy. + + When set to 'joint', all experiments get default weights and + are used together in a single optimization. + + Args: + strategy: Either 'single' or 'joint'. + + Raises: + ValueError: If an unsupported strategy value is + provided. + """ if strategy not in ['single', 'joint']: raise ValueError("Fit mode must be either 'single' or 'joint'") self._fit_mode = strategy if strategy == 'joint' and not hasattr(self, 'joint_fit_experiments'): # Pre-populate all experiments with weight 0.5 self.joint_fit_experiments = JointFitExperiments() - for id in self.project.experiments.ids: - self.joint_fit_experiments.add(id, weight=0.5) + for id in self.project.experiments.names: + self.joint_fit_experiments.add_from_args(id=id, weight=0.5) print(paragraph('Current fit mode changed to')) print(self._fit_mode) def show_available_fit_modes(self) -> None: + """Print all supported fitting strategies and their + descriptions. + """ strategies = [ { 'Strategy': 'single', @@ -373,22 +454,26 @@ def show_available_fit_modes(self) -> None: ) def show_current_fit_mode(self) -> None: + """Print the currently active fitting strategy.""" print(paragraph('Current fit mode')) print(self.fit_mode) def calculate_pattern(self, expt_name: str) -> None: - """Calculate the diffraction pattern for a given experiment. The - calculated pattern is stored within the experiment's datastore. + """Calculate and store the diffraction pattern for an + experiment. + + The pattern is stored in the target experiment's datastore. Args: - expt_name: The name of the experiment. + expt_name: The identifier of the experiment to compute. """ experiment = self.project.experiments[expt_name] sample_models = self.project.sample_models self.calculator.calculate_pattern(sample_models, experiment) def show_constraints(self) -> None: - constraints_dict = self.constraints._items + """Print a table of all user-defined symbolic constraints.""" + constraints_dict = dict(self.constraints) if not self.constraints._items: print(warning('No constraints defined.')) @@ -415,6 +500,9 @@ def show_constraints(self) -> None: ) def apply_constraints(self): + """Apply the currently defined constraints to the active + project. + """ if not self.constraints._items: print(warning('No constraints defined.')) return @@ -424,6 +512,14 @@ def apply_constraints(self): self.constraints_handler.apply() def fit(self): + """Execute fitting using the selected mode, calculator and + minimizer. + + In 'single' mode, fits each experiment independently. In + 'joint' mode, performs a simultaneous fit across experiments + with weights. + Sets :attr:`fit_results` on success. + """ sample_models = self.project.sample_models if not sample_models: print('No sample models found in the project. Cannot run fit.') @@ -440,7 +536,7 @@ def fit(self): return # Run the fitting process - experiment_ids = experiments.ids + experiment_ids = experiments.names if self.fit_mode == 'joint': print( @@ -455,7 +551,7 @@ def fit(self): weights=self.joint_fit_experiments, ) elif self.fit_mode == 'single': - for expt_name in experiments.ids: + for expt_name in experiments.names: print( paragraph(f"Using experiment 🔬 '{expt_name}' for '{self.fit_mode}' fitting") ) @@ -470,24 +566,19 @@ def fit(self): self.fit_results = self.fitter.results def as_cif(self): - current_minimizer = self.current_minimizer - if ' ' in current_minimizer: - current_minimizer = f'"{current_minimizer}"' - - lines = [] - lines.append(f'_analysis.calculator_engine {self.current_calculator}') - lines.append(f'_analysis.fitting_engine {current_minimizer}') - lines.append(f'_analysis.fit_mode {self.fit_mode}') + """Serialize the analysis section to a CIF string. - lines.append('') - lines.append(self.aliases.as_cif()) - - lines.append('') - lines.append(self.constraints.as_cif()) + Returns: + The analysis section represented as a CIF document string. + """ + from easydiffraction.io.cif.serialize import analysis_to_cif - return '\n'.join(lines) + return analysis_to_cif(self) def show_as_cif(self) -> None: + """Render the analysis section as CIF in a formatted console + view. + """ cif_text: str = self.as_cif() paragraph_title: str = paragraph('Analysis 🧮 info as cif') render_cif(cif_text, paragraph_title) diff --git a/src/easydiffraction/analysis/calculation.py b/src/easydiffraction/analysis/calculation.py index 3456973f..6d3ee9fc 100644 --- a/src/easydiffraction/analysis/calculation.py +++ b/src/easydiffraction/analysis/calculation.py @@ -5,18 +5,18 @@ from typing import List from typing import Optional -from easydiffraction.analysis.calculators.calculator_factory import CalculatorFactory -from easydiffraction.experiments.experiment import Experiment +from easydiffraction.analysis.calculators.factory import CalculatorFactory +from easydiffraction.experiments.experiment.base import ExperimentBase from easydiffraction.experiments.experiments import Experiments from easydiffraction.sample_models.sample_models import SampleModels -class DiffractionCalculator: +class Calculator: """Invokes calculation engines for pattern generation.""" def __init__(self, engine: str = 'cryspy') -> None: - """Initialize the DiffractionCalculator with a specified backend - engine. + """Initialize the diffraction calculator with a specified + backend engine. Args: engine: Type of the calculation engine to use. @@ -54,7 +54,7 @@ def calculate_structure_factors( def calculate_pattern( self, sample_models: SampleModels, - experiment: Experiment, + experiment: ExperimentBase, ) -> None: """Calculate diffraction pattern based on sample models and experiment. The calculated pattern is stored within the diff --git a/src/easydiffraction/analysis/calculators/calculator_base.py b/src/easydiffraction/analysis/calculators/base.py similarity index 85% rename from src/easydiffraction/analysis/calculators/calculator_base.py rename to src/easydiffraction/analysis/calculators/base.py index 7fef0b91..27d87b6e 100644 --- a/src/easydiffraction/analysis/calculators/calculator_base.py +++ b/src/easydiffraction/analysis/calculators/base.py @@ -9,8 +9,8 @@ import numpy as np from easydiffraction.core.singletons import ConstraintsHandler -from easydiffraction.experiments.experiment import Experiment -from easydiffraction.sample_models.sample_model import SampleModel +from easydiffraction.experiments.experiment.base import ExperimentBase +from easydiffraction.sample_models.sample_model.base import SampleModelBase from easydiffraction.sample_models.sample_models import SampleModels @@ -30,8 +30,8 @@ def engine_imported(self) -> bool: @abstractmethod def calculate_structure_factors( self, - sample_model: SampleModel, - experiment: Experiment, + sample_model: SampleModelBase, + experiment: ExperimentBase, ) -> None: """Calculate structure factors for a single sample model and experiment. @@ -41,7 +41,7 @@ def calculate_structure_factors( def calculate_pattern( self, sample_models: SampleModels, - experiment: Experiment, + experiment: ExperimentBase, called_by_minimizer: bool = False, ) -> None: """Calculate the diffraction pattern for multiple sample models @@ -66,7 +66,7 @@ def calculate_pattern( # Calculate contributions from valid linked sample models y_calc_scaled = y_calc_zeros for linked_phase in valid_linked_phases: - sample_model_id = linked_phase._entry_id + sample_model_id = linked_phase._identity.category_entry_name sample_model_scale = linked_phase.scale.value sample_model = sample_models[sample_model_id] @@ -87,7 +87,9 @@ def calculate_pattern( # Calculate background contribution y_bkg = np.zeros_like(x_data) - if hasattr(experiment, 'background'): + # TODO: Change to the following check in other places instead of + # old `hasattr` check, because `hasattr` triggers warnings? + if 'background' in experiment._public_attrs(): y_bkg = experiment.background.calculate(x_data) experiment.datastore.bkg = y_bkg @@ -99,7 +101,7 @@ def calculate_pattern( def _calculate_single_model_pattern( self, sample_model: SampleModels, - experiment: Experiment, + experiment: ExperimentBase, called_by_minimizer: bool, ) -> np.ndarray: """Calculate the diffraction pattern for a single sample model @@ -119,7 +121,7 @@ def _calculate_single_model_pattern( def _get_valid_linked_phases( self, sample_models: SampleModels, - experiment: Experiment, + experiment: ExperimentBase, ) -> List[Any]: """Get valid linked phases from the experiment. @@ -136,10 +138,10 @@ def _get_valid_linked_phases( valid_linked_phases = [] for linked_phase in experiment.linked_phases: - if linked_phase._entry_id not in sample_models.get_ids(): + if linked_phase._identity.category_entry_name not in sample_models.names: print( f"Warning: Linked phase '{linked_phase.id.value}' not " - f'found in Sample Models {sample_models.get_ids()}' + f'found in Sample Models {sample_models.names}' ) continue valid_linked_phases.append(linked_phase) diff --git a/src/easydiffraction/analysis/calculators/calculator_crysfml.py b/src/easydiffraction/analysis/calculators/crysfml.py similarity index 94% rename from src/easydiffraction/analysis/calculators/calculator_crysfml.py rename to src/easydiffraction/analysis/calculators/crysfml.py index 76edb5a7..b58bddb5 100644 --- a/src/easydiffraction/analysis/calculators/calculator_crysfml.py +++ b/src/easydiffraction/analysis/calculators/crysfml.py @@ -8,10 +8,10 @@ import numpy as np -from easydiffraction.analysis.calculators.calculator_base import CalculatorBase -from easydiffraction.experiments.experiment import Experiment +from easydiffraction.analysis.calculators.base import CalculatorBase +from easydiffraction.experiments.experiment.base import ExperimentBase from easydiffraction.experiments.experiments import Experiments -from easydiffraction.sample_models.sample_models import SampleModel +from easydiffraction.sample_models.sample_model.base import SampleModelBase from easydiffraction.sample_models.sample_models import SampleModels try: @@ -54,7 +54,7 @@ def calculate_structure_factors( def _calculate_single_model_pattern( self, sample_model: SampleModels, - experiment: Experiment, + experiment: ExperimentBase, called_by_minimizer: bool = False, ) -> Union[np.ndarray, List[float]]: """Calculates the diffraction pattern using Crysfml for the @@ -105,8 +105,8 @@ def _adjust_pattern_length( def _crysfml_dict( self, sample_model: SampleModels, - experiment: Experiment, - ) -> Dict[str, Union[Experiment, SampleModel]]: + experiment: ExperimentBase, + ) -> Dict[str, Union[ExperimentBase, SampleModelBase]]: """Converts the sample model and experiment into a dictionary format for Crysfml. @@ -127,7 +127,7 @@ def _crysfml_dict( def _convert_sample_model_to_dict( self, - sample_model: SampleModel, + sample_model: SampleModelBase, ) -> Dict[str, Any]: """Converts a sample model into a dictionary format. @@ -167,7 +167,7 @@ def _convert_sample_model_to_dict( def _convert_experiment_to_dict( self, - experiment: Experiment, + experiment: ExperimentBase, ) -> Dict[str, Any]: """Converts an experiment into a dictionary format. diff --git a/src/easydiffraction/analysis/calculators/calculator_cryspy.py b/src/easydiffraction/analysis/calculators/cryspy.py similarity index 81% rename from src/easydiffraction/analysis/calculators/calculator_cryspy.py rename to src/easydiffraction/analysis/calculators/cryspy.py index d2a345a3..0a3d665a 100644 --- a/src/easydiffraction/analysis/calculators/calculator_cryspy.py +++ b/src/easydiffraction/analysis/calculators/cryspy.py @@ -11,10 +11,10 @@ import numpy as np -from easydiffraction.analysis.calculators.calculator_base import CalculatorBase -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.experiment import Experiment -from easydiffraction.sample_models.sample_model import SampleModel +from easydiffraction.analysis.calculators.base import CalculatorBase +from easydiffraction.experiments.experiment.base import ExperimentBase +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.sample_models.sample_model.base import SampleModelBase try: import cryspy @@ -49,8 +49,8 @@ def __init__(self) -> None: def calculate_structure_factors( self, - sample_model: SampleModel, - experiment: Experiment, + sample_model: SampleModelBase, + experiment: ExperimentBase, ) -> None: """Raises a NotImplementedError as HKL calculation is not implemented. @@ -65,8 +65,8 @@ def calculate_structure_factors( def _calculate_single_model_pattern( self, - sample_model: SampleModel, - experiment: Experiment, + sample_model: SampleModelBase, + experiment: ExperimentBase, called_by_minimizer: bool = False, ) -> Union[np.ndarray, List[float]]: """Calculates the diffraction pattern using Cryspy for the given @@ -141,8 +141,8 @@ def _calculate_single_model_pattern( def _recreate_cryspy_dict( self, - sample_model: SampleModel, - experiment: Experiment, + sample_model: SampleModelBase, + experiment: ExperimentBase, ) -> Dict[str, Any]: """Recreates the Cryspy dictionary for the given sample model and experiment. @@ -160,6 +160,8 @@ def _recreate_cryspy_dict( cryspy_model_id = f'crystal_{sample_model.name}' cryspy_model_dict = cryspy_dict[cryspy_model_id] + # Update sample model parameters + # Cell cryspy_cell = cryspy_model_dict['unit_cell_parameters'] cryspy_cell[0] = sample_model.cell.length_a.value @@ -186,15 +188,18 @@ def _recreate_cryspy_dict( for idx, atom_site in enumerate(sample_model.atom_sites): cryspy_biso[idx] = atom_site.b_iso.value - # ---------- Update experiment parameters ---------- + # Update experiment parameters + if experiment.type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: cryspy_expt_name = f'pd_{experiment.name}' cryspy_expt_dict = cryspy_dict[cryspy_expt_name] + # Instrument cryspy_expt_dict['offset_ttheta'][0] = np.deg2rad( experiment.instrument.calib_twotheta_offset.value ) cryspy_expt_dict['wavelength'][0] = experiment.instrument.setup_wavelength.value + # Peak cryspy_resolution = cryspy_expt_dict['resolution_parameters'] cryspy_resolution[0] = experiment.peak.broad_gauss_u.value @@ -206,6 +211,7 @@ def _recreate_cryspy_dict( elif experiment.type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: cryspy_expt_name = f'tof_{experiment.name}' cryspy_expt_dict = cryspy_dict[cryspy_expt_name] + # Instrument cryspy_expt_dict['zero'][0] = experiment.instrument.calib_d_to_tof_offset.value cryspy_expt_dict['dtt1'][0] = experiment.instrument.calib_d_to_tof_linear.value @@ -213,6 +219,7 @@ def _recreate_cryspy_dict( cryspy_expt_dict['ttheta_bank'] = np.deg2rad( experiment.instrument.setup_twotheta_bank.value ) + # Peak cryspy_sigma = cryspy_expt_dict['profile_sigmas'] cryspy_sigma[0] = experiment.peak.broad_gauss_sigma_0.value @@ -231,8 +238,8 @@ def _recreate_cryspy_dict( def _recreate_cryspy_obj( self, - sample_model: SampleModel, - experiment: Experiment, + sample_model: SampleModelBase, + experiment: ExperimentBase, ) -> Any: """Recreates the Cryspy object for the given sample model and experiment. @@ -263,7 +270,7 @@ def _recreate_cryspy_obj( def _convert_sample_model_to_cryspy_cif( self, - sample_model: SampleModel, + sample_model: SampleModelBase, ) -> str: """Converts a sample model to a Cryspy CIF string. @@ -273,11 +280,11 @@ def _convert_sample_model_to_cryspy_cif( Returns: The Cryspy CIF string representation of the sample model. """ - return sample_model.as_cif() + return sample_model.as_cif def _convert_experiment_to_cryspy_cif( self, - experiment: Experiment, + experiment: ExperimentBase, linked_phase: Any, ) -> str: """Converts an experiment to a Cryspy CIF string. @@ -304,42 +311,54 @@ def _convert_experiment_to_cryspy_cif( cif_lines.append(f'_setup_radiation {radiation_probe}') if instrument: - instrument_mapping = { - 'setup_wavelength': '_setup_wavelength', - 'calib_twotheta_offset': '_setup_offset_2theta', - 'setup_twotheta_bank': '_tof_parameters_2theta_bank', - 'calib_d_to_tof_offset': '_tof_parameters_Zero', - 'calib_d_to_tof_linear': '_tof_parameters_Dtt1', - 'calib_d_to_tof_quad': '_tof_parameters_dtt2', - } + # Restrict to only attributes relevant for the beam mode to + # avoid probing non-existent guarded attributes (which + # triggers diagnostics). + if expt_type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: + instrument_mapping = { + 'setup_wavelength': '_setup_wavelength', + 'calib_twotheta_offset': '_setup_offset_2theta', + } + elif expt_type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: + instrument_mapping = { + 'setup_twotheta_bank': '_tof_parameters_2theta_bank', + 'calib_d_to_tof_offset': '_tof_parameters_Zero', + 'calib_d_to_tof_linear': '_tof_parameters_Dtt1', + 'calib_d_to_tof_quad': '_tof_parameters_dtt2', + } cif_lines.append('') for local_attr_name, engine_key_name in instrument_mapping.items(): - if hasattr(instrument, local_attr_name): - attr_value = getattr(instrument, local_attr_name).value - cif_lines.append(f'{engine_key_name} {attr_value}') + # attr_obj = instrument.__dict__.get(local_attr_name) + attr_obj = getattr(instrument, local_attr_name) + if attr_obj is not None: + cif_lines.append(f'{engine_key_name} {attr_obj.value}') if peak: - peak_mapping = { - 'broad_gauss_u': '_pd_instr_resolution_U', - 'broad_gauss_v': '_pd_instr_resolution_V', - 'broad_gauss_w': '_pd_instr_resolution_W', - 'broad_lorentz_x': '_pd_instr_resolution_X', - 'broad_lorentz_y': '_pd_instr_resolution_Y', - 'broad_gauss_sigma_0': '_tof_profile_sigma0', - 'broad_gauss_sigma_1': '_tof_profile_sigma1', - 'broad_gauss_sigma_2': '_tof_profile_sigma2', - 'broad_mix_beta_0': '_tof_profile_beta0', - 'broad_mix_beta_1': '_tof_profile_beta1', - 'asym_alpha_0': '_tof_profile_alpha0', - 'asym_alpha_1': '_tof_profile_alpha1', - } - cif_lines.append('') - if expt_type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: + if expt_type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: + peak_mapping = { + 'broad_gauss_u': '_pd_instr_resolution_U', + 'broad_gauss_v': '_pd_instr_resolution_V', + 'broad_gauss_w': '_pd_instr_resolution_W', + 'broad_lorentz_x': '_pd_instr_resolution_X', + 'broad_lorentz_y': '_pd_instr_resolution_Y', + } + elif expt_type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: + peak_mapping = { + 'broad_gauss_sigma_0': '_tof_profile_sigma0', + 'broad_gauss_sigma_1': '_tof_profile_sigma1', + 'broad_gauss_sigma_2': '_tof_profile_sigma2', + 'broad_mix_beta_0': '_tof_profile_beta0', + 'broad_mix_beta_1': '_tof_profile_beta1', + 'asym_alpha_0': '_tof_profile_alpha0', + 'asym_alpha_1': '_tof_profile_alpha1', + } cif_lines.append('_tof_profile_peak_shape Gauss') + cif_lines.append('') for local_attr_name, engine_key_name in peak_mapping.items(): - if hasattr(peak, local_attr_name): - attr_value = getattr(peak, local_attr_name).value - cif_lines.append(f'{engine_key_name} {attr_value}') + # attr_obj = peak.__dict__.get(local_attr_name) + attr_obj = getattr(peak, local_attr_name) + if attr_obj is not None: + cif_lines.append(f'{engine_key_name} {attr_obj.value}') x_data = experiment.datastore.x twotheta_min = float(x_data.min()) diff --git a/src/easydiffraction/analysis/calculators/calculator_factory.py b/src/easydiffraction/analysis/calculators/factory.py similarity index 64% rename from src/easydiffraction/analysis/calculators/calculator_factory.py rename to src/easydiffraction/analysis/calculators/factory.py index d354dc29..e4e447c2 100644 --- a/src/easydiffraction/analysis/calculators/calculator_factory.py +++ b/src/easydiffraction/analysis/calculators/factory.py @@ -7,16 +7,24 @@ from typing import Type from typing import Union -from easydiffraction.analysis.calculators.calculator_base import CalculatorBase -from easydiffraction.analysis.calculators.calculator_crysfml import CrysfmlCalculator -from easydiffraction.analysis.calculators.calculator_cryspy import CryspyCalculator -from easydiffraction.analysis.calculators.calculator_pdffit import PdffitCalculator +from easydiffraction.analysis.calculators.base import CalculatorBase +from easydiffraction.analysis.calculators.crysfml import CrysfmlCalculator +from easydiffraction.analysis.calculators.cryspy import CryspyCalculator +from easydiffraction.analysis.calculators.pdffit import PdffitCalculator from easydiffraction.utils.formatting import error from easydiffraction.utils.formatting import paragraph from easydiffraction.utils.utils import render_table class CalculatorFactory: + """Factory for creating calculation engine instances. + + The factory exposes discovery helpers to list and show available + calculators in the current environment and a creator that returns an + instantiated calculator or ``None`` if the requested one is not + available. + """ + _potential_calculators: Dict[str, Dict[str, Union[str, Type[CalculatorBase]]]] = { 'crysfml': { 'description': 'CrysFML library for crystallographic calculations', @@ -36,6 +44,14 @@ class CalculatorFactory: def _supported_calculators( cls, ) -> Dict[str, Dict[str, Union[str, Type[CalculatorBase]]]]: + """Return calculators whose engines are importable. + + This filters the list of potential calculators by instantiating + their classes and checking the ``engine_imported`` property. + + Returns: + Mapping from calculator name to its config dict. + """ return { name: cfg for name, cfg in cls._potential_calculators.items() @@ -44,10 +60,16 @@ def _supported_calculators( @classmethod def list_supported_calculators(cls) -> List[str]: + """List names of calculators available in the environment. + + Returns: + List of calculator identifiers, e.g. ``["crysfml", ...]``. + """ return list(cls._supported_calculators().keys()) @classmethod def show_supported_calculators(cls) -> None: + """Pretty-print supported calculators and their descriptions.""" columns_headers: List[str] = ['Calculator', 'Description'] columns_alignment = ['left', 'left'] columns_data: List[List[str]] = [] @@ -64,6 +86,14 @@ def show_supported_calculators(cls) -> None: @classmethod def create_calculator(cls, calculator_name: str) -> Optional[CalculatorBase]: + """Create a calculator instance by name. + + Args: + calculator_name: Identifier of the calculator to create. + + Returns: + A calculator instance or ``None`` if unknown or unsupported. + """ config = cls._supported_calculators().get(calculator_name) if not config: print(error(f"Unknown calculator '{calculator_name}'")) diff --git a/src/easydiffraction/analysis/calculators/calculator_pdffit.py b/src/easydiffraction/analysis/calculators/pdffit.py similarity index 87% rename from src/easydiffraction/analysis/calculators/calculator_pdffit.py rename to src/easydiffraction/analysis/calculators/pdffit.py index 25d55acf..153110d9 100644 --- a/src/easydiffraction/analysis/calculators/calculator_pdffit.py +++ b/src/easydiffraction/analysis/calculators/pdffit.py @@ -1,5 +1,10 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause +"""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. +""" import os import re @@ -8,9 +13,9 @@ import numpy as np -from easydiffraction.analysis.calculators.calculator_base import CalculatorBase -from easydiffraction.experiments.experiment import Experiment -from easydiffraction.sample_models.sample_model import SampleModel +from easydiffraction.analysis.calculators.base import CalculatorBase +from easydiffraction.experiments.experiment.base import ExperimentBase +from easydiffraction.sample_models.sample_model.base import SampleModelBase try: from diffpy.pdffit2 import PdfFit @@ -51,8 +56,8 @@ def calculate_structure_factors(self, sample_models, experiments): def _calculate_single_model_pattern( self, - sample_model: SampleModel, - experiment: Experiment, + sample_model: SampleModelBase, + experiment: ExperimentBase, called_by_minimizer: bool = False, ): # Intentionally unused, required by public API/signature @@ -67,7 +72,7 @@ def _calculate_single_model_pattern( # TODO: move CIF v2 -> CIF v1 conversion to a separate module # Convert the sample model to CIF supported by PDFfit - cif_string_v2 = sample_model.as_cif() + cif_string_v2 = sample_model.as_cif # convert to version 1 of CIF format # this means: replace all dots with underscores for # cases where the dot is surrounded by letters on both sides. diff --git a/src/easydiffraction/analysis/collections/__init__.py b/src/easydiffraction/analysis/categories/__init__.py similarity index 100% rename from src/easydiffraction/analysis/collections/__init__.py rename to src/easydiffraction/analysis/categories/__init__.py diff --git a/src/easydiffraction/analysis/categories/aliases.py b/src/easydiffraction/analysis/categories/aliases.py new file mode 100644 index 00000000..5a7faaa6 --- /dev/null +++ b/src/easydiffraction/analysis/categories/aliases.py @@ -0,0 +1,106 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""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. +""" + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RegexValidator +from easydiffraction.io.cif.handler import CifHandler + + +class Alias(CategoryItem): + """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, + *, + label: str, + param_uid: str, + ) -> None: + super().__init__() + + self._label: StringDescriptor = StringDescriptor( + name='label', + description='...', + value_spec=AttributeSpec( + value=label, + type_=DataTypes.STRING, + default='...', + content_validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), + ), + cif_handler=CifHandler( + names=[ + '_alias.label', + ] + ), + ) + self._param_uid: StringDescriptor = StringDescriptor( + name='param_uid', + description='...', + value_spec=AttributeSpec( + value=param_uid, + type_=DataTypes.STRING, + default='...', + content_validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), + ), + cif_handler=CifHandler( + names=[ + '_alias.param_uid', + ] + ), + ) + + self._identity.category_code = 'alias' + self._identity.category_entry_name = lambda: self.label.value + + @property + def label(self): + """Alias label descriptor.""" + return self._label + + @label.setter + def label(self, value): + """Set alias label. + + Args: + value: New label. + """ + self._label.value = value + + @property + def param_uid(self): + """Parameter uid descriptor the alias points to.""" + return self._param_uid + + @param_uid.setter + def param_uid(self, value): + """Set the parameter uid. + + Args: + value: New uid. + """ + self._param_uid.value = value + + +class Aliases(CategoryCollection): + """Collection of :class:`Alias` items.""" + + def __init__(self): + """Create an empty collection of aliases.""" + super().__init__(item_type=Alias) diff --git a/src/easydiffraction/analysis/categories/constraints.py b/src/easydiffraction/analysis/categories/constraints.py new file mode 100644 index 00000000..f99f93b1 --- /dev/null +++ b/src/easydiffraction/analysis/categories/constraints.py @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""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. +""" + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RegexValidator +from easydiffraction.io.cif.handler import CifHandler + + +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. + """ + + def __init__( + self, + *, + lhs_alias: str, + rhs_expr: str, + ) -> None: + super().__init__() + + self._lhs_alias: StringDescriptor = StringDescriptor( + name='lhs_alias', + description='...', + value_spec=AttributeSpec( + value=lhs_alias, + type_=DataTypes.STRING, + default='...', + content_validator=RegexValidator(pattern=r'.*'), + ), + cif_handler=CifHandler( + names=[ + '_constraint.lhs_alias', + ] + ), + ) + self._rhs_expr: StringDescriptor = StringDescriptor( + name='rhs_expr', + description='...', + value_spec=AttributeSpec( + value=rhs_expr, + type_=DataTypes.STRING, + default='...', + content_validator=RegexValidator(pattern=r'.*'), + ), + cif_handler=CifHandler( + names=[ + '_constraint.rhs_expr', + ] + ), + ) + + self._identity.category_code = 'constraint' + self._identity.category_entry_name = lambda: self.lhs_alias.value + + @property + def lhs_alias(self): + """Alias name on the left-hand side of the equation.""" + return self._lhs_alias + + @lhs_alias.setter + def lhs_alias(self, value): + """Set the left-hand side alias. + + Args: + value: New alias string. + """ + self._lhs_alias.value = value + + @property + def rhs_expr(self): + """Right-hand side expression string.""" + return self._rhs_expr + + @rhs_expr.setter + def rhs_expr(self, value): + """Set the right-hand side expression. + + Args: + value: New expression string. + """ + self._rhs_expr.value = value + + +class Constraints(CategoryCollection): + """Collection of :class:`Constraint` items.""" + + def __init__(self): + """Create an empty constraints collection.""" + super().__init__(item_type=Constraint) diff --git a/src/easydiffraction/analysis/categories/joint_fit_experiments.py b/src/easydiffraction/analysis/categories/joint_fit_experiments.py new file mode 100644 index 00000000..119e413d --- /dev/null +++ b/src/easydiffraction/analysis/categories/joint_fit_experiments.py @@ -0,0 +1,104 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Joint-fit experiment weighting configuration. + +Stores per-experiment weights to be used when multiple experiments are +fitted simultaneously. +""" + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import NumericDescriptor +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.core.validation import RegexValidator +from easydiffraction.io.cif.handler import CifHandler + + +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. + """ + + def __init__( + self, + *, + id: str, + weight: float, + ) -> None: + super().__init__() + + self._id: StringDescriptor = StringDescriptor( + name='id', # TODO: need new name instead of id + description='...', + value_spec=AttributeSpec( + value=id, + type_=DataTypes.STRING, + default='...', + content_validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), + ), + cif_handler=CifHandler( + names=[ + '_joint_fit_experiment.id', + ] + ), + ) + self._weight: NumericDescriptor = NumericDescriptor( + name='weight', + description='...', + value_spec=AttributeSpec( + value=weight, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_joint_fit_experiment.weight', + ] + ), + ) + + self._identity.category_code = 'joint_fit_experiment' + self._identity.category_entry_name = lambda: self.id.value + + @property + def id(self): + """Experiment identifier descriptor.""" + return self._id + + @id.setter + def id(self, value): + """Set the experiment identifier. + + Args: + value: New id string. + """ + self._id.value = value + + @property + def weight(self): + """Weight factor descriptor.""" + return self._weight + + @weight.setter + def weight(self, value): + """Set the weight factor. + + Args: + value: New weight value. + """ + self._weight.value = value + + +class JointFitExperiments(CategoryCollection): + """Collection of :class:`JointFitExperiment` items.""" + + def __init__(self): + """Create an empty joint-fit experiments collection.""" + super().__init__(item_type=JointFitExperiment) diff --git a/src/easydiffraction/analysis/collections/aliases.py b/src/easydiffraction/analysis/collections/aliases.py deleted file mode 100644 index 76820fde..00000000 --- a/src/easydiffraction/analysis/collections/aliases.py +++ /dev/null @@ -1,50 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from typing import Type - -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor - - -class Alias(Component): - @property - def category_key(self) -> str: - return 'alias' - - @property - def cif_category_key(self) -> str: - return 'alias' - - def __init__(self, label: str, param_uid: str) -> None: - super().__init__() - - self.label: Descriptor = Descriptor( - value=label, - name='label', - cif_name='label', - ) - self.param_uid: Descriptor = Descriptor( - value=param_uid, - name='param_uid', - cif_name='param_uid', - ) - - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = label - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class Aliases(Collection): - @property - def _type(self) -> str: - return 'category' # datablock or category - - @property - def _child_class(self) -> Type[Alias]: - return Alias diff --git a/src/easydiffraction/analysis/collections/constraints.py b/src/easydiffraction/analysis/collections/constraints.py deleted file mode 100644 index c7f032f5..00000000 --- a/src/easydiffraction/analysis/collections/constraints.py +++ /dev/null @@ -1,50 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from typing import Type - -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor - - -class Constraint(Component): - @property - def category_key(self) -> str: - return 'constraint' - - @property - def cif_category_key(self) -> str: - return 'constraint' - - def __init__(self, lhs_alias: str, rhs_expr: str) -> None: - super().__init__() - - self.lhs_alias: Descriptor = Descriptor( - value=lhs_alias, - name='lhs_alias', - cif_name='lhs_alias', - ) - self.rhs_expr: Descriptor = Descriptor( - value=rhs_expr, - name='rhs_expr', - cif_name='rhs_expr', - ) - - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = lhs_alias - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class Constraints(Collection): - @property - def _type(self) -> str: - return 'category' # datablock or category - - @property - def _child_class(self) -> Type[Constraint]: - return Constraint diff --git a/src/easydiffraction/analysis/collections/joint_fit_experiments.py b/src/easydiffraction/analysis/collections/joint_fit_experiments.py deleted file mode 100644 index bec45433..00000000 --- a/src/easydiffraction/analysis/collections/joint_fit_experiments.py +++ /dev/null @@ -1,54 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from typing import Type - -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor - - -class JointFitExperiment(Component): - @property - def category_key(self) -> str: - return 'joint_fit_experiment' - - @property - def cif_category_key(self) -> str: - return 'joint_fit_experiment' - - def __init__(self, id: str, weight: float) -> None: - super().__init__() - - self.id: Descriptor = Descriptor( - value=id, - name='id', - cif_name='id', - ) - self.weight: Descriptor = Descriptor( - value=weight, - name='weight', - cif_name='weight', - ) - - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = id - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class JointFitExperiments(Collection): - """Collection manager for experiments that are fitted together in a - `joint` fit. - """ - - @property - def _type(self) -> str: - return 'category' # datablock or category - - @property - def _child_class(self) -> Type[JointFitExperiment]: - return JointFitExperiment diff --git a/src/easydiffraction/analysis/fitting/__init__.py b/src/easydiffraction/analysis/fit_helpers/__init__.py similarity index 100% rename from src/easydiffraction/analysis/fitting/__init__.py rename to src/easydiffraction/analysis/fit_helpers/__init__.py diff --git a/src/easydiffraction/analysis/fitting/metrics.py b/src/easydiffraction/analysis/fit_helpers/metrics.py similarity index 97% rename from src/easydiffraction/analysis/fitting/metrics.py rename to src/easydiffraction/analysis/fit_helpers/metrics.py index 2ed11b5b..42a372e0 100644 --- a/src/easydiffraction/analysis/fitting/metrics.py +++ b/src/easydiffraction/analysis/fit_helpers/metrics.py @@ -6,7 +6,7 @@ import numpy as np -from easydiffraction.analysis.calculators.calculator_base import CalculatorBase +from easydiffraction.analysis.calculators.base import CalculatorBase from easydiffraction.experiments.experiments import Experiments from easydiffraction.sample_models.sample_models import SampleModels @@ -141,7 +141,7 @@ def get_reliability_inputs( y_obs_all = [] y_calc_all = [] y_err_all = [] - for experiment in experiments._items.values(): + for experiment in experiments.values(): calculator.calculate_pattern(sample_models, experiment) y_calc = experiment.datastore.calc y_meas = experiment.datastore.meas diff --git a/src/easydiffraction/analysis/fitting/results.py b/src/easydiffraction/analysis/fit_helpers/reporting.py similarity index 63% rename from src/easydiffraction/analysis/fitting/results.py rename to src/easydiffraction/analysis/fit_helpers/reporting.py index 1ad0ae27..76e8c9f6 100644 --- a/src/easydiffraction/analysis/fitting/results.py +++ b/src/easydiffraction/analysis/fit_helpers/reporting.py @@ -6,14 +6,21 @@ from typing import Optional from easydiffraction import paragraph -from easydiffraction.analysis.fitting.metrics import calculate_r_factor -from easydiffraction.analysis.fitting.metrics import calculate_r_factor_squared -from easydiffraction.analysis.fitting.metrics import calculate_rb_factor -from easydiffraction.analysis.fitting.metrics import calculate_weighted_r_factor +from easydiffraction.analysis.fit_helpers.metrics import calculate_r_factor +from easydiffraction.analysis.fit_helpers.metrics import calculate_r_factor_squared +from easydiffraction.analysis.fit_helpers.metrics import calculate_rb_factor +from easydiffraction.analysis.fit_helpers.metrics import calculate_weighted_r_factor from easydiffraction.utils.utils import render_table class FitResults: + """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 + indicators and a table of fitted parameters. + """ + def __init__( self, success: bool = False, @@ -27,6 +34,22 @@ def __init__( fitting_time: Optional[float] = None, **kwargs: Any, ) -> 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. + """ self.success: bool = success self.parameters: List[Any] = parameters if parameters is not None else [] self.chi_square: Optional[float] = chi_square @@ -54,6 +77,15 @@ 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. + """ status_icon = '✅' if self.success else '❌' rf = rf2 = wr = br = None if y_obs is not None and y_calc is not None: @@ -103,30 +135,34 @@ def display_results( rows = [] for param in self.parameters: - datablock_id = getattr(param, 'datablock_id', 'N/A') - category_key = getattr(param, 'category_key', 'N/A') - collection_entry_id = getattr(param, 'collection_entry_id', 'N/A') + datablock_entry_name = ( + param._identity.datablock_entry_name + ) # getattr(param, 'datablock_name', 'N/A') + category_code = param._identity.category_code # getattr(param, 'category_key', 'N/A') + category_entry_name = ( + param._identity.category_entry_name + ) # getattr(param, 'category_entry_name', 'N/A') name = getattr(param, 'name', 'N/A') start = ( - f'{getattr(param, "start_value", "N/A"):.4f}' - if param.start_value is not None + f'{getattr(param, "_fit_start_value", "N/A"):.4f}' + if param._fit_start_value is not None else 'N/A' ) fitted = f'{param.value:.4f}' if param.value is not None else 'N/A' uncertainty = f'{param.uncertainty:.4f}' if param.uncertainty is not None else 'N/A' units = getattr(param, 'units', 'N/A') - if param.start_value and param.value: - change = ((param.value - param.start_value) / param.start_value) * 100 + if param._fit_start_value and param.value: + change = ((param.value - param._fit_start_value) / param._fit_start_value) * 100 arrow = '↑' if change > 0 else '↓' relative_change = f'{abs(change):.2f} % {arrow}' else: relative_change = 'N/A' rows.append([ - datablock_id, - category_key, - collection_entry_id, + datablock_entry_name, + category_code, + category_entry_name, name, start, fitted, diff --git a/src/easydiffraction/analysis/fitting/progress_tracker.py b/src/easydiffraction/analysis/fit_helpers/tracking.py similarity index 82% rename from src/easydiffraction/analysis/fitting/progress_tracker.py rename to src/easydiffraction/analysis/fit_helpers/tracking.py index a1b5a3f7..94629592 100644 --- a/src/easydiffraction/analysis/fitting/progress_tracker.py +++ b/src/easydiffraction/analysis/fit_helpers/tracking.py @@ -15,7 +15,7 @@ display = None clear_output = None -from easydiffraction.analysis.fitting.metrics import calculate_reduced_chi_square +from easydiffraction.analysis.fit_helpers.metrics import calculate_reduced_chi_square from easydiffraction.utils.utils import is_notebook from easydiffraction.utils.utils import render_table @@ -41,9 +41,12 @@ def format_cell( return cell_str -class FittingProgressTracker: - """Tracks and reports the reduced chi-square during the optimization - process. +class FitProgressTracker: + """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 + a table in notebooks or a text UI in terminals. """ def __init__(self) -> None: @@ -59,6 +62,7 @@ def __init__(self) -> None: self._display_handle: Optional[DisplayHandle] = None def reset(self) -> None: + """Reset internal state before a new optimization run.""" self._iteration = 0 self._previous_chi2 = None self._last_chi2 = None @@ -72,15 +76,14 @@ def track( residuals: np.ndarray, parameters: List[float], ) -> np.ndarray: - """Track chi-square progress during the optimization process. + """Update progress with current residuals and parameters. - Parameters: - residuals (np.ndarray): Array of residuals between measured - and calculated data. - parameters (list): List of free parameters being fitted. + Args: + residuals: Residuals between measured and calculated data. + parameters: Current free parameters being fitted. Returns: - np.ndarray: Residuals unchanged, for optimizer consumption. + Residuals unchanged, for optimizer consumption. """ self._iteration += 1 @@ -133,28 +136,39 @@ def track( @property def best_chi2(self) -> Optional[float]: + """Best recorded reduced chi-square value or None.""" return self._best_chi2 @property def best_iteration(self) -> Optional[int]: + """Iteration index at which the best chi-square was observed.""" return self._best_iteration @property def iteration(self) -> int: + """Current iteration counter.""" return self._iteration @property def fitting_time(self) -> Optional[float]: + """Elapsed time of the last run in seconds, if available.""" return self._fitting_time def start_timer(self) -> None: + """Begin timing of a fit run.""" self._start_time = time.perf_counter() def stop_timer(self) -> None: + """Stop timing and store elapsed time for the run.""" self._end_time = time.perf_counter() 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. + + Args: + minimizer_name: Name of the minimizer used for the run. + """ print(f"🚀 Starting fit process with '{minimizer_name}'...") print('📈 Goodness-of-fit (reduced χ²) change:') @@ -189,6 +203,11 @@ def start_tracking(self, minimizer_name: str) -> None: print('╞' + '╪'.join(['═' * FIXED_WIDTH for _ in DEFAULT_HEADERS]) + '╡') def add_tracking_info(self, row: List[str]) -> None: + """Append a formatted row to the progress display. + + Args: + row: Columns corresponding to DEFAULT_HEADERS. + """ if is_notebook() and display is not None: # Add row to DataFrame self._df_rows.append(row) @@ -214,6 +233,7 @@ def add_tracking_info(self, row: List[str]) -> None: print(formatted_row) def finish_tracking(self) -> None: + """Finalize progress display and print best result summary.""" # Add last iteration as last row row: List[str] = [ str(self._last_iteration), diff --git a/src/easydiffraction/analysis/minimization.py b/src/easydiffraction/analysis/fitting.py similarity index 87% rename from src/easydiffraction/analysis/minimization.py rename to src/easydiffraction/analysis/fitting.py index 3fc04cbf..f447a17f 100644 --- a/src/easydiffraction/analysis/minimization.py +++ b/src/easydiffraction/analysis/fitting.py @@ -9,18 +9,18 @@ import numpy as np -from easydiffraction.analysis.calculators.calculator_base import CalculatorBase -from easydiffraction.analysis.fitting.metrics import get_reliability_inputs -from easydiffraction.analysis.minimizers.minimizer_factory import MinimizerFactory -from easydiffraction.core.objects import Parameter +from easydiffraction.analysis.calculators.base import CalculatorBase +from easydiffraction.analysis.fit_helpers.metrics import get_reliability_inputs +from easydiffraction.analysis.minimizers.factory import MinimizerFactory +from easydiffraction.core.parameters import Parameter from easydiffraction.experiments.experiments import Experiments from easydiffraction.sample_models.sample_models import SampleModels if TYPE_CHECKING: - from easydiffraction.analysis.fitting.results import FitResults + from easydiffraction.analysis.fit_helpers.reporting import FitResults -class DiffractionMinimizer: +class Fitter: """Handles the fitting workflow using a pluggable minimizer.""" def __init__(self, selection: str = 'lmfit (leastsq)') -> None: @@ -44,14 +44,14 @@ def fit( calculator: The calculator to use for pattern generation. weights: Optional weights for joint fitting. """ - params = sample_models.get_free_params() + experiments.get_free_params() + params = sample_models.free_parameters + experiments.free_parameters if not params: print('⚠️ No parameters selected for fitting.') return None for param in params: - param.start_value = param.value + param._fit_start_value = param.value def objective_function(engine_params: Dict[str, Any]) -> np.ndarray: return self._residual_function( @@ -114,9 +114,7 @@ def _collect_free_parameters( Returns: List of free parameters. """ - free_params: List[Parameter] = ( - sample_models.get_free_params() + experiments.get_free_params() - ) + free_params: List[Parameter] = sample_models.free_parameters + experiments.free_parameters return free_params def _residual_function( @@ -147,13 +145,13 @@ def _residual_function( self.minimizer._sync_result_to_parameters(parameters, engine_params) # Prepare weights for joint fitting - num_expts: int = len(experiments.ids) + num_expts: int = len(experiments.names) if weights is None: _weights = np.ones(num_expts) else: _weights_list: List[float] = [] - for id in experiments.ids: - _weight = weights._items[id].weight.value + for name in experiments.names: + _weight = weights[name].weight.value _weights_list.append(_weight) _weights = np.array(_weights_list, dtype=np.float64) @@ -165,7 +163,7 @@ def _residual_function( _weights *= num_expts / np.sum(_weights) residuals: List[float] = [] - for experiment, weight in zip(experiments._items.values(), _weights, strict=True): + for experiment, weight in zip(experiments.values(), _weights, strict=True): # Calculate the difference between measured and calculated # patterns calculator.calculate_pattern( diff --git a/src/easydiffraction/analysis/minimizers/minimizer_base.py b/src/easydiffraction/analysis/minimizers/base.py similarity index 66% rename from src/easydiffraction/analysis/minimizers/minimizer_base.py rename to src/easydiffraction/analysis/minimizers/base.py index 162c33ae..2f966d5c 100644 --- a/src/easydiffraction/analysis/minimizers/minimizer_base.py +++ b/src/easydiffraction/analysis/minimizers/base.py @@ -11,14 +11,19 @@ import numpy as np -from easydiffraction.analysis.fitting.progress_tracker import FittingProgressTracker -from easydiffraction.analysis.fitting.results import FitResults +from easydiffraction.analysis.fit_helpers.reporting import FitResults +from easydiffraction.analysis.fit_helpers.tracking import FitProgressTracker class MinimizerBase(ABC): - """Abstract base class for minimizer implementations. - - Provides shared logic and structure for concrete minimizers. + """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__( @@ -36,21 +41,32 @@ def __init__( self._best_chi2: Optional[float] = None self._best_iteration: Optional[int] = None self._fitting_time: Optional[float] = None - self.tracker: FittingProgressTracker = FittingProgressTracker() + self.tracker: FitProgressTracker = FitProgressTracker() def _start_tracking(self, minimizer_name: str) -> None: + """Initialize progress tracking and timer. + + Args: + minimizer_name: Human-readable name shown in progress. + """ self.tracker.reset() self.tracker.start_tracking(minimizer_name) self.tracker.start_timer() def _stop_tracking(self) -> None: + """Stop timer and finalize tracking.""" self.tracker.stop_timer() self.tracker.finish_tracking() @abstractmethod def _prepare_solver_args(self, parameters: List[Any]) -> Dict[str, Any]: - """Prepare the solver arguments directly from the list of free - parameters. + """Prepare keyword-arguments for the underlying solver. + + Args: + parameters: List of free parameters to be fitted. + + Returns: + Mapping of keyword arguments to pass into ``_run_solver``. """ pass @@ -60,6 +76,7 @@ def _run_solver( objective_function: Callable[..., Any], engine_parameters: Dict[str, Any], ) -> Any: + """Execute the concrete solver and return its raw result.""" pass @abstractmethod @@ -68,6 +85,9 @@ def _sync_result_to_parameters( raw_result: Any, parameters: List[Any], ) -> None: + """Copy values from ``raw_result`` back to ``parameters`` in- + place. + """ pass def _finalize_fit( @@ -75,6 +95,15 @@ def _finalize_fit( parameters: List[Any], raw_result: Any, ) -> 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. + """ self._sync_result_to_parameters(parameters, raw_result) success = self._check_success(raw_result) self.result = FitResults( @@ -89,10 +118,7 @@ def _finalize_fit( @abstractmethod def _check_success(self, raw_result: Any) -> bool: - """Determine whether the fit was successful. - - This must be implemented by concrete minimizers. - """ + """Determine whether the fit was successful.""" pass def fit( @@ -100,6 +126,16 @@ def fit( parameters: List[Any], objective_function: Callable[..., Any], ) -> 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: + FitResults with success flag, best chi2 and timing. + """ minimizer_name = self.name or 'Unnamed Minimizer' if self.method is not None: minimizer_name += f' ({self.method})' @@ -123,6 +159,7 @@ def _objective_function( experiments: Any, calculator: Any, ) -> np.ndarray: + """Default objective helper computing residuals array.""" return self._compute_residuals( engine_params, parameters, @@ -138,6 +175,7 @@ def _create_objective_function( experiments: Any, calculator: Any, ) -> Callable[[Dict[str, Any]], np.ndarray]: + """Return a closure capturing problem context for the solver.""" return lambda engine_params: self._objective_function( engine_params, parameters, diff --git a/src/easydiffraction/analysis/minimizers/minimizer_dfols.py b/src/easydiffraction/analysis/minimizers/dfols.py similarity index 91% rename from src/easydiffraction/analysis/minimizers/minimizer_dfols.py rename to src/easydiffraction/analysis/minimizers/dfols.py index 6ba2999d..c3b70740 100644 --- a/src/easydiffraction/analysis/minimizers/minimizer_dfols.py +++ b/src/easydiffraction/analysis/minimizers/dfols.py @@ -8,7 +8,7 @@ import numpy as np from dfols import solve -from easydiffraction.analysis.minimizers.minimizer_base import MinimizerBase +from easydiffraction.analysis.minimizers.base import MinimizerBase DEFAULT_MAX_ITERATIONS = 1000 @@ -34,8 +34,8 @@ def _prepare_solver_args(self, parameters: List[Any]) -> Dict[str, Any]: bounds_upper = [] for param in parameters: x0.append(param.value) - bounds_lower.append(param.min if param.min is not None else -np.inf) - bounds_upper.append(param.max if param.max is not None else np.inf) + bounds_lower.append(param.fit_min) + bounds_upper.append(param.fit_max) bounds = (np.array(bounds_lower), np.array(bounds_upper)) return {'x0': np.array(x0), 'bounds': bounds} diff --git a/src/easydiffraction/analysis/minimizers/minimizer_factory.py b/src/easydiffraction/analysis/minimizers/factory.py similarity index 94% rename from src/easydiffraction/analysis/minimizers/minimizer_factory.py rename to src/easydiffraction/analysis/minimizers/factory.py index 0f05ec7a..3d5d6aba 100644 --- a/src/easydiffraction/analysis/minimizers/minimizer_factory.py +++ b/src/easydiffraction/analysis/minimizers/factory.py @@ -7,9 +7,9 @@ from typing import Optional from typing import Type -from easydiffraction.analysis.minimizers.minimizer_base import MinimizerBase -from easydiffraction.analysis.minimizers.minimizer_dfols import DfolsMinimizer -from easydiffraction.analysis.minimizers.minimizer_lmfit import LmfitMinimizer +from easydiffraction.analysis.minimizers.base import MinimizerBase +from easydiffraction.analysis.minimizers.dfols import DfolsMinimizer +from easydiffraction.analysis.minimizers.lmfit import LmfitMinimizer from easydiffraction.utils.formatting import paragraph from easydiffraction.utils.utils import render_table diff --git a/src/easydiffraction/analysis/minimizers/minimizer_lmfit.py b/src/easydiffraction/analysis/minimizers/lmfit.py similarity index 91% rename from src/easydiffraction/analysis/minimizers/minimizer_lmfit.py rename to src/easydiffraction/analysis/minimizers/lmfit.py index b61c18c2..acbe2f2f 100644 --- a/src/easydiffraction/analysis/minimizers/minimizer_lmfit.py +++ b/src/easydiffraction/analysis/minimizers/lmfit.py @@ -7,7 +7,7 @@ import lmfit -from easydiffraction.analysis.minimizers.minimizer_base import MinimizerBase +from easydiffraction.analysis.minimizers.base import MinimizerBase DEFAULT_METHOD = 'leastsq' DEFAULT_MAX_ITERATIONS = 1000 @@ -44,11 +44,11 @@ def _prepare_solver_args( engine_parameters = lmfit.Parameters() for param in parameters: engine_parameters.add( - name=param.minimizer_uid, + name=param._minimizer_uid, value=param.value, vary=param.free, - min=param.min, - max=param.max, + min=param.fit_min, + max=param.fit_max, ) return {'engine_parameters': engine_parameters} @@ -86,9 +86,9 @@ def _sync_result_to_parameters( param_values = raw_result.params if hasattr(raw_result, 'params') else raw_result for param in parameters: - param_result = param_values.get(param.minimizer_uid) + param_result = param_values.get(param._minimizer_uid) if param_result is not None: - param.value = param_result.value + param._value = param_result.value # Bypass ranges check param.uncertainty = getattr(param_result, 'stderr', None) def _check_success(self, raw_result: Any) -> bool: diff --git a/src/easydiffraction/core/category.py b/src/easydiffraction/core/category.py new file mode 100644 index 00000000..2c055170 --- /dev/null +++ b/src/easydiffraction/core/category.py @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from easydiffraction.core.collection import CollectionBase +from easydiffraction.core.guard import GuardedBase +from easydiffraction.core.parameters import GenericDescriptorBase +from easydiffraction.core.validation import checktype +from easydiffraction.io.cif.serialize import category_collection_to_cif +from easydiffraction.io.cif.serialize import category_item_to_cif + + +class CategoryItem(GuardedBase): + """Base class for items in a category collection.""" + + def __str__(self) -> str: + """Human-readable representation of this component.""" + name = self._log_name + params = ', '.join(f'{p.name}={p.value!r}' for p in self.parameters) + return f'<{name} ({params})>' + + @property + def unique_name(self): + parts = [ + self._identity.datablock_entry_name, + self._identity.category_code, + self._identity.category_entry_name, + ] + return '.'.join(filter(None, parts)) + + @property + def parameters(self): + return [v for v in vars(self).values() if isinstance(v, GenericDescriptorBase)] + + @property + def as_cif(self) -> str: + """Return CIF representation of this object.""" + return category_item_to_cif(self) + + +class CategoryCollection(CollectionBase): + """Handles loop-style category containers (e.g. AtomSites). + + Each item is a CategoryItem (component). + """ + + def __str__(self) -> str: + """Human-readable representation of this component.""" + name = self._log_name + size = len(self) + return f'<{name} collection ({size} items)>' + + @property + def unique_name(self): + return None + + @property + def parameters(self): + """All parameters from all items in this collection.""" + params = [] + for item in self._items: + params.extend(item.parameters) + return params + + @property + def as_cif(self) -> str: + """Return CIF representation of this object.""" + return category_collection_to_cif(self) + + @checktype + def add(self, item) -> None: + """Add an item to the collection.""" + self[item._identity.category_entry_name] = item + + @checktype + def add_from_args(self, *args, **kwargs) -> None: + """Create and add a new child instance from the provided + arguments. + """ + child_obj = self._item_type(*args, **kwargs) + self.add(child_obj) diff --git a/src/easydiffraction/core/collection.py b/src/easydiffraction/core/collection.py new file mode 100644 index 00000000..e572827d --- /dev/null +++ b/src/easydiffraction/core/collection.py @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""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. +""" + +from __future__ import annotations + +from easydiffraction.core.guard import GuardedBase + + +class CollectionBase(GuardedBase): + """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. + """ + + def __init__(self, item_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. + + Rebuilds the internal index on a cache miss to stay consistent + with recent mutations. + """ + try: + return self._index[name] + except KeyError: + self._rebuild_index() + return self._index[name] + + def __setitem__(self, name: str, item) -> None: + """Insert or replace an item under the given identity key.""" + # Check if item with same identity exists; if so, replace it + for i, existing_item in enumerate(self._items): + if existing_item._identity.category_entry_name == name: + self._items[i] = item + self._rebuild_index() + return + # Otherwise append new item + item._parent = self # Explicitly set the parent for the item + self._items.append(item) + self._rebuild_index() + + def __delitem__(self, name: str) -> None: + """Delete an item by key or raise ``KeyError`` if missing.""" + # Remove from _items by identity entry name + for i, item in enumerate(self._items): + if item._identity.category_entry_name == name: + object.__setattr__(item, '_parent', None) # Unlink the parent before removal + del self._items[i] + self._rebuild_index() + return + raise KeyError(name) + + def __iter__(self): + """Iterate over items in insertion order.""" + return iter(self._items) + + def __len__(self) -> int: + """Return the number of items in the collection.""" + return len(self._items) + + def _key_for(self, item): + """Return the identity key for ``item`` (category or + datablock). + """ + return item._identity.category_entry_name or item._identity.datablock_entry_name + + def _rebuild_index(self) -> None: + """Rebuild the name-to-item index from the ordered item list.""" + self._index.clear() + for item in self._items: + key = self._key_for(item) + if key: + self._index[key] = item + + def keys(self): + """Yield keys for all items in insertion order.""" + return (self._key_for(item) for item in self._items) + + def values(self): + """Yield items in insertion order.""" + return (item for item in self._items) + + def items(self): + """Yield ``(key, item)`` pairs in insertion order.""" + return ((self._key_for(item), item) for item in self._items) + + @property + def names(self): + """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 new file mode 100644 index 00000000..bd6f5d9f --- /dev/null +++ b/src/easydiffraction/core/datablock.py @@ -0,0 +1,90 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typeguard import typechecked + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.collection import CollectionBase +from easydiffraction.core.guard import GuardedBase +from easydiffraction.core.parameters import Parameter +from easydiffraction.io.cif.serialize import datablock_collection_to_cif +from easydiffraction.io.cif.serialize import datablock_item_to_cif + + +class DatablockItem(GuardedBase): + """Base class for items in a datablock collection.""" + + def __str__(self) -> str: + """Human-readable representation of this component.""" + name = self._log_name + items = getattr(self, '_items', None) + return f'<{name} ({items})>' + + @property + def unique_name(self): + return self._identity.datablock_entry_name + + @property + def parameters(self): + """All parameters from all categories contained in this + datablock. + """ + params = [] + for v in vars(self).values(): + if isinstance(v, (CategoryItem, CategoryCollection)): + params.extend(v.parameters) + return params + + @property + def as_cif(self) -> str: + """Return CIF representation of this object.""" + return datablock_item_to_cif(self) + + +class DatablockCollection(CollectionBase): + """Handles top-level category collections (e.g. SampleModels, + Experiments). + + Each item is a DatablockItem. + """ + + def __str__(self) -> str: + """Human-readable representation of this component.""" + name = self._log_name + size = len(self) + return f'<{name} collection ({size} items)>' + + @property + def unique_name(self): + return None + + @property + def parameters(self): + """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: + 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: + return [p for p in self.fittable_parameters if p.free] + + @property + def as_cif(self) -> str: + """Return CIF representation of this object.""" + return datablock_collection_to_cif(self) + + @typechecked + def add(self, item) -> None: + """Add an item to the collection.""" + self[item._identity.datablock_entry_name] = item diff --git a/src/easydiffraction/core/diagnostic.py b/src/easydiffraction/core/diagnostic.py new file mode 100644 index 00000000..38da87a1 --- /dev/null +++ b/src/easydiffraction/core/diagnostic.py @@ -0,0 +1,217 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Diagnostics helpers for logging validation messages. + +This module centralizes human-friendly error and debug logs for +attribute validation and configuration checks. +""" + +import difflib + +from easydiffraction import log + + +class Diagnostics: + """Centralized logger for attribute errors and validation hints.""" + + # ============================================================== + # Configuration / definition diagnostics + # ============================================================== + + @staticmethod + def type_override_error(cls_name: str, expected, got): + """Report an invalid DataTypes override. + + Used when descriptor and AttributeSpec types conflict. + """ + expected_label = str(expected) + got_label = str(got) + msg = ( + f'Invalid type override in <{cls_name}>. ' + f'Descriptor enforces `{expected_label}`, ' + f'but AttributeSpec defines `{got_label}`.' + ) + Diagnostics._log_error(msg, exc_type=TypeError) + + # ============================================================== + # Attribute diagnostics + # ============================================================== + + @staticmethod + def readonly_error( + name: str, + key: str | None = None, + ): + """Log an attempt to change a read-only attribute.""" + Diagnostics._log_error( + f"Cannot modify read-only attribute '{key}' of <{name}>.", + exc_type=AttributeError, + ) + + @staticmethod + def attr_error( + name: str, + key: str, + allowed: set[str], + label='Allowed', + ): + """Log access to an unknown attribute 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) + Diagnostics._log_error( + f"Unknown attribute '{key}' of <{name}>.{hint}", + exc_type=AttributeError, + ) + + # ============================================================== + # Validation diagnostics + # ============================================================== + + @staticmethod + def type_mismatch( + name: str, + value, + expected_type, + current=None, + default=None, + ): + """Log a type mismatch and keep current or default value.""" + got_type = type(value).__name__ + msg = ( + f'Type mismatch for <{name}>. ' + f'Expected `{expected_type}`, got `{got_type}` ({value!r}).' + ) + Diagnostics._log_error_with_fallback( + msg, current=current, default=default, exc_type=TypeError + ) + + @staticmethod + def range_mismatch( + name: str, + value, + ge, + le, + current=None, + default=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( + msg, current=current, default=default, exc_type=TypeError + ) + + @staticmethod + def choice_mismatch( + name: str, + value, + allowed, + current=None, + default=None, + ): + """Log an invalid choice against allowed values.""" + msg = f'Value mismatch for <{name}>. Provided {value!r} is unknown.' + if allowed is not None: + msg += Diagnostics._build_allowed(allowed, label='Allowed values') + Diagnostics._log_error_with_fallback( + msg, current=current, default=default, exc_type=TypeError + ) + + @staticmethod + def regex_mismatch( + name: str, + value, + pattern, + current=None, + default=None, + ): + """Log a regex mismatch with the expected pattern.""" + msg = ( + f"Value mismatch for <{name}>. Provided {value!r} does not match pattern '{pattern}'." + ) + Diagnostics._log_error_with_fallback( + msg, current=current, default=default, exc_type=TypeError + ) + + @staticmethod + def no_value(name, default): + """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): + """Log explicit None provided by a user.""" + Diagnostics._log_debug(f'Using `None` explicitly provided for <{name}>.') + + @staticmethod + def none_value_skip_range(name): + """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): + """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.') + + # ============================================================== + # Helper log methods + # ============================================================== + + @staticmethod + def _log_error(msg, exc_type=Exception): + """Emit an error-level message via shared logger.""" + log.error(message=msg, exc_type=exc_type) + + @staticmethod + def _log_error_with_fallback( + msg, + current=None, + default=None, + exc_type=Exception, + ): + """Emit an error message and mention kept or default value.""" + if current is not None: + msg += f' Keeping current {current!r}.' + else: + msg += f' Using default {default!r}.' + log.error(message=msg, exc_type=exc_type) + + @staticmethod + def _log_debug(msg): + """Emit a debug-level message via shared logger.""" + log.debug(message=msg) + + # ============================================================== + # Suggestion and allowed value helpers + # ============================================================== + + @staticmethod + def _suggest(key: str, allowed: set[str]): + """Suggest closest allowed key using string similarity.""" + if not allowed: + return None + # Return the allowed key with smallest Levenshtein distance + matches = difflib.get_close_matches(key, allowed, n=1) + return matches[0] if matches else None + + @staticmethod + def _build_suggestion(key: str, allowed: set[str]): + s = Diagnostics._suggest(key, allowed) + return f" Did you mean '{s}'?" if s else '' + + @staticmethod + def _build_allowed(allowed, label='Allowed attributes'): + # allowed may be a set, list, or other iterable + if allowed: + allowed_list = list(allowed) + if len(allowed_list) <= 10: + s = ', '.join(map(repr, sorted(allowed_list))) + return f' {label}: {s}.' + else: + return f' ({len(allowed_list)} {label.lower()} not listed here).' + return '' diff --git a/src/easydiffraction/core/factory.py b/src/easydiffraction/core/factory.py new file mode 100644 index 00000000..cc0ecd79 --- /dev/null +++ b/src/easydiffraction/core/factory.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from typing import Iterable +from typing import Mapping + + +class FactoryBase: + """Reusable argument validation mixin.""" + + @staticmethod + def _validate_args( + present: set[str], + allowed_specs: Iterable[Mapping[str, Iterable[str]]], + factory_name: str, + ) -> None: + """Validate provided arguments against allowed combinations.""" + for spec in allowed_specs: + required = set(spec.get('required', [])) + optional = set(spec.get('optional', [])) + if required.issubset(present) and present <= (required | optional): + return # valid combo + # build readable error message + combos = [] + for spec in allowed_specs: + req = ', '.join(spec.get('required', [])) + opt = ', '.join(spec.get('optional', [])) + if opt: + combos.append(f'({req}[, {opt}])') + else: + combos.append(f'({req})') + raise ValueError( + f'Invalid argument combination for {factory_name} creation.\n' + f'Provided: {sorted(present)}\n' + f'Allowed combinations:\n ' + '\n '.join(combos) + ) diff --git a/src/easydiffraction/core/guard.py b/src/easydiffraction/core/guard.py new file mode 100644 index 00000000..f5d81ec4 --- /dev/null +++ b/src/easydiffraction/core/guard.py @@ -0,0 +1,144 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from abc import ABC +from abc import abstractmethod + +from easydiffraction.core.diagnostic import Diagnostics +from easydiffraction.core.identity import Identity + + +class GuardedBase(ABC): + """Base class enforcing controlled attribute access and parent + linkage. + """ + + # 5b: Use class-level diagnoser + _diagnoser = Diagnostics() + + def __init__(self): + self._identity = Identity(owner=self) + + def __str__(self) -> str: + return f'<{self.unique_name}>' + + def __repr__(self) -> str: + return self.__str__() + + def __getattr__(self, key: str): + cls = type(self) + allowed = cls._public_attrs() + if key not in allowed: + type(self)._diagnoser.attr_error( + self.unique_name, + key, + allowed, + label='Allowed readable/writable', + ) + + def __setattr__(self, key: str, value): + # Always allow private or special attributes without diagnostics + if key.startswith('_'): + object.__setattr__(self, key, value) + # Also maintain parent linkage for nested objects + if key != '_parent' and isinstance(value, GuardedBase): + object.__setattr__(value, '_parent', self) + return + + # Handle public attributes with diagnostics + cls = type(self) + # Prevent modification of read-only attributes + if key in cls._public_readonly_attrs(): + cls._diagnoser.readonly_error( + self.unique_name, + key, + ) + return + # Prevent assignment to unknown attributes + # Show writable attributes only as allowed + if key not in cls._public_attrs(): + allowed = cls._public_writable_attrs() + cls._diagnoser.attr_error( + self.unique_name, + key, + allowed, + label='Allowed writable', + ) + return + + self._assign_attr(key, value) + + def _assign_attr(self, key, value): + """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. + + Yields: + tuple[str, property]: Each (key, property) pair for public + attributes. + """ + for base in cls.mro(): + for key, attr in base.__dict__.items(): + if key.startswith('_') or not isinstance(attr, property): + continue + yield key, attr + + @classmethod + def _public_attrs(cls): + """All public properties (read-only + writable).""" + return {key for key, _ in cls._iter_properties()} + + @classmethod + def _public_readonly_attrs(cls): + """Public properties without a setter.""" + return {key for key, prop in cls._iter_properties() if prop.fset is None} + + @classmethod + 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): + cls = type(self) + if writable_only: + return cls._public_writable_attrs() + return cls._public_attrs() + + @property + def _log_name(self): + return type(self).__name__ + + @property + def unique_name(self): + return type(self).__name__ + + # @property + # def identity(self): + # """Expose a limited read-only view of identity attributes.""" + # return SimpleNamespace( + # datablock_entry_name=self._identity.datablock_entry_name, + # category_code=self._identity.category_code, + # category_entry_name=self._identity.category_entry_name, + # ) + + @property + @abstractmethod + def parameters(self): + """Return a list of parameter objects (to be implemented by + subclasses). + """ + raise NotImplementedError + + @property + @abstractmethod + def as_cif(self) -> str: + """Return CIF representation of this object (to be implemented + by subclasses). + """ + raise NotImplementedError diff --git a/src/easydiffraction/core/identity.py b/src/easydiffraction/core/identity.py new file mode 100644 index 00000000..90c74231 --- /dev/null +++ b/src/easydiffraction/core/identity.py @@ -0,0 +1,77 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Identity helpers to build CIF-like hierarchical names. + +Used by containers and items to expose datablock/category/entry names +without tight coupling. +""" + +from typing import Callable + + +class Identity: + """Resolve datablock/category/entry relationships lazily.""" + + def __init__( + self, + *, + owner: object, + datablock_entry: Callable | None = None, + category_code: str | None = None, + category_entry: Callable | 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): + """Resolve attribute by walking up parent chain safely.""" + if visited is None: + visited = set() + if id(self) in visited: + return None + visited.add(id(self)) + + # Direct callable or value on self + value = getattr(self, f'_{attr}', None) + if callable(value): + return value() + if isinstance(value, str): + return value + + # Climb to parent if available + parent = getattr(self._owner, '__dict__', {}).get('_parent') + if parent and hasattr(parent, '_identity'): + return parent._identity._resolve_up(attr, visited) + return None + + @property + def datablock_entry_name(self): + """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): + """Set callable returning datablock entry name.""" + self._datablock_entry = func + + @property + def category_code(self): + """Category code like 'atom_site' or 'background'.""" + return self._resolve_up('category_code') + + @category_code.setter + def category_code(self, value: str): + """Set category code value.""" + self._category_code = value + + @property + def category_entry_name(self): + """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): + """Set callable returning category entry name.""" + self._category_entry = func diff --git a/src/easydiffraction/core/objects.py b/src/easydiffraction/core/objects.py deleted file mode 100644 index edf5e675..00000000 --- a/src/easydiffraction/core/objects.py +++ /dev/null @@ -1,538 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -import secrets -import string -from abc import ABC -from abc import abstractmethod -from typing import Any -from typing import Dict -from typing import Iterator -from typing import List -from typing import Optional -from typing import TypeVar -from typing import Union - -from easydiffraction.core.singletons import UidMapHandler -from easydiffraction.utils.decorators import enforce_type -from easydiffraction.utils.formatting import error -from easydiffraction.utils.formatting import warning - -T = TypeVar('T') - - -class Descriptor: - """Base class for descriptors (non-refinable attributes).""" - - def __init__( - self, - # Value of the parameter - value: Any, - # ED parameter name (to access it in the code) - name: str, - # CIF parameter name (to show it in the CIF) - cif_name: str, - # Pretty name (to show it in the table) - pretty_name: Optional[str] = None, - # Parent datablock name - datablock_id: Optional[str] = None, - # ED parent category name - category_key: Optional[str] = None, - # CIF parent category name - cif_category_key: Optional[str] = None, - # Parent collection entry id - collection_entry_id: Optional[str] = None, - # Units of the parameter - units: Optional[str] = None, - # Description of the parameter - description: Optional[str] = None, - # If false, the parameter can never be edited. It is calculated - # automatically - editable: bool = True, - ) -> None: - self._value = value - self.name: str = name - self.cif_name: str = cif_name - self.pretty_name: Optional[str] = pretty_name - self._datablock_id: Optional[str] = datablock_id - self.category_key: Optional[str] = category_key - self.cif_category_key: Optional[str] = cif_category_key - self._collection_entry_id: Optional[str] = collection_entry_id - self.units: Optional[str] = units - self._description: Optional[str] = description - self._editable: bool = editable - - self._human_uid = self._generate_human_readable_unique_id() - - UidMapHandler.get().add_to_uid_map(self) - - def __str__(self): - # Base value string - value_str = f'{self.__class__.__name__}: {self.uid} = {self.value}' - - # Append ± uncertainty if it exists and is nonzero - if hasattr(self, 'uncertainty') and self.uncertainty != 0.0: - value_str += f' ± {self.uncertainty}' - - # Append units if available - if self.units: - value_str += f' {self.units}' - - return value_str - - def __repr__(self): - return self.__str__() - - def _generate_random_unique_id(self) -> str: - # Derived class Parameter will use this unique id for the - # minimization process to identify the parameter. It will also - # be used to create the alias for the parameter in the - # constraint expression. - length = 16 - letters = [secrets.choice(string.ascii_lowercase) for _ in range(length)] - uid = ''.join(letters) - return uid - - def _generate_human_readable_unique_id(self): - # Instead of generating a random string, we can use the - # name of the parameter and the block name to create a unique - # id. - # E.g.: - # - "block-id.category-name.parameter-name": - # "lbco.cell.length_a" - # - "block-id.category-name.entry-id.parameter-name": - # "lbco.atom_site.Ba.fract_x" - # For the analysis, we can use the same format, but without the - # datablock id. E.g.: - # - "category-name.entry-id.parameter-name": - # "alias.occ_Ba.label" - # This need to be called after the parameter is created and all - # its attributes are set. - if self.datablock_id: - uid = f'{self.datablock_id}.{self.cif_category_key}' - else: - uid = f'{self.cif_category_key}' - if self.collection_entry_id: - uid += f'.{self.collection_entry_id}' - uid += f'.{self.cif_name}' - return uid - - @property - def datablock_id(self): - return self._datablock_id - - @datablock_id.setter - def datablock_id(self, new_id): - self._datablock_id = new_id - # Update the unique id, when datablock_id attribute is of - # the parameter is changed - self.uid = self._generate_human_readable_unique_id() - - @property - def collection_entry_id(self): - return self._collection_entry_id - - @collection_entry_id.setter - def collection_entry_id(self, new_id): - self._collection_entry_id = new_id - # Update the unique id, when datablock_id attribute is of - # the parameter is changed - self.uid = self._generate_human_readable_unique_id() - - @property - def uid(self): - return self._human_uid - - @uid.setter - def uid(self, new_uid): - # Update the unique id in the global uid map - old_uid = self._human_uid - self._human_uid = new_uid - UidMapHandler.get().replace_uid(old_uid, new_uid) - - @property - def minimizer_uid(self): - return self.uid.replace('.', '__') - - @property - def value(self) -> Any: - return self._value - - @value.setter - def value(self, new_value: Any) -> None: - if self._editable: - self._value = new_value - else: - print( - warning( - f"The parameter '{self.cif_name}' it is calculated " - f'automatically and cannot be changed manually.' - ) - ) - - @property - def description(self) -> Optional[str]: - return self._description - - @property - def editable(self) -> bool: - return self._editable - - -class Parameter(Descriptor): - """A parameter with a value, uncertainty, units, and CIF - representation. - """ - - def __init__( - self, - value: Any, - name: str, - cif_name: str, - pretty_name: Optional[str] = None, - datablock_id: Optional[str] = None, # Parent datablock name - category_key: Optional[str] = None, - cif_category_key: Optional[str] = None, - collection_entry_id: Optional[str] = None, - units: Optional[str] = None, - description: Optional[str] = None, - editable: bool = True, - uncertainty: float = 0.0, - free: bool = False, - constrained: bool = False, - min_value: Optional[float] = None, - max_value: Optional[float] = None, - ) -> None: - super().__init__( - value, - name, - cif_name, - pretty_name, - datablock_id, - category_key, - cif_category_key, - collection_entry_id, - units, - description, - editable, - ) - self.uncertainty: float = ( - uncertainty # Standard uncertainty or estimated standard deviation - ) - self.free: bool = free # If the parameter is free to be fitted during the optimization - self.constrained: bool = ( - constrained # If symmetry constrains the parameter during the optimization - ) - self.min: Optional[float] = min_value # Minimum physical value of the parameter - self.max: Optional[float] = max_value # Maximum physical value of the parameter - self.start_value: Optional[Any] = None # Starting value for optimization - - -class Component(ABC): - """Base class for standard components, like Cell, Peak, etc.""" - - @property - @abstractmethod - def category_key(self): - """Must be implemented in subclasses to return the ED category - name. - - Can differ from cif_category_key. - """ - pass - - @property - @abstractmethod - def cif_category_key(self): - """Must be implemented in subclasses to return the CIF category - name. - """ - pass - - def __init__(self): - self._locked = False # If adding new attributes is locked - - self._datablock_id = None # Parent datablock name to be set by the parent - self._entry_id = None # Parent collection entry id to be set by the parent - - # TODO: Currently, it is not used. Planned to be used for - # displaying the parameters in the specific order. - self._ordered_attrs: List[str] = [] - - def __getattr__(self, name: str) -> Any: - """If the attribute is a Parameter or Descriptor, return its - value by default. - """ - attr = self.__dict__.get(name, None) - if isinstance(attr, (Descriptor, Parameter)): - return attr.value - raise AttributeError(f'{name} not found in {self}') - - def __setattr__(self, name: str, value: Any) -> None: - """If an object is locked for adding new attributes, raise an - error. - - If the attribute 'name' does not exist, add it. If the attribute - 'name' exists and is a Parameter or Descriptor, set its value. - """ - if hasattr(self, '_locked') and self._locked and not hasattr(self, name): - print(error(f"Cannot add new parameter '{name}'")) - return - - # Try to get the attribute from the instance's dictionary - attr = self.__dict__.get(name, None) - - # If the attribute is not set, and it is a Parameter or - # Descriptor, set its category_key and cif_category_key to the - # current category_key and cif_category_key and add it to the - # component. Also add its name to the list of ordered attributes - if attr is None: - if isinstance(value, (Descriptor, Parameter)): - value.category_key = self.category_key - value.cif_category_key = self.cif_category_key - self._ordered_attrs.append(name) - super().__setattr__(name, value) - # If the attribute is already set and is a Parameter or - # Descriptor, update its value. Else, allow normal reassignment - else: - if isinstance(attr, (Descriptor, Parameter)): - attr.value = value - else: - super().__setattr__(name, value) - - @property - def datablock_id(self): - return self._datablock_id - - @datablock_id.setter - def datablock_id(self, new_id): - self._datablock_id = new_id - # For each parameter in this component, also update its - # datablock_id - for param in self.get_all_params(): - param.datablock_id = new_id - - @property - def entry_id(self): - return self._entry_id - - @entry_id.setter - def entry_id(self, new_id): - self._entry_id = new_id - # For each parameter in the component, set the entry_id - for param in self.get_all_params(): - param.collection_entry_id = new_id - - def get_all_params(self): - attr_objs = [] - for attr_name in dir(self): - attr_obj = getattr(self, attr_name) - if isinstance(attr_obj, (Descriptor, Parameter)): - attr_objs.append(attr_obj) - return attr_objs - - def as_dict(self) -> Dict[str, Any]: - d = {} - - for attr_name in dir(self): - if attr_name.startswith('_'): - continue - - attr_obj = getattr(self, attr_name) - if not isinstance(attr_obj, (Descriptor, Parameter)): - continue - - key = attr_obj.cif_name - value = attr_obj.value - d[key] = value - - return d - - def as_cif(self) -> str: - if not self.cif_category_key: - raise ValueError('cif_category_key must be defined in the derived class.') - - lines = [] - - for attr_name in dir(self): - if attr_name.startswith('_'): - continue - - attr_obj = getattr(self, attr_name) - if not isinstance(attr_obj, (Descriptor, Parameter)): - continue - - key = f'_{self.cif_category_key}.{attr_obj.cif_name}' - value = attr_obj.value - - if value is None: - continue - - if isinstance(value, str) and ' ' in value: - value = f'"{value}"' - - line = f'{key} {value}' - lines.append(line) - - return '\n'.join(lines) - - -class Collection(ABC): - """Base class for collections like AtomSites, LinkedPhases, - SampleModels, Experiments, etc. - """ - - @property - @abstractmethod - def _child_class(self): - return None - - def __init__(self, parent=None): - self._parent = parent # Parent datablock - self._datablock_id = None # Parent datablock name to be set by the parent - self._items = {} - - def __getitem__(self, key: str) -> Union[Component, 'Collection']: - return self._items[key] - - def __iter__(self) -> Iterator[Union[Component, 'Collection']]: - return iter(self._items.values()) - - @property - def datablock_id(self): - return self._datablock_id - - @datablock_id.setter - def datablock_id(self, new_id): - self._datablock_id = new_id - for param in self.get_all_params(): - param.datablock_id = new_id - - def add(self, *args, **kwargs): - """Add a new item to the collection. - - The item must be a subclass of Component. - """ - if self._child_class is None: - raise ValueError('Child class is not defined.') - child_obj = self._child_class(*args, **kwargs) - # Setting the datablock_id to update its child parameters - child_obj.datablock_id = self.datablock_id - # Forcing the entry_id to be reset to update its child - # parameters - child_obj.entry_id = child_obj.entry_id - self._items[child_obj._entry_id] = child_obj - - # Call on_item_added if it exists, i.e. defined in the derived - # class - if hasattr(self, 'on_item_added'): - self.on_item_added(child_obj) - - def get_all_params(self): - params = [] - for item in self._items.values(): - if isinstance(item, Datablock): - datablock = item - for datablock_item in datablock.items(): - if isinstance(datablock_item, Component): - component = datablock_item - for param in component.get_all_params(): - params.append(param) - elif isinstance(datablock_item, Collection): - collection = datablock_item - for component in collection: - for param in component.get_all_params(): - params.append(param) - elif isinstance(item, Component): - component = item - for param in component.get_all_params(): - params.append(param) - else: - raise TypeError(f'Expected a Component or Datablock, got {type(item)}') - return params - - def get_fittable_params(self) -> List[Parameter]: - all_params = self.get_all_params() - params = [] - for param in all_params: - if hasattr(param, 'free') and not param.constrained: - params.append(param) - return params - - def get_free_params(self) -> List[Parameter]: - fittable_params = self.get_fittable_params() - params = [] - for param in fittable_params: - if param.free: - params.append(param) - return params - - def as_cif(self) -> str: - lines = [] - if self._type == 'category': - for idx, item in enumerate(self._items.values()): - params = item.as_dict() - category_key = item.cif_category_key - # Keys - keys = [f'_{category_key}.{param_key}' for param_key in params] - # Values. If the value is a string and contains spaces, - # add quotes - values = [] - for value in params.values(): - value = f'{value}' - if ' ' in value: - value = f'"{value}"' - values.append(value) - # Header is added only for the first item - if idx == 0: - lines.append('loop_') - header = '\n'.join(keys) - lines.append(header) - line = ' '.join(values) - lines.append(line) - return '\n'.join(lines) - - -class Datablock: - """Base class for Sample Model and Experiment data blocks.""" - - # TODO: Consider unifying with class Component? - - def __init__(self): - self._name = None - - def __setattr__(self, name, value): - # TODO: compare with class Component - # If the value is a Component or Collection: - # - set its datablock_id to the current datablock name - # - add it to the datablock - if isinstance(value, (Component, Collection)): - value.datablock_id = self._name - super().__setattr__(name, value) - - def items(self): - """Returns a list of both components and collections in the data - block. - """ - attr_objs = [] - for attr_name in dir(self): - if attr_name.startswith('_'): - continue - attr_obj = getattr(self, attr_name) - if isinstance(attr_obj, (Component, Collection)): - attr_objs.append(attr_obj) - return attr_objs - - @property - def name(self): - return self._name - - @name.setter - @enforce_type - def name(self, new_name: str): - self._name = new_name - # For each component/collection in this datablock, - # also update its datablock_id if it has one - for item in getattr(self, '__dict__', {}).values(): - if isinstance(item, (Component, Collection)): - item.datablock_id = new_name diff --git a/src/easydiffraction/core/parameters.py b/src/easydiffraction/core/parameters.py new file mode 100644 index 00000000..5a1c3177 --- /dev/null +++ b/src/easydiffraction/core/parameters.py @@ -0,0 +1,365 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import secrets +import string +from typing import TYPE_CHECKING +from typing import Any + +import numpy as np + +from easydiffraction.core.diagnostic import Diagnostics +from easydiffraction.core.guard import GuardedBase +from easydiffraction.core.singletons import UidMapHandler +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.core.validation import TypeValidator +from easydiffraction.io.cif.serialize import param_to_cif + +if TYPE_CHECKING: + from easydiffraction.io.cif.handler import CifHandler + + +class GenericDescriptorBase(GuardedBase): + """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. + """ + + _BOOL_SPEC_TEMPLATE = AttributeSpec( + type_=DataTypes.BOOL, + default=False, + ) + + def __init__( + self, + *, + 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. + """ + super().__init__() + + expected_type = getattr(self, '_value_type', None) + + if expected_type: + user_type = ( + value_spec._type_validator.expected_type + if value_spec._type_validator is not None + else None + ) + if user_type and user_type is not expected_type: + Diagnostics.type_override_error( + type(self).__name__, + expected_type, + user_type, + ) + else: + # Enforce descriptor's own type if not already defined + value_spec._type_validator = TypeValidator(expected_type) + + self._value_spec = value_spec + self._name = name + self._description = description + self._uid: str = self._generate_uid() + UidMapHandler.get().add_to_uid_map(self) + + # Initial validated states + self._value = self._value_spec.validated( + value_spec.value, + name=self.unique_name, + ) + + def __str__(self) -> str: + return f'<{self.unique_name} = {self.value!r}>' + + @staticmethod + def _generate_uid(length: int = 16) -> str: + letters = string.ascii_lowercase + return ''.join(secrets.choice(letters) for _ in range(length)) + + @property + def uid(self): + """Stable random identifier for this descriptor.""" + return self._uid + + @property + def name(self) -> str: + """Local name of the descriptor (without category/datablock).""" + return self._name + + @property + def unique_name(self): + """Fully qualified name including datablock, category and entry + name. + """ + # 7c: Use filter(None, [...]) + parts = [ + self._identity.datablock_entry_name, + self._identity.category_code, + self._identity.category_entry_name, + self.name, + ] + return '.'.join(filter(None, parts)) + + @property + def value(self): + """Current validated value.""" + return self._value + + @value.setter + def value(self, v): + """Set a new value after validating against the spec.""" + self._value = self._value_spec.validated( + v, + name=self.unique_name, + current=self._value, + ) + + @property + def description(self): + """Optional human-readable description.""" + return self._description + + @property + def parameters(self): + """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 + structures. + """ + return [self] + + @property + def as_cif(self) -> str: + """Serialize this descriptor to a CIF-formatted string.""" + return param_to_cif(self) + + +class GenericStringDescriptor(GenericDescriptorBase): + _value_type = DataTypes.STRING + + def __init__( + self, + **kwargs: Any, + ) -> None: + super().__init__(**kwargs) + + +class GenericNumericDescriptor(GenericDescriptorBase): + _value_type = DataTypes.NUMERIC + + def __init__( + self, + *, + units: str = '', + **kwargs: Any, + ) -> None: + super().__init__(**kwargs) + self._units: str = units + + def __str__(self) -> str: + s: str = super().__str__() + s = s[1:-1] # strip <> + if self.units: + s += f' {self.units}' + return f'<{s}>' + + @property + def units(self) -> str: + """Units associated with the numeric value, if any.""" + return self._units + + +class GenericParameter(GenericNumericDescriptor): + """Numeric descriptor extended with fitting-related attributes. + + Adds standard attributes used by minimizers: "free" flag, + uncertainty, bounds and an optional starting value. Subclasses can + integrate with specific backends while preserving this interface. + """ + + def __init__( + self, + **kwargs: Any, + ): + super().__init__(**kwargs) + + # Initial validated states + self._free_spec = self._BOOL_SPEC_TEMPLATE + self._free = self._free_spec.default + self._uncertainty_spec = AttributeSpec( + type_=DataTypes.NUMERIC, + content_validator=RangeValidator(ge=0), + allow_none=True, + ) + self._uncertainty = self._uncertainty_spec.default + self._fit_min_spec = AttributeSpec(type_=DataTypes.NUMERIC, default=-np.inf) + self._fit_min = self._fit_min_spec.default + self._fit_max_spec = AttributeSpec(type_=DataTypes.NUMERIC, default=np.inf) + self._fit_max = self._fit_max_spec.default + self._start_value_spec = AttributeSpec(type_=DataTypes.NUMERIC, default=0.0) + self._start_value = self._start_value_spec.default + self._constrained_spec = self._BOOL_SPEC_TEMPLATE + self._constrained = self._constrained_spec.default + + def __str__(self) -> str: + s = GenericDescriptorBase.__str__(self) + s = s[1:-1] # strip <> + if self.uncertainty is not None: + s += f' ± {self.uncertainty}' + if self.units is not None: + s += f' {self.units}' + s += f' (free={self.free})' + return f'<{s}>' + + @property + def _minimizer_uid(self): + """Variant of uid that is safe for minimizer engines.""" + # return self.unique_name.replace('.', '__') + return self.uid + + @property + def name(self) -> str: + """Local name of the parameter (without category/datablock).""" + return self._name + + @property + def unique_name(self): + """Fully qualified parameter name including its context path.""" + parts = [ + self._identity.datablock_entry_name, + self._identity.category_code, + self._identity.category_entry_name, + self.name, + ] + return '.'.join(filter(None, parts)) + + @property + def constrained(self): + """Whether this parameter is part of a constraint expression.""" + return self._constrained + + @property + def free(self): + """Whether this parameter is currently varied during fitting.""" + return self._free + + @free.setter + def free(self, v): + """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. + """ + return self._uncertainty + + @uncertainty.setter + def uncertainty(self, v): + """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): + """Lower fitting bound.""" + return self._fit_min + + @fit_min.setter + def fit_min(self, v): + """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): + """Upper fitting bound.""" + return self._fit_max + + @fit_max.setter + def fit_max(self, v): + """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 + ) + + +class StringDescriptor(GenericStringDescriptor): + def __init__( + self, + *, + cif_handler: CifHandler, + **kwargs: Any, + ) -> None: + """String descriptor bound to a CIF handler. + + Args: + cif_handler: Object that tracks CIF identifiers. + **kwargs: Forwarded to GenericStringDescriptor. + """ + super().__init__(**kwargs) + self._cif_handler = cif_handler + self._cif_handler.attach(self) + + +class NumericDescriptor(GenericNumericDescriptor): + def __init__( + self, + *, + cif_handler: CifHandler, + **kwargs: Any, + ) -> None: + """Numeric descriptor bound to a CIF handler. + + Args: + cif_handler: Object that tracks CIF identifiers. + **kwargs: Forwarded to GenericNumericDescriptor. + """ + super().__init__(**kwargs) + self._cif_handler = cif_handler + self._cif_handler.attach(self) + + +class Parameter(GenericParameter): + def __init__( + self, + *, + cif_handler: CifHandler, + **kwargs: Any, + ) -> None: + """Fittable parameter bound to a CIF handler. + + Args: + cif_handler: Object that tracks CIF identifiers. + **kwargs: Forwarded to GenericParameter. + """ + super().__init__(**kwargs) + self._cif_handler = cif_handler + self._cif_handler.attach(self) diff --git a/src/easydiffraction/core/singletons.py b/src/easydiffraction/core/singletons.py index 4d03b78b..b699422e 100644 --- a/src/easydiffraction/core/singletons.py +++ b/src/easydiffraction/core/singletons.py @@ -10,10 +10,10 @@ from asteval import Interpreter -T = TypeVar('T', bound='BaseSingleton') +T = TypeVar('T', bound='SingletonBase') -class BaseSingleton: +class SingletonBase: """Base class to implement Singleton pattern. Ensures only one shared instance of a class is ever created. Useful @@ -30,7 +30,7 @@ def get(cls: Type[T]) -> T: return cls._instance -class UidMapHandler(BaseSingleton): +class UidMapHandler(SingletonBase): """Global handler to manage UID-to-Parameter object mapping.""" def __init__(self) -> None: @@ -42,7 +42,18 @@ def get_uid_map(self) -> Dict[str, Any]: return self._uid_map def add_to_uid_map(self, parameter): - """Adds a single Parameter object to the UID map.""" + """Adds a single Parameter or Descriptor object to the UID map. + + Only Descriptor or Parameter instances are allowed (not + Components or others). + """ + from easydiffraction.core.parameters import GenericDescriptorBase + + if not isinstance(parameter, GenericDescriptorBase): + raise TypeError( + f'Cannot add object of type {type(parameter).__name__} to UID map. ' + 'Only Descriptor or Parameter instances are allowed.' + ) self._uid_map[parameter.uid] = parameter def replace_uid(self, old_uid, new_uid): @@ -51,17 +62,18 @@ def replace_uid(self, old_uid, new_uid): Moves the associated parameter from old_uid to new_uid. Raises a KeyError if the old_uid doesn't exist. """ - if old_uid in self._uid_map: - self._uid_map[new_uid] = self._uid_map.pop(old_uid) - else: + if old_uid not in self._uid_map: + # Only raise if old_uid is not None and not empty + print('DEBUG: replace_uid failed', old_uid, 'current map:', list(self._uid_map.keys())) raise KeyError(f"UID '{old_uid}' not found in the UID map.") + self._uid_map[new_uid] = self._uid_map.pop(old_uid) # TODO: Implement removing from the UID map # TODO: Implement changing atrr '.constrained' back to False # when removing constraints -class ConstraintsHandler(BaseSingleton): +class ConstraintsHandler(SingletonBase): """Manages user-defined parameter constraints using aliases and expressions. @@ -88,7 +100,7 @@ def set_aliases(self, aliases): Called when user registers parameter aliases like: alias='biso_La', param=model.atom_sites['La'].b_iso """ - self._alias_to_param = aliases._items + self._alias_to_param = dict(aliases.items()) def set_constraints(self, constraints): """Sets the constraints and triggers parsing into internal @@ -107,7 +119,7 @@ def _parse_constraints(self) -> None: """ self._parsed_constraints = [] - for expr_obj in self._constraints.values(): + for expr_obj in self._constraints: lhs_alias = expr_obj.lhs_alias.value rhs_expr = expr_obj.rhs_expr.value @@ -152,8 +164,8 @@ def apply(self) -> None: param = uid_map[dependent_uid] # Update its value and mark it as constrained - param.value = rhs_value - param.constrained = True + param._value = rhs_value # To bypass ranges check + param._constrained = True # To bypass read-only check except Exception as error: print(f"Failed to apply constraint '{lhs_alias} = {rhs_expr}': {error}") diff --git a/src/easydiffraction/core/validation.py b/src/easydiffraction/core/validation.py new file mode 100644 index 00000000..6bb88eae --- /dev/null +++ b/src/easydiffraction/core/validation.py @@ -0,0 +1,345 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Lightweight runtime validation utilities. + +Provides DataTypes, type/content validators, and AttributeSpec used by +descriptors and parameters. Only documentation was added here. +""" + +import functools +import re +from abc import ABC +from abc import abstractmethod +from enum import Enum +from enum import auto + +import numpy as np +from typeguard import TypeCheckError +from typeguard import typechecked + +from easydiffraction.core.diagnostic import Diagnostics +from easydiffraction.utils.logging import log + +# ============================================================== +# Shared constants +# ============================================================== + + +class DataTypes(Enum): + NUMERIC = (int, float, np.integer, np.floating, np.number) + STRING = (str,) + BOOL = (bool,) + ANY = (object,) # fallback for unconstrained + + def __str__(self): + return self.name.lower() + + @property + def expected_type(self): + """Convenience alias for tuple of allowed Python types.""" + return self.value + + +# ============================================================== +# Runtime type checking decorator +# ============================================================== + +# Runtime type checking decorator for validating those methods +# annotated with type hints, which are writable for the user, and +# which are not covered by custom validators for Parameter attribute +# types and content, implemented below. + + +def checktype(func=None, *, context=None): + """Runtime type check decorator using typeguard. + + When a TypeCheckError occurs, the error is logged and None is + returned. If context is provided, it is added to the message. + """ + + def decorator(f): + checked_func = typechecked(f) + + @functools.wraps(f) + def wrapper(*args, **kwargs): + try: + return checked_func(*args, **kwargs) + except TypeCheckError as err: + msg = str(err) + if context: + msg = f'{context}: {msg}' + log.error(message=msg, exc_type=TypeError) + return None + + return wrapper + + if func is None: + return decorator + return decorator(func) + + +# ============================================================== +# Validation stages (enum/constant) +# ============================================================== + + +class ValidationStage(Enum): + """Phases of validation for diagnostic logging.""" + + TYPE = auto() + RANGE = auto() + MEMBERSHIP = auto() + REGEX = auto() + + def __str__(self): + return self.name.lower() + + +# ============================================================== +# Advanced runtime custom validators for Parameter types/content +# ============================================================== + + +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. + + Subclasses must implement this method. + """ + raise NotImplementedError + + def _fallback( + self, + current=None, + default=None, + ): + """Return current if set, else default.""" + return current if current is not None else default + + +class TypeValidator(ValidatorBase): + """Ensure a value is of the expected Python type.""" + + def __init__(self, expected_type: DataTypes): + if isinstance(expected_type, DataTypes): + self.expected_type = expected_type + self.expected_label = str(expected_type) + else: + raise TypeError(f'TypeValidator expected a DataTypes member, got {expected_type!r}') + + def validated( + self, + value, + name, + default=None, + current=None, + allow_none=False, + ): + """Validate type and return value or fallback. + + If allow_none is True, None bypasses content checks. + """ + # Fresh initialization, use default + if current is None and value is None: + Diagnostics.no_value(name, default) + return default + + # Explicit None (allowed) + if value is None and allow_none: + Diagnostics.none_value(name) + return None + + # Normal type validation + if not isinstance(value, self.expected_type.value): + Diagnostics.type_mismatch( + name, + value, + expected_type=self.expected_label, + current=current, + default=default, + ) + return self._fallback(current, default) + + Diagnostics.validated( + name, + value, + stage=ValidationStage.TYPE, + ) + return value + + +class RangeValidator(ValidatorBase): + """Ensure a numeric value lies within [ge, le].""" + + def __init__( + self, + *, + ge=-np.inf, + le=np.inf, + ): + self.ge, self.le = ge, le + + def validated( + self, + value, + name, + default=None, + current=None, + ): + """Validate range and return value or fallback.""" + if not (self.ge <= value <= self.le): + Diagnostics.range_mismatch( + name, + value, + self.ge, + self.le, + current=current, + default=default, + ) + return self._fallback(current, default) + + Diagnostics.validated( + name, + value, + stage=ValidationStage.RANGE, + ) + return value + + +class MembershipValidator(ValidatorBase): + """Ensure that a value is among allowed choices. + + `allowed` may be an iterable or a callable returning a collection. + """ + + def __init__(self, allowed): + # Do not convert immediately to list — may be callable + self.allowed = allowed + + def validated( + self, + value, + name, + default=None, + current=None, + ): + """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 + + if value not in allowed_values: + Diagnostics.choice_mismatch( + name, + value, + allowed_values, + current=current, + default=default, + ) + return self._fallback(current, default) + + Diagnostics.validated( + name, + value, + stage=ValidationStage.MEMBERSHIP, + ) + return value + + +class RegexValidator(ValidatorBase): + """Ensure that a string matches a given regular expression.""" + + def __init__(self, pattern): + self.pattern = re.compile(pattern) + + def validated( + self, + value, + name, + default=None, + current=None, + ): + """Validate regex and return value or fallback.""" + if not self.pattern.fullmatch(value): + Diagnostics.regex_mismatch( + name, + value, + self.pattern.pattern, + current=current, + default=default, + ) + return self._fallback(current, default) + + Diagnostics.validated( + name, + value, + stage=ValidationStage.REGEX, + ) + return value + + +# ============================================================== +# Attribute specification holding metadata and validators +# ============================================================== + + +class AttributeSpec: + """Hold metadata and validators for a single attribute.""" + + def __init__( + self, + *, + value=None, + type_=None, + default=None, + content_validator=None, + allow_none: bool = False, + ): + self.value = value + self.default = default + self.allow_none = allow_none + self._type_validator = TypeValidator(type_) if type_ else None + self._content_validator = content_validator + + def validated( + self, + value, + name, + current=None, + ): + """Validate through type and content validators. + + Returns validated value, possibly default or current if errors + occur. None may short-circuit further checks when allowed. + """ + val = value + # Evaluate callable defaults dynamically + default = self.default() if callable(self.default) else self.default + + # Type validation + if self._type_validator: + val = self._type_validator.validated( + val, + name, + default=default, + current=current, + allow_none=self.allow_none, + ) + + # Skip further validation: Special case for None + if val is None and self.allow_none: + Diagnostics.none_value_skip_range(name) + return None + + # Content validation + if self._content_validator and val is not None: + val = self._content_validator.validated( + val, + name, + default=default, + current=current, + ) + + return val diff --git a/src/easydiffraction/crystallography/crystallography.py b/src/easydiffraction/crystallography/crystallography.py index 66cdc446..c5a7fc9f 100644 --- a/src/easydiffraction/crystallography/crystallography.py +++ b/src/easydiffraction/crystallography/crystallography.py @@ -46,32 +46,32 @@ def apply_cell_symmetry_constraints( a = cell['lattice_a'] cell['lattice_b'] = a cell['lattice_c'] = a - cell['angle_alpha'] = 90 - cell['angle_beta'] = 90 - cell['angle_gamma'] = 90 + cell['angle_alpha'] = 90.0 + cell['angle_beta'] = 90.0 + cell['angle_gamma'] = 90.0 elif crystal_system == 'tetragonal': a = cell['lattice_a'] cell['lattice_b'] = a - cell['angle_alpha'] = 90 - cell['angle_beta'] = 90 - cell['angle_gamma'] = 90 + cell['angle_alpha'] = 90.0 + cell['angle_beta'] = 90.0 + cell['angle_gamma'] = 90.0 elif crystal_system == 'orthorhombic': - cell['angle_alpha'] = 90 - cell['angle_beta'] = 90 - cell['angle_gamma'] = 90 + cell['angle_alpha'] = 90.0 + cell['angle_beta'] = 90.0 + cell['angle_gamma'] = 90.0 elif crystal_system in {'hexagonal', 'trigonal'}: a = cell['lattice_a'] cell['lattice_b'] = a - cell['angle_alpha'] = 90 - cell['angle_beta'] = 90 - cell['angle_gamma'] = 120 + cell['angle_alpha'] = 90.0 + cell['angle_beta'] = 90.0 + cell['angle_gamma'] = 120.0 elif crystal_system == 'monoclinic': - cell['angle_alpha'] = 90 - cell['angle_gamma'] = 90 + cell['angle_alpha'] = 90.0 + cell['angle_gamma'] = 90.0 elif crystal_system == 'triclinic': pass # No constraints to apply diff --git a/src/easydiffraction/crystallography/space_groups.py b/src/easydiffraction/crystallography/space_groups.py index d647a347..0ffa5c09 100644 --- a/src/easydiffraction/crystallography/space_groups.py +++ b/src/easydiffraction/crystallography/space_groups.py @@ -1,3 +1,12 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""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 +involved. +""" + import gzip import pickle # noqa: S403 - trusted internal pickle file (package data only) from pathlib import Path @@ -15,6 +24,7 @@ def _restricted_pickle_load(file_obj) -> Any: def _load(): + """Load space-group data from the packaged archive.""" path = Path(__file__).with_name('space_groups.pkl.gz') with gzip.open(path, 'rb') as f: return _restricted_pickle_load(f) diff --git a/src/easydiffraction/experiments/collections/__init__.py b/src/easydiffraction/experiments/categories/__init__.py similarity index 100% rename from src/easydiffraction/experiments/collections/__init__.py rename to src/easydiffraction/experiments/categories/__init__.py diff --git a/src/easydiffraction/experiments/components/__init__.py b/src/easydiffraction/experiments/categories/background/__init__.py similarity index 100% rename from src/easydiffraction/experiments/components/__init__.py rename to src/easydiffraction/experiments/categories/background/__init__.py diff --git a/src/easydiffraction/experiments/categories/background/base.py b/src/easydiffraction/experiments/categories/background/base.py new file mode 100644 index 00000000..48c4a7df --- /dev/null +++ b/src/easydiffraction/experiments/categories/background/base.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from abc import abstractmethod +from typing import Any + +from easydiffraction.core.category import CategoryCollection + + +class BackgroundBase(CategoryCollection): + """Abstract base for background subcategories in experiments. + + Concrete implementations provide parameterized background models and + compute background intensities on the experiment grid. + """ + + @abstractmethod + def calculate(self, x_data: Any) -> Any: + """Compute background values for the provided x grid. + + Args: + x_data: X positions (e.g. 2θ, TOF) at which to evaluate. + + Returns: + Background intensity array aligned with ``x_data``. + """ + pass + + # TODO: Consider moving to CategoryCollection + @abstractmethod + def show(self) -> None: + """Print a human-readable view of background components.""" + pass diff --git a/src/easydiffraction/experiments/categories/background/chebyshev.py b/src/easydiffraction/experiments/categories/background/chebyshev.py new file mode 100644 index 00000000..8bdfb9ac --- /dev/null +++ b/src/easydiffraction/experiments/categories/background/chebyshev.py @@ -0,0 +1,115 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Chebyshev polynomial background model. + +Provides a collection of polynomial terms and evaluation helpers. +""" + +from __future__ import annotations + +from typing import List +from typing import Union + +import numpy as np +from numpy.polynomial.chebyshev import chebval + +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import NumericDescriptor +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.experiments.categories.background.base import BackgroundBase +from easydiffraction.io.cif.handler import CifHandler +from easydiffraction.utils.formatting import paragraph +from easydiffraction.utils.formatting import warning +from easydiffraction.utils.utils import render_table + + +class PolynomialTerm(CategoryItem): + """Chebyshev polynomial term. + + New public attribute names: ``order`` and ``coef`` replacing the + longer ``chebyshev_order`` / ``chebyshev_coef``. Backward-compatible + aliases are kept so existing serialized data / external code does + not break immediately. Tests should migrate to the short names. + """ + + def __init__(self, *, order: int, coef: float) -> None: + super().__init__() + + # Canonical descriptors + self._order = NumericDescriptor( + name='order', + description='Order used in a Chebyshev polynomial background term', + value_spec=AttributeSpec( + value=order, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler(names=['_pd_background.Chebyshev_order']), + ) + self._coef = Parameter( + name='coef', + description='Coefficient used in a Chebyshev polynomial background term', + value_spec=AttributeSpec( + value=coef, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler(names=['_pd_background.Chebyshev_coef']), + ) + + self._identity.category_code = 'background' + self._identity.category_entry_name = lambda: str(self.order.value) + + @property + def order(self): + return self._order + + @order.setter + def order(self, value): + self._order.value = value + + @property + def coef(self): + return self._coef + + @coef.setter + def coef(self, value): + self._coef.value = value + + +class ChebyshevPolynomialBackground(BackgroundBase): + _description: str = 'Chebyshev polynomial background' + + def __init__(self): + super().__init__(item_type=PolynomialTerm) + + def calculate(self, x_data): + """Evaluate polynomial background over x_data.""" + if not self._items: + print(warning('No background points found. Setting background to zero.')) + return np.zeros_like(x_data) + + u = (x_data - x_data.min()) / (x_data.max() - x_data.min()) * 2 - 1 + coefs = [term.coef.value for term in self._items] + y_data = chebval(u, coefs) + return y_data + + def show(self) -> None: + """Print a table of polynomial orders and coefficients.""" + columns_headers: List[str] = ['Order', 'Coefficient'] + columns_alignment = ['left', 'left'] + columns_data: List[List[Union[int, float]]] = [ + [t.order.value, t.coef.value] for t in self._items + ] + + print(paragraph('Chebyshev polynomial background terms')) + render_table( + columns_headers=columns_headers, + columns_alignment=columns_alignment, + columns_data=columns_data, + ) diff --git a/src/easydiffraction/experiments/categories/background/enums.py b/src/easydiffraction/experiments/categories/background/enums.py new file mode 100644 index 00000000..4d163c52 --- /dev/null +++ b/src/easydiffraction/experiments/categories/background/enums.py @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Enumerations for background model types.""" + +from __future__ import annotations + +from enum import Enum + + +# TODO: Consider making EnumBase class with: default, description, ... +class BackgroundTypeEnum(str, Enum): + """Supported background model types.""" + + LINE_SEGMENT = 'line-segment' + CHEBYSHEV = 'chebyshev polynomial' + + @classmethod + def default(cls) -> 'BackgroundTypeEnum': + """Return a default background type.""" + return cls.LINE_SEGMENT + + def description(self) -> str: + """Human-friendly description for the enum value.""" + if self is BackgroundTypeEnum.LINE_SEGMENT: + return 'Linear interpolation between points' + elif self is BackgroundTypeEnum.CHEBYSHEV: + return 'Chebyshev polynomial background' diff --git a/src/easydiffraction/experiments/categories/background/factory.py b/src/easydiffraction/experiments/categories/background/factory.py new file mode 100644 index 00000000..2447f7a6 --- /dev/null +++ b/src/easydiffraction/experiments/categories/background/factory.py @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Background collection entry point (public facade). + +End users should import Background classes from this module. Internals +live under the package +`easydiffraction.experiments.category_collections.background_types` +and are re-exported here for a stable and readable API. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing import Optional + +from easydiffraction.experiments.categories.background.enums import BackgroundTypeEnum + +if TYPE_CHECKING: + from easydiffraction.experiments.categories.background import BackgroundBase + + +class BackgroundFactory: + """Create background collections by type.""" + + BT = BackgroundTypeEnum + + @classmethod + def _supported_map(cls) -> dict: + """Return mapping of enum values to concrete background + classes. + """ + # Lazy import to avoid circulars + from easydiffraction.experiments.categories.background.chebyshev import ( + ChebyshevPolynomialBackground, + ) + from easydiffraction.experiments.categories.background.line_segment import ( + LineSegmentBackground, + ) + + return { + cls.BT.LINE_SEGMENT: LineSegmentBackground, + cls.BT.CHEBYSHEV: ChebyshevPolynomialBackground, + } + + @classmethod + def create( + cls, + background_type: Optional[BackgroundTypeEnum] = None, + ) -> BackgroundBase: + """Instantiate a background collection of requested type. + + If type is None, the default enum value is used. + """ + if background_type is None: + background_type = BackgroundTypeEnum.default() + + supported = cls._supported_map() + if background_type not in supported: + supported_types = list(supported.keys()) + + raise ValueError( + f"Unsupported background type: '{background_type}'.\n" + f' Supported background types: {[bt.value for bt in supported_types]}' + ) + + background_class = supported[background_type] + return background_class() diff --git a/src/easydiffraction/experiments/categories/background/line_segment.py b/src/easydiffraction/experiments/categories/background/line_segment.py new file mode 100644 index 00000000..0a946550 --- /dev/null +++ b/src/easydiffraction/experiments/categories/background/line_segment.py @@ -0,0 +1,118 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Line-segment background model. + +Interpolate user-specified points to form a background curve. +""" + +from __future__ import annotations + +from typing import List + +import numpy as np +from scipy.interpolate import interp1d + +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import NumericDescriptor +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.experiments.categories.background.base import BackgroundBase +from easydiffraction.io.cif.handler import CifHandler +from easydiffraction.utils.formatting import paragraph +from easydiffraction.utils.formatting import warning +from easydiffraction.utils.utils import render_table + + +class LineSegment(CategoryItem): + """Single background control point for interpolation.""" + + def __init__(self, *, x: float, y: float): + super().__init__() + + self._x = NumericDescriptor( + name='x', + description=( + 'X-coordinates used to create many straight-line segments ' + 'representing the background in a calculated diffractogram.' + ), + value_spec=AttributeSpec( + value=x, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler(names=['_pd_background.line_segment_X']), + ) + 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' + ), + value_spec=AttributeSpec( + value=y, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), # TODO: rename to intensity + cif_handler=CifHandler(names=['_pd_background.line_segment_intensity']), + ) + + self._identity.category_code = 'background' + self._identity.category_entry_name = lambda: str(self.x.value) + + @property + def x(self): + return self._x + + @x.setter + def x(self, value): + self._x.value = value + + @property + def y(self): + return self._y + + @y.setter + def y(self, value): + self._y.value = value + + +class LineSegmentBackground(BackgroundBase): + _description: str = 'Linear interpolation between points' + + def __init__(self): + super().__init__(item_type=LineSegment) + + def calculate(self, x_data): + """Interpolate background points over x_data.""" + if not self: + print(warning('No background points found. Setting background to zero.')) + return np.zeros_like(x_data) + + background_x = np.array([point.x.value for point in self.values()]) + background_y = np.array([point.y.value for point in self.values()]) + interp_func = interp1d( + background_x, + background_y, + kind='linear', + bounds_error=False, + fill_value=(background_y[0], background_y[-1]), + ) + y_data = interp_func(x_data) + return y_data + + def show(self) -> None: + """Print a table of control points (x, intensity).""" + columns_headers: List[str] = ['X', 'Intensity'] + columns_alignment = ['left', 'left'] + columns_data: List[List[float]] = [[p.x.value, p.y.value] for p in self._items] + + print(paragraph('Line-segment background points')) + render_table( + columns_headers=columns_headers, + columns_alignment=columns_alignment, + columns_data=columns_data, + ) diff --git a/src/easydiffraction/experiments/categories/excluded_regions.py b/src/easydiffraction/experiments/categories/excluded_regions.py new file mode 100644 index 00000000..afd760c2 --- /dev/null +++ b/src/easydiffraction/experiments/categories/excluded_regions.py @@ -0,0 +1,125 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Exclude ranges of x from fitting/plotting (masked regions).""" + +from typing import List + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import NumericDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.io.cif.handler import CifHandler +from easydiffraction.utils.formatting import paragraph +from easydiffraction.utils.utils import render_table + + +class ExcludedRegion(CategoryItem): + """Closed interval [start, end] to be excluded.""" + + def __init__( + self, + *, + start: float, + end: float, + ): + super().__init__() + + self._start = NumericDescriptor( + name='start', + description='Start of the excluded region.', + value_spec=AttributeSpec( + value=start, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_excluded_region.start', + ] + ), + ) + self._end = NumericDescriptor( + name='end', + description='End of the excluded region.', + value_spec=AttributeSpec( + value=end, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_excluded_region.end', + ] + ), + ) + # self._category_entry_attr_name = f'{start}-{end}' + # self._category_entry_attr_name = self.start.name + # self.name = self.start.value + self._identity.category_code = 'excluded_regions' + self._identity.category_entry_name = lambda: self.start.value + + @property + def start(self) -> NumericDescriptor: + return self._start + + @start.setter + def start(self, value: float): + self._start.value = value + + @property + def end(self) -> NumericDescriptor: + return self._end + + @end.setter + def end(self, value: float): + self._end.value = value + + +class ExcludedRegions(CategoryCollection): + """Collection of ExcludedRegion instances.""" + + def __init__(self): + super().__init__(item_type=ExcludedRegion) + + def add(self, item: ExcludedRegion) -> None: + """Mark excluded points in the pattern when a region is + added. + """ + # 1. Call parent add first + + super().add(item) + + # 2. Now add extra behavior specific to ExcludedRegions + + datastore = self._parent.datastore + + # Boolean mask for points within the new excluded region + in_region = (datastore.full_x >= item.start.value) & (datastore.full_x <= item.end.value) + + # Update the exclusion mask + datastore.excluded[in_region] = True + + # Update the excluded points in the datastore + datastore.x = datastore.full_x[~datastore.excluded] + datastore.meas = datastore.full_meas[~datastore.excluded] + datastore.meas_su = datastore.full_meas_su[~datastore.excluded] + + def show(self) -> None: + """Print a table of excluded [start, end] intervals.""" + # TODO: Consider moving this to the base class + # to avoid code duplication with implementations in Background, + # etc. Consider using parameter names as column headers + columns_headers: List[str] = ['start', 'end'] + columns_alignment = ['left', 'left'] + columns_data: List[List[float]] = [[r.start.value, r.end.value] for r in self._items] + + print(paragraph('Excluded regions')) + render_table( + columns_headers=columns_headers, + columns_alignment=columns_alignment, + columns_data=columns_data, + ) diff --git a/src/easydiffraction/experiments/categories/experiment_type.py b/src/easydiffraction/experiments/categories/experiment_type.py new file mode 100644 index 00000000..8e94bd7e --- /dev/null +++ b/src/easydiffraction/experiments/categories/experiment_type.py @@ -0,0 +1,156 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Experiment type descriptor (form, beam, probe, scattering). + +This lightweight container stores the categorical attributes defining +an experiment configuration and handles CIF serialization via +``CifHandler``. +""" + +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import MembershipValidator +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import RadiationProbeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum +from easydiffraction.io.cif.handler import CifHandler + + +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. + """ + + def __init__( + self, + *, + sample_form: str, + beam_mode: str, + radiation_probe: str, + scattering_type: str, + ): + super().__init__() + + self._sample_form: StringDescriptor = StringDescriptor( + name='sample_form', + description='Specifies whether the diffraction data corresponds to ' + 'powder diffraction or single crystal diffraction', + value_spec=AttributeSpec( + value=sample_form, + type_=DataTypes.STRING, + default=SampleFormEnum.default(), + content_validator=MembershipValidator( + allowed=[member.value for member in SampleFormEnum] + ), + ), + cif_handler=CifHandler( + names=[ + '_expt_type.sample_form', + ] + ), + ) + + self._beam_mode: StringDescriptor = StringDescriptor( + name='beam_mode', + description='Defines whether the measurement is performed with a ' + 'constant wavelength (CW) or time-of-flight (TOF) method', + value_spec=AttributeSpec( + value=beam_mode, + type_=DataTypes.STRING, + default=BeamModeEnum.default(), + content_validator=MembershipValidator( + allowed=[member.value for member in BeamModeEnum] + ), + ), + cif_handler=CifHandler( + names=[ + '_expt_type.beam_mode', + ] + ), + ) + self._radiation_probe: StringDescriptor = StringDescriptor( + name='radiation_probe', + description='Specifies whether the measurement uses neutrons or X-rays', + value_spec=AttributeSpec( + value=radiation_probe, + type_=DataTypes.STRING, + default=RadiationProbeEnum.default(), + content_validator=MembershipValidator( + allowed=[member.value for member in RadiationProbeEnum] + ), + ), + cif_handler=CifHandler( + names=[ + '_expt_type.radiation_probe', + ] + ), + ) + self._scattering_type: StringDescriptor = 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)', + value_spec=AttributeSpec( + value=scattering_type, + type_=DataTypes.STRING, + default=ScatteringTypeEnum.default(), + content_validator=MembershipValidator( + allowed=[member.value for member in ScatteringTypeEnum] + ), + ), + cif_handler=CifHandler( + names=[ + '_expt_type.scattering_type', + ] + ), + ) + + self._identity.category_code = 'expt_type' + + @property + def sample_form(self): + """Sample form descriptor (powder/single crystal).""" + return self._sample_form + + @sample_form.setter + def sample_form(self, value): + """Set sample form value.""" + self._sample_form.value = value + + @property + def beam_mode(self): + """Beam mode descriptor (CW/TOF).""" + return self._beam_mode + + @beam_mode.setter + def beam_mode(self, value): + """Set beam mode value.""" + self._beam_mode.value = value + + @property + def radiation_probe(self): + """Radiation probe descriptor (neutrons/X-rays).""" + return self._radiation_probe + + @radiation_probe.setter + def radiation_probe(self, value): + """Set radiation probe value.""" + self._radiation_probe.value = value + + @property + def scattering_type(self): + """Scattering type descriptor (Bragg/Total).""" + return self._scattering_type + + @scattering_type.setter + def scattering_type(self, value): + """Set scattering type value.""" + self._scattering_type.value = value diff --git a/src/easydiffraction/sample_models/collections/__init__.py b/src/easydiffraction/experiments/categories/instrument/__init__.py similarity index 100% rename from src/easydiffraction/sample_models/collections/__init__.py rename to src/easydiffraction/experiments/categories/instrument/__init__.py diff --git a/src/easydiffraction/experiments/categories/instrument/base.py b/src/easydiffraction/experiments/categories/instrument/base.py new file mode 100644 index 00000000..429829d9 --- /dev/null +++ b/src/easydiffraction/experiments/categories/instrument/base.py @@ -0,0 +1,24 @@ +"""Instrument category base definitions for CWL/TOF instruments. + +This module provides the shared parent used by concrete instrument +implementations under the instrument category. +""" +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from easydiffraction.core.category import CategoryItem + + +class InstrumentBase(CategoryItem): + """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. + """ + + def __init__(self) -> None: + """Initialize instrument base and set category code.""" + super().__init__() + self._identity.category_code = 'instrument' diff --git a/src/easydiffraction/experiments/categories/instrument/cwl.py b/src/easydiffraction/experiments/categories/instrument/cwl.py new file mode 100644 index 00000000..e7d0c9cb --- /dev/null +++ b/src/easydiffraction/experiments/categories/instrument/cwl.py @@ -0,0 +1,72 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.experiments.categories.instrument.base import InstrumentBase +from easydiffraction.io.cif.handler import CifHandler + + +class CwlInstrument(InstrumentBase): + def __init__( + self, + *, + setup_wavelength=None, + calib_twotheta_offset=None, + ) -> None: + super().__init__() + + self._setup_wavelength: Parameter = Parameter( + name='wavelength', + description='Incident neutron or X-ray wavelength', + value_spec=AttributeSpec( + value=setup_wavelength, + type_=DataTypes.NUMERIC, + default=1.5406, + content_validator=RangeValidator(), + ), + units='Å', + cif_handler=CifHandler( + names=[ + '_instr.wavelength', + ] + ), + ) + self._calib_twotheta_offset: Parameter = Parameter( + name='twotheta_offset', + description='Instrument misalignment offset', + value_spec=AttributeSpec( + value=calib_twotheta_offset, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='deg', + cif_handler=CifHandler( + names=[ + '_instr.2theta_offset', + ] + ), + ) + + @property + def setup_wavelength(self): + """Incident wavelength parameter (Å).""" + return self._setup_wavelength + + @setup_wavelength.setter + def setup_wavelength(self, value): + """Set incident wavelength value (Å).""" + self._setup_wavelength.value = value + + @property + def calib_twotheta_offset(self): + """Instrument misalignment two-theta offset (deg).""" + return self._calib_twotheta_offset + + @calib_twotheta_offset.setter + def calib_twotheta_offset(self, value): + """Set two-theta offset value (deg).""" + self._calib_twotheta_offset.value = value diff --git a/src/easydiffraction/experiments/categories/instrument/factory.py b/src/easydiffraction/experiments/categories/instrument/factory.py new file mode 100644 index 00000000..db1c72ff --- /dev/null +++ b/src/easydiffraction/experiments/categories/instrument/factory.py @@ -0,0 +1,74 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Factory for instrument category items. + +Provides a stable entry point for creating instrument objects from the +experiment's scattering type and beam mode. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing import Optional +from typing import Type + +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + +if TYPE_CHECKING: + from easydiffraction.experiments.categories.instrument.base import InstrumentBase + + +class InstrumentFactory: + """Create instrument instances for supported modes. + + The factory hides implementation details and lazy-loads concrete + instrument classes to avoid circular imports. + """ + + ST = ScatteringTypeEnum + BM = BeamModeEnum + + @classmethod + def _supported_map(cls) -> dict: + # Lazy import to avoid circulars + from easydiffraction.experiments.categories.instrument.cwl import CwlInstrument + from easydiffraction.experiments.categories.instrument.tof import TofInstrument + + return { + cls.ST.BRAGG: { + cls.BM.CONSTANT_WAVELENGTH: CwlInstrument, + cls.BM.TIME_OF_FLIGHT: TofInstrument, + } + } + + @classmethod + def create( + cls, + scattering_type: Optional[ScatteringTypeEnum] = None, + beam_mode: Optional[BeamModeEnum] = None, + ) -> InstrumentBase: + if beam_mode is None: + beam_mode = BeamModeEnum.default() + if scattering_type is None: + scattering_type = ScatteringTypeEnum.default() + + supported = cls._supported_map() + + supported_scattering_types = list(supported.keys()) + if scattering_type not in supported_scattering_types: + raise ValueError( + f"Unsupported scattering type: '{scattering_type}'.\n " + f'Supported scattering types: {supported_scattering_types}' + ) + + supported_beam_modes = list(supported[scattering_type].keys()) + if beam_mode not in supported_beam_modes: + raise ValueError( + f"Unsupported beam mode: '{beam_mode}' for scattering type: " + f"'{scattering_type}'.\n " + f'Supported beam modes: {supported_beam_modes}' + ) + + instrument_class: Type[InstrumentBase] = supported[scattering_type][beam_mode] + return instrument_class() diff --git a/src/easydiffraction/experiments/categories/instrument/tof.py b/src/easydiffraction/experiments/categories/instrument/tof.py new file mode 100644 index 00000000..981a57e3 --- /dev/null +++ b/src/easydiffraction/experiments/categories/instrument/tof.py @@ -0,0 +1,153 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.experiments.categories.instrument.base import InstrumentBase +from easydiffraction.io.cif.handler import CifHandler + + +class TofInstrument(InstrumentBase): + def __init__( + self, + *, + setup_twotheta_bank=None, + calib_d_to_tof_offset=None, + calib_d_to_tof_linear=None, + calib_d_to_tof_quad=None, + calib_d_to_tof_recip=None, + ) -> None: + super().__init__() + + self._setup_twotheta_bank: Parameter = Parameter( + name='twotheta_bank', + description='Detector bank position', + value_spec=AttributeSpec( + value=setup_twotheta_bank, + type_=DataTypes.NUMERIC, + default=150.0, + content_validator=RangeValidator(), + ), + units='deg', + cif_handler=CifHandler( + names=[ + '_instr.2theta_bank', + ] + ), + ) + self._calib_d_to_tof_offset: Parameter = Parameter( + name='d_to_tof_offset', + description='TOF offset', + value_spec=AttributeSpec( + value=calib_d_to_tof_offset, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs', + cif_handler=CifHandler( + names=[ + '_instr.d_to_tof_offset', + ] + ), + ) + self._calib_d_to_tof_linear: Parameter = Parameter( + name='d_to_tof_linear', + description='TOF linear conversion', + value_spec=AttributeSpec( + value=calib_d_to_tof_linear, + type_=DataTypes.NUMERIC, + default=10000.0, + content_validator=RangeValidator(), + ), + units='µs/Å', + cif_handler=CifHandler( + names=[ + '_instr.d_to_tof_linear', + ] + ), + ) + self._calib_d_to_tof_quad: Parameter = Parameter( + name='d_to_tof_quad', + description='TOF quadratic correction', + value_spec=AttributeSpec( + value=calib_d_to_tof_quad, + type_=DataTypes.NUMERIC, + default=-0.00001, + content_validator=RangeValidator(), + ), + units='µs/Ų', + cif_handler=CifHandler( + names=[ + '_instr.d_to_tof_quad', + ] + ), + ) + self._calib_d_to_tof_recip: Parameter = Parameter( + name='d_to_tof_recip', + description='TOF reciprocal velocity correction', + value_spec=AttributeSpec( + value=calib_d_to_tof_recip, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs·Å', + cif_handler=CifHandler( + names=[ + '_instr.d_to_tof_recip', + ] + ), + ) + + @property + def setup_twotheta_bank(self): + """Detector bank two-theta position (deg).""" + return self._setup_twotheta_bank + + @setup_twotheta_bank.setter + def setup_twotheta_bank(self, value): + """Set detector bank two-theta position (deg).""" + self._setup_twotheta_bank.value = value + + @property + def calib_d_to_tof_offset(self): + """TOF offset calibration parameter (µs).""" + return self._calib_d_to_tof_offset + + @calib_d_to_tof_offset.setter + def calib_d_to_tof_offset(self, value): + """Set TOF offset (µs).""" + self._calib_d_to_tof_offset.value = value + + @property + def calib_d_to_tof_linear(self): + """Linear d to TOF conversion coefficient (µs/Å).""" + return self._calib_d_to_tof_linear + + @calib_d_to_tof_linear.setter + def calib_d_to_tof_linear(self, value): + """Set linear d to TOF coefficient (µs/Å).""" + self._calib_d_to_tof_linear.value = value + + @property + def calib_d_to_tof_quad(self): + """Quadratic d to TOF correction coefficient (µs/Ų).""" + return self._calib_d_to_tof_quad + + @calib_d_to_tof_quad.setter + def calib_d_to_tof_quad(self, value): + """Set quadratic d to TOF correction (µs/Ų).""" + self._calib_d_to_tof_quad.value = value + + @property + def calib_d_to_tof_recip(self): + """Reciprocal-velocity d to TOF correction (µs·Å).""" + return self._calib_d_to_tof_recip + + @calib_d_to_tof_recip.setter + def calib_d_to_tof_recip(self, value): + """Set reciprocal-velocity d to TOF correction (µs·Å).""" + self._calib_d_to_tof_recip.value = value diff --git a/src/easydiffraction/experiments/categories/linked_phases.py b/src/easydiffraction/experiments/categories/linked_phases.py new file mode 100644 index 00000000..9ee953a7 --- /dev/null +++ b/src/easydiffraction/experiments/categories/linked_phases.py @@ -0,0 +1,86 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Linked phases allow combining phases with scale factors.""" + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.core.validation import RegexValidator +from easydiffraction.io.cif.handler import CifHandler + + +class LinkedPhase(CategoryItem): + """Link to a phase by id with a scale factor.""" + + def __init__( + self, + *, + id: str, # TODO: need new name instead of id + scale: float, + ): + super().__init__() + + self._id = StringDescriptor( + name='id', + description='Identifier of the linked phase.', + value_spec=AttributeSpec( + value=id, + type_=DataTypes.STRING, + default='Si', + content_validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), + ), + cif_handler=CifHandler( + names=[ + '_pd_phase_block.id', + ] + ), + ) + self._scale = Parameter( + name='scale', + description='Scale factor of the linked phase.', + value_spec=AttributeSpec( + value=scale, + type_=DataTypes.NUMERIC, + default=1.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_pd_phase_block.scale', + ] + ), + ) + self._identity.category_code = 'linked_phases' + self._identity.category_entry_name = lambda: self.id.value + + @property + def id(self) -> StringDescriptor: + """Identifier of the linked phase.""" + return self._id + + @id.setter + def id(self, value: str): + """Set the linked phase identifier.""" + self._id.value = value + + @property + def scale(self) -> Parameter: + """Scale factor parameter.""" + return self._scale + + @scale.setter + def scale(self, value: float): + """Set scale factor value.""" + self._scale.value = value + + +class LinkedPhases(CategoryCollection): + """Collection of LinkedPhase instances.""" + + def __init__(self): + """Create an empty collection of linked phases.""" + super().__init__(item_type=LinkedPhase) diff --git a/src/easydiffraction/sample_models/components/__init__.py b/src/easydiffraction/experiments/categories/peak/__init__.py similarity index 100% rename from src/easydiffraction/sample_models/components/__init__.py rename to src/easydiffraction/experiments/categories/peak/__init__.py diff --git a/src/easydiffraction/experiments/categories/peak/base.py b/src/easydiffraction/experiments/categories/peak/base.py new file mode 100644 index 00000000..ce8030b9 --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/base.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Base class for peak profile categories.""" + +from easydiffraction.core.category import CategoryItem + + +class PeakBase(CategoryItem): + """Base class for peak profile categories.""" + + def __init__(self) -> None: + super().__init__() + self._identity.category_code = 'peak' diff --git a/src/easydiffraction/experiments/categories/peak/cwl.py b/src/easydiffraction/experiments/categories/peak/cwl.py new file mode 100644 index 00000000..9170c1dc --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/cwl.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Constant-wavelength peak profile classes.""" + +from easydiffraction.experiments.categories.peak.base import PeakBase +from easydiffraction.experiments.categories.peak.cwl_mixins import CwlBroadeningMixin +from easydiffraction.experiments.categories.peak.cwl_mixins import EmpiricalAsymmetryMixin +from easydiffraction.experiments.categories.peak.cwl_mixins import FcjAsymmetryMixin + + +class CwlPseudoVoigt( + PeakBase, + CwlBroadeningMixin, +): + """Constant-wavelength pseudo-Voigt peak shape.""" + + def __init__(self) -> None: + super().__init__() + self._add_constant_wavelength_broadening() + + +class CwlSplitPseudoVoigt( + PeakBase, + CwlBroadeningMixin, + EmpiricalAsymmetryMixin, +): + """Split pseudo-Voigt (empirical asymmetry) for CWL mode.""" + + def __init__(self) -> None: + super().__init__() + self._add_constant_wavelength_broadening() + self._add_empirical_asymmetry() + + +class CwlThompsonCoxHastings( + PeakBase, + CwlBroadeningMixin, + FcjAsymmetryMixin, +): + """Thompson–Cox–Hastings with FCJ asymmetry for CWL mode.""" + + def __init__(self) -> None: + super().__init__() + self._add_constant_wavelength_broadening() + self._add_fcj_asymmetry() diff --git a/src/easydiffraction/experiments/categories/peak/cwl_mixins.py b/src/easydiffraction/experiments/categories/peak/cwl_mixins.py new file mode 100644 index 00000000..d37f98d4 --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/cwl_mixins.py @@ -0,0 +1,331 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Constant-wavelength (CWL) peak-profile mixins. + +This module provides mixins that add broadening and asymmetry parameters +for constant-wavelength powder diffraction peak profiles. They are +composed into concrete peak classes elsewhere. +""" + +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.io.cif.handler import CifHandler + + +class CwlBroadeningMixin: + """Mixin that adds CWL Gaussian and Lorentz broadening + parameters. + """ + + def _add_constant_wavelength_broadening(self) -> None: + """Create CWL broadening parameters and attach them to the + class. + + Defines Gaussian (U, V, W) and Lorentz (X, Y) terms + often used in the TCH formulation. Values are stored as + ``Parameter`` objects. + """ + self._broad_gauss_u: Parameter = Parameter( + name='broad_gauss_u', + description='Gaussian broadening coefficient (dependent on ' + 'sample size and instrument resolution)', + value_spec=AttributeSpec( + value=0.01, + type_=DataTypes.NUMERIC, + default=0.01, + content_validator=RangeValidator(), + ), + units='deg²', + cif_handler=CifHandler( + names=[ + '_peak.broad_gauss_u', + ] + ), + ) + self._broad_gauss_v: Parameter = Parameter( + name='broad_gauss_v', + description='Gaussian broadening coefficient (instrumental broadening contribution)', + value_spec=AttributeSpec( + value=-0.01, + type_=DataTypes.NUMERIC, + default=-0.01, + content_validator=RangeValidator(), + ), + units='deg²', + cif_handler=CifHandler( + names=[ + '_peak.broad_gauss_v', + ] + ), + ) + self._broad_gauss_w: Parameter = Parameter( + name='broad_gauss_w', + description='Gaussian broadening coefficient (instrumental broadening contribution)', + value_spec=AttributeSpec( + value=0.02, + type_=DataTypes.NUMERIC, + default=0.02, + content_validator=RangeValidator(), + ), + units='deg²', + cif_handler=CifHandler( + names=[ + '_peak.broad_gauss_w', + ] + ), + ) + self._broad_lorentz_x: Parameter = Parameter( + name='broad_lorentz_x', + description='Lorentzian broadening coefficient (dependent on sample strain effects)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='deg', + cif_handler=CifHandler( + names=[ + '_peak.broad_lorentz_x', + ] + ), + ) + self._broad_lorentz_y: Parameter = Parameter( + name='broad_lorentz_y', + description='Lorentzian broadening coefficient (dependent on ' + 'microstructural defects and strain)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='deg', + cif_handler=CifHandler( + names=[ + '_peak.broad_lorentz_y', + ] + ), + ) + + @property + def broad_gauss_u(self) -> Parameter: + """Get Gaussian U broadening parameter.""" + return self._broad_gauss_u + + @broad_gauss_u.setter + def broad_gauss_u(self, value: float) -> None: + """Set Gaussian U broadening parameter.""" + self._broad_gauss_u.value = value + + @property + def broad_gauss_v(self) -> Parameter: + """Get Gaussian V broadening parameter.""" + return self._broad_gauss_v + + @broad_gauss_v.setter + def broad_gauss_v(self, value: float) -> None: + """Set Gaussian V broadening parameter.""" + self._broad_gauss_v.value = value + + @property + def broad_gauss_w(self) -> Parameter: + """Get Gaussian W broadening parameter.""" + return self._broad_gauss_w + + @broad_gauss_w.setter + def broad_gauss_w(self, value: float) -> None: + """Set Gaussian W broadening parameter.""" + self._broad_gauss_w.value = value + + @property + def broad_lorentz_x(self) -> Parameter: + """Get Lorentz X broadening parameter.""" + return self._broad_lorentz_x + + @broad_lorentz_x.setter + def broad_lorentz_x(self, value: float) -> None: + """Set Lorentz X broadening parameter.""" + self._broad_lorentz_x.value = value + + @property + def broad_lorentz_y(self) -> Parameter: + """Get Lorentz Y broadening parameter.""" + return self._broad_lorentz_y + + @broad_lorentz_y.setter + def broad_lorentz_y(self, value: float) -> None: + """Set Lorentz Y broadening parameter.""" + self._broad_lorentz_y.value = value + + +class EmpiricalAsymmetryMixin: + """Mixin that adds empirical CWL peak asymmetry parameters.""" + + def _add_empirical_asymmetry(self) -> None: + """Create empirical asymmetry parameters p1..p4.""" + self._asym_empir_1: Parameter = Parameter( + name='asym_empir_1', + description='Empirical asymmetry coefficient p1', + value_spec=AttributeSpec( + value=0.1, + type_=DataTypes.NUMERIC, + default=0.1, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_empir_1', + ] + ), + ) + self._asym_empir_2: Parameter = Parameter( + name='asym_empir_2', + description='Empirical asymmetry coefficient p2', + value_spec=AttributeSpec( + value=0.2, + type_=DataTypes.NUMERIC, + default=0.2, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_empir_2', + ] + ), + ) + self._asym_empir_3: Parameter = Parameter( + name='asym_empir_3', + description='Empirical asymmetry coefficient p3', + value_spec=AttributeSpec( + value=0.3, + type_=DataTypes.NUMERIC, + default=0.3, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_empir_3', + ] + ), + ) + self._asym_empir_4: Parameter = Parameter( + name='asym_empir_4', + description='Empirical asymmetry coefficient p4', + value_spec=AttributeSpec( + value=0.4, + type_=DataTypes.NUMERIC, + default=0.4, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_empir_4', + ] + ), + ) + + @property + def asym_empir_1(self) -> Parameter: + """Get empirical asymmetry coefficient p1.""" + return self._asym_empir_1 + + @asym_empir_1.setter + def asym_empir_1(self, value: float) -> None: + """Set empirical asymmetry coefficient p1.""" + self._asym_empir_1.value = value + + @property + def asym_empir_2(self) -> Parameter: + """Get empirical asymmetry coefficient p2.""" + return self._asym_empir_2 + + @asym_empir_2.setter + def asym_empir_2(self, value: float) -> None: + """Set empirical asymmetry coefficient p2.""" + self._asym_empir_2.value = value + + @property + def asym_empir_3(self) -> Parameter: + """Get empirical asymmetry coefficient p3.""" + return self._asym_empir_3 + + @asym_empir_3.setter + def asym_empir_3(self, value: float) -> None: + """Set empirical asymmetry coefficient p3.""" + self._asym_empir_3.value = value + + @property + def asym_empir_4(self) -> Parameter: + """Get empirical asymmetry coefficient p4.""" + return self._asym_empir_4 + + @asym_empir_4.setter + def asym_empir_4(self, value: float) -> None: + """Set empirical asymmetry coefficient p4.""" + self._asym_empir_4.value = value + + +class FcjAsymmetryMixin: + """Mixin that adds Finger–Cox–Jephcoat (FCJ) asymmetry params.""" + + def _add_fcj_asymmetry(self) -> None: + """Create FCJ asymmetry parameters.""" + self._asym_fcj_1: Parameter = Parameter( + name='asym_fcj_1', + description='Finger-Cox-Jephcoat asymmetry parameter 1', + value_spec=AttributeSpec( + value=0.01, + type_=DataTypes.NUMERIC, + default=0.01, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_fcj_1', + ] + ), + ) + self._asym_fcj_2: Parameter = Parameter( + name='asym_fcj_2', + description='Finger-Cox-Jephcoat asymmetry parameter 2', + value_spec=AttributeSpec( + value=0.02, + type_=DataTypes.NUMERIC, + default=0.02, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_fcj_2', + ] + ), + ) + + @property + def asym_fcj_1(self) -> Parameter: + """Get FCJ asymmetry parameter 1.""" + return self._asym_fcj_1 + + @asym_fcj_1.setter + def asym_fcj_1(self, value: float) -> None: + """Set FCJ asymmetry parameter 1.""" + self._asym_fcj_1.value = value + + @property + def asym_fcj_2(self) -> Parameter: + """Get FCJ asymmetry parameter 2.""" + return self._asym_fcj_2 + + @asym_fcj_2.setter + def asym_fcj_2(self, value: float) -> None: + """Set FCJ asymmetry parameter 2.""" + self._asym_fcj_2.value = value diff --git a/src/easydiffraction/experiments/categories/peak/factory.py b/src/easydiffraction/experiments/categories/peak/factory.py new file mode 100644 index 00000000..d96092e9 --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/factory.py @@ -0,0 +1,130 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from typing import Optional + +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import PeakProfileTypeEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + + +# TODO: Consider inheriting from FactoryBase +class PeakFactory: + """Factory for creating peak profile objects. + + Lazily imports implementations to avoid circular dependencies and + selects the appropriate class based on scattering type, beam mode + and requested profile type. + """ + + ST = ScatteringTypeEnum + BM = BeamModeEnum + PPT = PeakProfileTypeEnum + _supported = None # type: ignore[var-annotated] + + @classmethod + def _supported_map(cls): + """Return nested mapping of supported profile classes. + + Structure: + ``{ScatteringType: {BeamMode: {ProfileType: Class}}}``. + """ + # Lazy import to avoid circular imports between + # base and cw/tof/pdf modules + if cls._supported is None: + from easydiffraction.experiments.categories.peak.cwl import CwlPseudoVoigt as CwPv + from easydiffraction.experiments.categories.peak.cwl import ( + CwlSplitPseudoVoigt as CwSpv, + ) + from easydiffraction.experiments.categories.peak.cwl import ( + CwlThompsonCoxHastings as CwTch, + ) + from easydiffraction.experiments.categories.peak.tof import TofPseudoVoigt as TofPv + from easydiffraction.experiments.categories.peak.tof import ( + TofPseudoVoigtBackToBack as TofBtb, + ) + from easydiffraction.experiments.categories.peak.tof import ( + TofPseudoVoigtIkedaCarpenter as TofIc, + ) + from easydiffraction.experiments.categories.peak.total import ( + TotalGaussianDampedSinc as PdfGds, + ) + + cls._supported = { + cls.ST.BRAGG: { + cls.BM.CONSTANT_WAVELENGTH: { + cls.PPT.PSEUDO_VOIGT: CwPv, + cls.PPT.SPLIT_PSEUDO_VOIGT: CwSpv, + cls.PPT.THOMPSON_COX_HASTINGS: CwTch, + }, + cls.BM.TIME_OF_FLIGHT: { + cls.PPT.PSEUDO_VOIGT: TofPv, + cls.PPT.PSEUDO_VOIGT_IKEDA_CARPENTER: TofIc, + cls.PPT.PSEUDO_VOIGT_BACK_TO_BACK: TofBtb, + }, + }, + cls.ST.TOTAL: { + cls.BM.CONSTANT_WAVELENGTH: { + cls.PPT.GAUSSIAN_DAMPED_SINC: PdfGds, + }, + cls.BM.TIME_OF_FLIGHT: { + cls.PPT.GAUSSIAN_DAMPED_SINC: PdfGds, + }, + }, + } + return cls._supported + + @classmethod + def create( + cls, + scattering_type: Optional[ScatteringTypeEnum] = None, + beam_mode: Optional[BeamModeEnum] = None, + profile_type: Optional[PeakProfileTypeEnum] = None, + ): + """Instantiate a peak profile for the given configuration. + + Args: + scattering_type: Bragg or Total. Defaults to library + default. + beam_mode: CW or TOF. Defaults to library default. + profile_type: Concrete profile within the mode. If omitted, + a sensible default is chosen based on the other args. + + Returns: + A newly created peak profile object. + + Raises: + ValueError: If a requested option is not supported. + """ + if beam_mode is None: + beam_mode = BeamModeEnum.default() + if scattering_type is None: + scattering_type = ScatteringTypeEnum.default() + if profile_type is None: + profile_type = PeakProfileTypeEnum.default(scattering_type, beam_mode) + supported = cls._supported_map() + supported_scattering_types = list(supported.keys()) + if scattering_type not in supported_scattering_types: + raise ValueError( + f"Unsupported scattering type: '{scattering_type}'.\n" + f'Supported scattering types: {supported_scattering_types}' + ) + + supported_beam_modes = list(supported[scattering_type].keys()) + if beam_mode not in supported_beam_modes: + raise ValueError( + f"Unsupported beam mode: '{beam_mode}' for scattering type: " + f"'{scattering_type}'.\n Supported beam modes: '{supported_beam_modes}'" + ) + + supported_profile_types = list(supported[scattering_type][beam_mode].keys()) + if profile_type not in supported_profile_types: + raise ValueError( + f"Unsupported profile type '{profile_type}' for beam mode '{beam_mode}'.\n" + f'Supported profile types: {supported_profile_types}' + ) + + peak_class = supported[scattering_type][beam_mode][profile_type] + peak_obj = peak_class() + + return peak_obj diff --git a/src/easydiffraction/experiments/categories/peak/tof.py b/src/easydiffraction/experiments/categories/peak/tof.py new file mode 100644 index 00000000..c6a44512 --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/tof.py @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Time-of-flight peak profile classes.""" + +from easydiffraction.experiments.categories.peak.base import PeakBase +from easydiffraction.experiments.categories.peak.tof_mixins import IkedaCarpenterAsymmetryMixin +from easydiffraction.experiments.categories.peak.tof_mixins import TofBroadeningMixin + + +class TofPseudoVoigt( + PeakBase, + TofBroadeningMixin, +): + """Time-of-flight pseudo-Voigt peak shape.""" + + def __init__(self) -> None: + super().__init__() + self._add_time_of_flight_broadening() + + +class TofPseudoVoigtIkedaCarpenter( + PeakBase, + TofBroadeningMixin, + IkedaCarpenterAsymmetryMixin, +): + """TOF pseudo-Voigt with Ikeda–Carpenter asymmetry.""" + + def __init__(self) -> None: + super().__init__() + self._add_time_of_flight_broadening() + self._add_ikeda_carpenter_asymmetry() + + +class TofPseudoVoigtBackToBack( + PeakBase, + TofBroadeningMixin, + IkedaCarpenterAsymmetryMixin, +): + """TOF back-to-back pseudo-Voigt with asymmetry.""" + + def __init__(self) -> None: + super().__init__() + self._add_time_of_flight_broadening() + self._add_ikeda_carpenter_asymmetry() diff --git a/src/easydiffraction/experiments/categories/peak/tof_mixins.py b/src/easydiffraction/experiments/categories/peak/tof_mixins.py new file mode 100644 index 00000000..47fd4e41 --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/tof_mixins.py @@ -0,0 +1,293 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Time-of-flight (TOF) peak-profile mixins. + +Defines mixins that add Gaussian/Lorentz broadening, mixing, and +Ikeda–Carpenter asymmetry parameters used by TOF peak shapes. +""" + +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.io.cif.handler import CifHandler + + +class TofBroadeningMixin: + """Mixin that adds TOF Gaussian/Lorentz broadening and mixing + terms. + """ + + def _add_time_of_flight_broadening(self) -> None: + """Create TOF broadening and mixing parameters.""" + self._broad_gauss_sigma_0: Parameter = Parameter( + name='gauss_sigma_0', + description='Gaussian broadening coefficient (instrumental resolution)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs²', + cif_handler=CifHandler( + names=[ + '_peak.gauss_sigma_0', + ] + ), + ) + self._broad_gauss_sigma_1: Parameter = Parameter( + name='gauss_sigma_1', + description='Gaussian broadening coefficient (dependent on d-spacing)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs/Å', + cif_handler=CifHandler( + names=[ + '_peak.gauss_sigma_1', + ] + ), + ) + self._broad_gauss_sigma_2: Parameter = Parameter( + name='gauss_sigma_2', + description='Gaussian broadening coefficient (instrument-dependent term)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs²/Ų', + cif_handler=CifHandler( + names=[ + '_peak.gauss_sigma_2', + ] + ), + ) + self._broad_lorentz_gamma_0: Parameter = Parameter( + name='lorentz_gamma_0', + description='Lorentzian broadening coefficient (dependent on microstrain effects)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs', + cif_handler=CifHandler( + names=[ + '_peak.lorentz_gamma_0', + ] + ), + ) + self._broad_lorentz_gamma_1: Parameter = Parameter( + name='lorentz_gamma_1', + description='Lorentzian broadening coefficient (dependent on d-spacing)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs/Å', + cif_handler=CifHandler( + names=[ + '_peak.lorentz_gamma_1', + ] + ), + ) + self._broad_lorentz_gamma_2: Parameter = Parameter( + name='lorentz_gamma_2', + description='Lorentzian broadening coefficient (instrument-dependent term)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs²/Ų', + cif_handler=CifHandler( + names=[ + '_peak.lorentz_gamma_2', + ] + ), + ) + self._broad_mix_beta_0: Parameter = Parameter( + name='mix_beta_0', + description='Mixing parameter. Defines the ratio of Gaussian ' + 'to Lorentzian contributions in TOF profiles', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='deg', + cif_handler=CifHandler( + names=[ + '_peak.mix_beta_0', + ] + ), + ) + self._broad_mix_beta_1: Parameter = Parameter( + name='mix_beta_1', + description='Mixing parameter. Defines the ratio of Gaussian ' + 'to Lorentzian contributions in TOF profiles', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='deg', + cif_handler=CifHandler( + names=[ + '_peak.mix_beta_1', + ] + ), + ) + + @property + def broad_gauss_sigma_0(self) -> Parameter: + """Get Gaussian sigma_0 parameter.""" + return self._broad_gauss_sigma_0 + + @broad_gauss_sigma_0.setter + def broad_gauss_sigma_0(self, value: float) -> None: + """Set Gaussian sigma_0 parameter.""" + self._broad_gauss_sigma_0.value = value + + @property + def broad_gauss_sigma_1(self) -> Parameter: + """Get Gaussian sigma_1 parameter.""" + return self._broad_gauss_sigma_1 + + @broad_gauss_sigma_1.setter + def broad_gauss_sigma_1(self, value: float) -> None: + """Set Gaussian sigma_1 parameter.""" + self._broad_gauss_sigma_1.value = value + + @property + def broad_gauss_sigma_2(self) -> Parameter: + """Get Gaussian sigma_2 parameter.""" + return self._broad_gauss_sigma_2 + + @broad_gauss_sigma_2.setter + def broad_gauss_sigma_2(self, value: float) -> None: + """Set Gaussian sigma_2 parameter.""" + self._broad_gauss_sigma_2.value = value + + @property + def broad_lorentz_gamma_0(self) -> Parameter: + """Get Lorentz gamma_0 parameter.""" + return self._broad_lorentz_gamma_0 + + @broad_lorentz_gamma_0.setter + def broad_lorentz_gamma_0(self, value: float) -> None: + """Set Lorentz gamma_0 parameter.""" + self._broad_lorentz_gamma_0.value = value + + @property + def broad_lorentz_gamma_1(self) -> Parameter: + """Get Lorentz gamma_1 parameter.""" + return self._broad_lorentz_gamma_1 + + @broad_lorentz_gamma_1.setter + def broad_lorentz_gamma_1(self, value: float) -> None: + """Set Lorentz gamma_1 parameter.""" + self._broad_lorentz_gamma_1.value = value + + @property + def broad_lorentz_gamma_2(self) -> Parameter: + """Get Lorentz gamma_2 parameter.""" + return self._broad_lorentz_gamma_2 + + @broad_lorentz_gamma_2.setter + def broad_lorentz_gamma_2(self, value: float) -> None: + """Set Lorentz gamma_2 parameter.""" + self._broad_lorentz_gamma_2.value = value + + @property + def broad_mix_beta_0(self) -> Parameter: + """Get mixing parameter beta_0.""" + return self._broad_mix_beta_0 + + @broad_mix_beta_0.setter + def broad_mix_beta_0(self, value: float) -> None: + """Set mixing parameter beta_0.""" + self._broad_mix_beta_0.value = value + + @property + def broad_mix_beta_1(self) -> Parameter: + """Get mixing parameter beta_1.""" + return self._broad_mix_beta_1 + + @broad_mix_beta_1.setter + def broad_mix_beta_1(self, value: float) -> None: + """Set mixing parameter beta_1.""" + self._broad_mix_beta_1.value = value + + +class IkedaCarpenterAsymmetryMixin: + """Mixin that adds Ikeda–Carpenter asymmetry parameters.""" + + def _add_ikeda_carpenter_asymmetry(self) -> None: + """Create Ikeda–Carpenter asymmetry parameters alpha_0 and + alpha_1. + """ + self._asym_alpha_0: Parameter = Parameter( + name='asym_alpha_0', + description='Ikeda-Carpenter asymmetry parameter α₀', + value_spec=AttributeSpec( + value=0.01, + type_=DataTypes.NUMERIC, + default=0.01, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_alpha_0', + ] + ), + ) + self._asym_alpha_1: Parameter = Parameter( + name='asym_alpha_1', + description='Ikeda-Carpenter asymmetry parameter α₁', + value_spec=AttributeSpec( + value=0.02, + type_=DataTypes.NUMERIC, + default=0.02, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_alpha_1', + ] + ), + ) + + @property + def asym_alpha_0(self) -> Parameter: + """Get Ikeda–Carpenter asymmetry alpha_0.""" + return self._asym_alpha_0 + + @asym_alpha_0.setter + def asym_alpha_0(self, value: float) -> None: + """Set Ikeda–Carpenter asymmetry alpha_0.""" + self._asym_alpha_0.value = value + + @property + def asym_alpha_1(self) -> Parameter: + """Get Ikeda–Carpenter asymmetry alpha_1.""" + return self._asym_alpha_1 + + @asym_alpha_1.setter + def asym_alpha_1(self, value: float) -> None: + """Set Ikeda–Carpenter asymmetry alpha_1.""" + self._asym_alpha_1.value = value diff --git a/src/easydiffraction/experiments/categories/peak/total.py b/src/easydiffraction/experiments/categories/peak/total.py new file mode 100644 index 00000000..3b843c2e --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/total.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Total-scattering (PDF) peak profile classes.""" + +from easydiffraction.experiments.categories.peak.base import PeakBase +from easydiffraction.experiments.categories.peak.total_mixins import TotalBroadeningMixin + + +class TotalGaussianDampedSinc( + PeakBase, + TotalBroadeningMixin, +): + """Gaussian-damped sinc peak for total scattering (PDF).""" + + def __init__(self) -> None: + super().__init__() + self._add_pair_distribution_function_broadening() diff --git a/src/easydiffraction/experiments/categories/peak/total_mixins.py b/src/easydiffraction/experiments/categories/peak/total_mixins.py new file mode 100644 index 00000000..4d9b5877 --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/total_mixins.py @@ -0,0 +1,181 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Total scattering/PDF peak-profile mixins. + +Adds damping, broadening, sharpening and envelope parameters used in +pair distribution function (PDF) modeling. +""" + +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.io.cif.handler import CifHandler + + +class TotalBroadeningMixin: + """Mixin adding PDF broadening/damping/sharpening parameters.""" + + def _add_pair_distribution_function_broadening(self): + """Create PDF parameters: damp_q, broad_q, cutoff_q, + sharp deltas, and particle diameter envelope. + """ + self._damp_q: Parameter = Parameter( + name='damp_q', + description='Instrumental Q-resolution damping factor ' + '(affects high-r PDF peak amplitude)', + value_spec=AttributeSpec( + value=0.05, + type_=DataTypes.NUMERIC, + default=0.05, + content_validator=RangeValidator(), + ), + units='Å⁻¹', + cif_handler=CifHandler( + names=[ + '_peak.damp_q', + ] + ), + ) + self._broad_q: Parameter = Parameter( + name='broad_q', + description='Quadratic PDF peak broadening coefficient ' + '(thermal and model uncertainty contribution)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='Å⁻²', + cif_handler=CifHandler( + names=[ + '_peak.broad_q', + ] + ), + ) + self._cutoff_q: Parameter = Parameter( + name='cutoff_q', + description='Q-value cutoff applied to model PDF for Fourier ' + 'transform (controls real-space resolution)', + value_spec=AttributeSpec( + value=25.0, + type_=DataTypes.NUMERIC, + default=25.0, + content_validator=RangeValidator(), + ), + units='Å⁻¹', + cif_handler=CifHandler( + names=[ + '_peak.cutoff_q', + ] + ), + ) + self._sharp_delta_1: Parameter = Parameter( + name='sharp_delta_1', + description='PDF peak sharpening coefficient (1/r dependence)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='Å', + cif_handler=CifHandler( + names=[ + '_peak.sharp_delta_1', + ] + ), + ) + self._sharp_delta_2: Parameter = Parameter( + name='sharp_delta_2', + description='PDF peak sharpening coefficient (1/r² dependence)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='Ų', + cif_handler=CifHandler( + names=[ + '_peak.sharp_delta_2', + ] + ), + ) + self._damp_particle_diameter: Parameter = Parameter( + name='damp_particle_diameter', + description='Particle diameter for spherical envelope damping correction in PDF', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='Å', + cif_handler=CifHandler( + names=[ + '_peak.damp_particle_diameter', + ] + ), + ) + + @property + def damp_q(self) -> Parameter: + """Get Q-resolution damping factor.""" + return self._damp_q + + @damp_q.setter + def damp_q(self, value: float) -> None: + """Set Q-resolution damping factor.""" + self._damp_q.value = value + + @property + def broad_q(self) -> Parameter: + """Get quadratic PDF broadening coefficient.""" + return self._broad_q + + @broad_q.setter + def broad_q(self, value: float) -> None: + """Set quadratic PDF broadening coefficient.""" + self._broad_q.value = value + + @property + def cutoff_q(self) -> Parameter: + """Get Q cutoff used for Fourier transform.""" + return self._cutoff_q + + @cutoff_q.setter + def cutoff_q(self, value: float) -> None: + """Set Q cutoff used for Fourier transform.""" + self._cutoff_q.value = value + + @property + def sharp_delta_1(self) -> Parameter: + """Get sharpening coefficient with 1/r dependence.""" + return self._sharp_delta_1 + + @sharp_delta_1.setter + def sharp_delta_1(self, value: float) -> None: + """Set sharpening coefficient with 1/r dependence.""" + self._sharp_delta_1.value = value + + @property + def sharp_delta_2(self) -> Parameter: + """Get sharpening coefficient with 1/r^2 dependence.""" + return self._sharp_delta_2 + + @sharp_delta_2.setter + def sharp_delta_2(self, value: float) -> None: + """Set sharpening coefficient with 1/r^2 dependence.""" + self._sharp_delta_2.value = value + + @property + def damp_particle_diameter(self) -> Parameter: + """Get particle diameter for spherical envelope damping.""" + return self._damp_particle_diameter + + @damp_particle_diameter.setter + def damp_particle_diameter(self, value: float) -> None: + """Set particle diameter for spherical envelope damping.""" + self._damp_particle_diameter.value = value diff --git a/src/easydiffraction/experiments/collections/background.py b/src/easydiffraction/experiments/collections/background.py deleted file mode 100644 index 56c42298..00000000 --- a/src/easydiffraction/experiments/collections/background.py +++ /dev/null @@ -1,241 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from abc import abstractmethod -from enum import Enum -from typing import Dict -from typing import List -from typing import Optional -from typing import Type -from typing import Union - -import numpy as np -from numpy.polynomial.chebyshev import chebval -from scipy.interpolate import interp1d - -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor -from easydiffraction.core.objects import Parameter -from easydiffraction.utils.formatting import paragraph -from easydiffraction.utils.formatting import warning -from easydiffraction.utils.utils import render_table - - -# TODO: rename to LineSegment -class Point(Component): - @property - def category_key(self) -> str: - return 'background' - - @property - def cif_category_key(self) -> str: - return 'pd_background' - - def __init__( - self, - x: float, - y: float, - ): - super().__init__() - - self.x = Descriptor( - value=x, - name='x', - cif_name='line_segment_X', - description='X-coordinates used to create many straight-line segments ' - 'representing the background in a calculated diffractogram.', - ) - self.y = Parameter( - value=y, # TODO: rename to intensity - name='y', # TODO: rename to intensity - cif_name='line_segment_intensity', - description='Intensity used to create many straight-line segments ' - 'representing the background in a calculated diffractogram', - ) - - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = str(x) - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class PolynomialTerm(Component): - # TODO: make consistency in where to place the following properties: - # before or after the __init__ method - @property - def category_key(self) -> str: - return 'background' - - @property - def cif_category_key(self): - return 'pd_background' - - def __init__( - self, - order: int, - coef: float, - ) -> None: - super().__init__() - - self.order = Descriptor( - value=order, - name='chebyshev_order', - cif_name='Chebyshev_order', - description='The value of an order used in a Chebyshev polynomial ' - 'equation representing the background in a calculated diffractogram', - ) - self.coef = Parameter( - value=coef, - name='chebyshev_coef', - cif_name='Chebyshev_coef', - description='The value of a coefficient used in a Chebyshev polynomial ' - 'equation representing the background in a calculated diffractogram', - ) - - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = str(order) - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class BackgroundBase(Collection): - @property - def _type(self) -> str: - return 'category' # datablock or category - - @abstractmethod - def calculate(self, x_data: np.ndarray) -> np.ndarray: - pass - - @abstractmethod - def show(self) -> None: - pass - - -class LineSegmentBackground(BackgroundBase): - _description: str = 'Linear interpolation between points' - - @property - def _child_class(self) -> Type[Point]: - return Point - - def calculate(self, x_data: np.ndarray) -> np.ndarray: - """Interpolate background points over x_data.""" - if not self._items: - print(warning('No background points found. Setting background to zero.')) - return np.zeros_like(x_data) - - background_x = np.array([point.x.value for point in self._items.values()]) - background_y = np.array([point.y.value for point in self._items.values()]) - interp_func = interp1d( - background_x, - background_y, - kind='linear', - bounds_error=False, - fill_value=( - background_y[0], - background_y[-1], - ), - ) - y_data = interp_func(x_data) - return y_data - - def show(self) -> None: - columns_headers: List[str] = ['X', 'Intensity'] - columns_alignment = ['left', 'left'] - columns_data: List[List[float]] = [] - for point in self._items.values(): - x = point.x.value - y = point.y.value - columns_data.append([x, y]) - - print(paragraph('Line-segment background points')) - render_table( - columns_headers=columns_headers, - columns_alignment=columns_alignment, - columns_data=columns_data, - ) - - -class ChebyshevPolynomialBackground(BackgroundBase): - _description: str = 'Chebyshev polynomial background' - - @property - def _child_class(self) -> Type[PolynomialTerm]: - return PolynomialTerm - - def calculate(self, x_data: np.ndarray) -> np.ndarray: - """Evaluate polynomial background over x_data.""" - if not self._items: - print(warning('No background points found. Setting background to zero.')) - return np.zeros_like(x_data) - - u = (x_data - x_data.min()) / (x_data.max() - x_data.min()) * 2 - 1 # scale to [-1, 1] - coefs = [term.coef.value for term in self._items.values()] - y_data = chebval(u, coefs) - return y_data - - def show(self) -> None: - columns_headers: List[str] = ['Order', 'Coefficient'] - columns_alignment = ['left', 'left'] - columns_data: List[List[Union[int, float]]] = [] - for term in self._items.values(): - order = term.order.value - coef = term.coef.value - columns_data.append([order, coef]) - - print(paragraph('Chebyshev polynomial background terms')) - render_table( - columns_headers=columns_headers, - columns_alignment=columns_alignment, - columns_data=columns_data, - ) - - -class BackgroundTypeEnum(str, Enum): - LINE_SEGMENT = 'line-segment' - CHEBYSHEV = 'chebyshev polynomial' - - @classmethod - def default(cls) -> 'BackgroundTypeEnum': - return cls.LINE_SEGMENT - - def description(self) -> str: - if self is BackgroundTypeEnum.LINE_SEGMENT: - return 'Linear interpolation between points' - elif self is BackgroundTypeEnum.CHEBYSHEV: - return 'Chebyshev polynomial background' - - -class BackgroundFactory: - BT = BackgroundTypeEnum - _supported: Dict[BT, Type[BackgroundBase]] = { - BT.LINE_SEGMENT: LineSegmentBackground, - BT.CHEBYSHEV: ChebyshevPolynomialBackground, - } - - @classmethod - def create( - cls, - background_type: Optional[BackgroundTypeEnum] = None, - ) -> BackgroundBase: - if background_type is None: - background_type = BackgroundTypeEnum.default() - - if background_type not in cls._supported: - supported_types = list(cls._supported.keys()) - - raise ValueError( - f"Unsupported background type: '{background_type}'.\n" - f' Supported background types: {[bt.value for bt in supported_types]}' - ) - - background_class = cls._supported[background_type] - return background_class() diff --git a/src/easydiffraction/experiments/collections/excluded_regions.py b/src/easydiffraction/experiments/collections/excluded_regions.py deleted file mode 100644 index 68057e0a..00000000 --- a/src/easydiffraction/experiments/collections/excluded_regions.py +++ /dev/null @@ -1,98 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from typing import List -from typing import Type - -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor -from easydiffraction.core.objects import Parameter -from easydiffraction.utils.formatting import paragraph -from easydiffraction.utils.utils import render_table - - -class ExcludedRegion(Component): - @property - def category_key(self) -> str: - return 'excluded_regions' - - @property - def cif_category_key(self) -> str: - return 'excluded_region' - - def __init__( - self, - start: float, - end: float, - ): - super().__init__() - - self.start = Descriptor( - value=start, - name='start', - cif_name='start', - description='Start of the excluded region.', - ) - self.end = Parameter( - value=end, - name='end', - cif_name='end', - description='End of the excluded region.', - ) - - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = f'{start}-{end}' - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class ExcludedRegions(Collection): - """Collection of ExcludedRegion instances.""" - - @property - def _type(self) -> str: - return 'category' # datablock or category - - @property - def _child_class(self) -> Type[ExcludedRegion]: - return ExcludedRegion - - def on_item_added(self, item: ExcludedRegion) -> None: - """Mark excluded points in the experiment pattern when a new - region is added. - """ - datastore = self._parent.datastore - - # Boolean mask for points within the new excluded region - in_region = (datastore.full_x >= item.start.value) & (datastore.full_x <= item.end.value) - - # Update the exclusion mask - datastore.excluded[in_region] = True - - # Update the excluded points in the datastore - datastore.x = datastore.full_x[~datastore.excluded] - datastore.meas = datastore.full_meas[~datastore.excluded] - datastore.meas_su = datastore.full_meas_su[~datastore.excluded] - - def show(self) -> None: - # TODO: Consider moving this to the base class - # to avoid code duplication with implementations in Background, - # etc. Consider using parameter names as column headers - columns_headers: List[str] = ['start', 'end'] - columns_alignment = ['left', 'left'] - columns_data: List[List[float]] = [] - for region in self._items.values(): - start = region.start.value - end = region.end.value - columns_data.append([start, end]) - - print(paragraph('Excluded regions')) - render_table( - columns_headers=columns_headers, - columns_alignment=columns_alignment, - columns_data=columns_data, - ) diff --git a/src/easydiffraction/experiments/collections/linked_phases.py b/src/easydiffraction/experiments/collections/linked_phases.py deleted file mode 100644 index ad29f203..00000000 --- a/src/easydiffraction/experiments/collections/linked_phases.py +++ /dev/null @@ -1,59 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from typing import Type - -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor -from easydiffraction.core.objects import Parameter - - -class LinkedPhase(Component): - @property - def category_key(self) -> str: - return 'linked_phases' - - @property - def cif_category_key(self) -> str: - return 'pd_phase_block' - - def __init__( - self, - id: str, - scale: float, - ): - super().__init__() - - self.id = Descriptor( - value=id, - name='id', - cif_name='id', - description='Identifier of the linked phase.', - ) - self.scale = Parameter( - value=scale, - name='scale', - cif_name='scale', - description='Scale factor of the linked phase.', - ) - - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = id - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class LinkedPhases(Collection): - """Collection of LinkedPhase instances.""" - - @property - def _type(self) -> str: - return 'category' # datablock or category - - @property - def _child_class(self) -> Type[LinkedPhase]: - return LinkedPhase diff --git a/src/easydiffraction/experiments/components/experiment_type.py b/src/easydiffraction/experiments/components/experiment_type.py deleted file mode 100644 index a467a43b..00000000 --- a/src/easydiffraction/experiments/components/experiment_type.py +++ /dev/null @@ -1,119 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from enum import Enum - -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor - - -class SampleFormEnum(str, Enum): - POWDER = 'powder' - SINGLE_CRYSTAL = 'single crystal' - - @classmethod - def default(cls) -> 'SampleFormEnum': - return cls.POWDER - - def description(self) -> str: - if self is SampleFormEnum.POWDER: - return 'Powdered or polycrystalline sample.' - elif self is SampleFormEnum.SINGLE_CRYSTAL: - return 'Single crystal sample.' - - -class ScatteringTypeEnum(str, Enum): - BRAGG = 'bragg' - TOTAL = 'total' - - @classmethod - def default(cls) -> 'ScatteringTypeEnum': - return cls.BRAGG - - def description(self) -> str: - if self is ScatteringTypeEnum.BRAGG: - return 'Bragg diffraction for conventional structure refinement.' - elif self is ScatteringTypeEnum.TOTAL: - return 'Total scattering for pair distribution function analysis (PDF).' - - -class RadiationProbeEnum(str, Enum): - NEUTRON = 'neutron' - XRAY = 'xray' - - @classmethod - def default(cls) -> 'RadiationProbeEnum': - return cls.NEUTRON - - def description(self) -> str: - if self is RadiationProbeEnum.NEUTRON: - return 'Neutron diffraction.' - elif self is RadiationProbeEnum.XRAY: - return 'X-ray diffraction.' - - -class BeamModeEnum(str, Enum): - CONSTANT_WAVELENGTH = 'constant wavelength' - TIME_OF_FLIGHT = 'time-of-flight' - - @classmethod - def default(cls) -> 'BeamModeEnum': - return cls.CONSTANT_WAVELENGTH - - def description(self) -> str: - if self is BeamModeEnum.CONSTANT_WAVELENGTH: - return 'Constant wavelength (CW) diffraction.' - elif self is BeamModeEnum.TIME_OF_FLIGHT: - return 'Time-of-flight (TOF) diffraction.' - - -class ExperimentType(Component): - @property - def cif_category_key(self) -> str: - return 'expt_type' - - @property - def category_key(self) -> str: - return 'expt_type' - - def __init__( - self, - sample_form: str, - beam_mode: str, - radiation_probe: str, - scattering_type: str, - ): - super().__init__() - - self.sample_form: Descriptor = Descriptor( - value=sample_form, - name='sample_form', - cif_name='sample_form', - description='Specifies whether the diffraction data corresponds to ' - 'powder diffraction or single crystal diffraction', - ) - self.beam_mode: Descriptor = Descriptor( - value=beam_mode, - name='beam_mode', - cif_name='beam_mode', - description='Defines whether the measurement is performed with a ' - 'constant wavelength (CW) or time-of-flight (TOF) method', - ) - self.radiation_probe: Descriptor = Descriptor( - value=radiation_probe, - name='radiation_probe', - cif_name='radiation_probe', - description='Specifies whether the measurement uses neutrons or X-rays', - ) - self.scattering_type: Descriptor = Descriptor( - value=scattering_type, - name='scattering_type', - cif_name='scattering_type', - description='Specifies whether the experiment uses Bragg scattering ' - '(for conventional structure refinement) or total scattering ' - '(for pair distribution function analysis - PDF)', - ) - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True diff --git a/src/easydiffraction/experiments/components/instrument.py b/src/easydiffraction/experiments/components/instrument.py deleted file mode 100644 index 235fffae..00000000 --- a/src/easydiffraction/experiments/components/instrument.py +++ /dev/null @@ -1,141 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from typing import Optional - -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Parameter -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.components.experiment_type import ScatteringTypeEnum - - -class InstrumentBase(Component): - @property - def category_key(self) -> str: - return 'instrument' - - @property - def cif_category_key(self) -> str: - return 'instr' - - -class ConstantWavelengthInstrument(InstrumentBase): - def __init__( - self, - setup_wavelength: float = 1.5406, - calib_twotheta_offset: float = 0.0, - ) -> None: - super().__init__() - - self.setup_wavelength: Parameter = Parameter( - value=setup_wavelength, - name='wavelength', - cif_name='wavelength', - units='Å', - description='Incident neutron or X-ray wavelength', - ) - self.calib_twotheta_offset: Parameter = Parameter( - value=calib_twotheta_offset, - name='twotheta_offset', - cif_name='2theta_offset', - units='deg', - description='Instrument misalignment offset', - ) - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class TimeOfFlightInstrument(InstrumentBase): - def __init__( - self, - setup_twotheta_bank: float = 150.0, - calib_d_to_tof_offset: float = 0.0, - calib_d_to_tof_linear: float = 10000.0, - calib_d_to_tof_quad: float = -0.00001, - calib_d_to_tof_recip: float = 0.0, - ) -> None: - super().__init__() - - self.setup_twotheta_bank: Parameter = Parameter( - value=setup_twotheta_bank, - name='twotheta_bank', - cif_name='2theta_bank', - units='deg', - description='Detector bank position', - ) - self.calib_d_to_tof_offset: Parameter = Parameter( - value=calib_d_to_tof_offset, - name='d_to_tof_offset', - cif_name='d_to_tof_offset', - units='µs', - description='TOF offset', - ) - self.calib_d_to_tof_linear: Parameter = Parameter( - value=calib_d_to_tof_linear, - name='d_to_tof_linear', - cif_name='d_to_tof_linear', - units='µs/Å', - description='TOF linear conversion', - ) - self.calib_d_to_tof_quad: Parameter = Parameter( - value=calib_d_to_tof_quad, - name='d_to_tof_quad', - cif_name='d_to_tof_quad', - units='µs/Ų', - description='TOF quadratic correction', - ) - self.calib_d_to_tof_recip: Parameter = Parameter( - value=calib_d_to_tof_recip, - name='d_to_tof_recip', - cif_name='d_to_tof_recip', - units='µs·Å', - description='TOF reciprocal velocity correction', - ) - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class InstrumentFactory: - ST = ScatteringTypeEnum - BM = BeamModeEnum - _supported = { - ST.BRAGG: { - BM.CONSTANT_WAVELENGTH: ConstantWavelengthInstrument, - BM.TIME_OF_FLIGHT: TimeOfFlightInstrument, - } - } - - @classmethod - def create( - cls, - scattering_type: Optional[ScatteringTypeEnum] = None, - beam_mode: Optional[BeamModeEnum] = None, - ): - if beam_mode is None: - beam_mode = BeamModeEnum.default() - if scattering_type is None: - scattering_type = ScatteringTypeEnum.default() - - supported_scattering_types = list(cls._supported.keys()) - if scattering_type not in supported_scattering_types: - raise ValueError( - f"Unsupported scattering type: '{scattering_type}'.\n " - f'Supported scattering types: {supported_scattering_types}' - ) - - supported_beam_modes = list(cls._supported[scattering_type].keys()) - if beam_mode not in supported_beam_modes: - raise ValueError( - f"Unsupported beam mode: '{beam_mode}' for scattering type: " - f"'{scattering_type}'.\n " - f'Supported beam modes: {supported_beam_modes}' - ) - - instrument_class = cls._supported[scattering_type][beam_mode] - instrument_obj = instrument_class() - - return instrument_obj diff --git a/src/easydiffraction/experiments/components/peak.py b/src/easydiffraction/experiments/components/peak.py deleted file mode 100644 index 34c29b89..00000000 --- a/src/easydiffraction/experiments/components/peak.py +++ /dev/null @@ -1,466 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause -from enum import Enum -from typing import Optional - -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Parameter -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.components.experiment_type import ScatteringTypeEnum - - -class PeakProfileTypeEnum(str, Enum): - PSEUDO_VOIGT = 'pseudo-voigt' - SPLIT_PSEUDO_VOIGT = 'split pseudo-voigt' - THOMPSON_COX_HASTINGS = 'thompson-cox-hastings' - PSEUDO_VOIGT_IKEDA_CARPENTER = 'pseudo-voigt * ikeda-carpenter' - PSEUDO_VOIGT_BACK_TO_BACK = 'pseudo-voigt * back-to-back' - GAUSSIAN_DAMPED_SINC = 'gaussian-damped-sinc' - - @classmethod - def default( - cls, - scattering_type: ScatteringTypeEnum | None = None, - beam_mode: BeamModeEnum | None = None, - ) -> 'PeakProfileTypeEnum': - if scattering_type is None: - scattering_type = ScatteringTypeEnum.default() - if beam_mode is None: - beam_mode = BeamModeEnum.default() - - return { - ( - ScatteringTypeEnum.BRAGG, - BeamModeEnum.CONSTANT_WAVELENGTH, - ): cls.PSEUDO_VOIGT, - ( - ScatteringTypeEnum.BRAGG, - BeamModeEnum.TIME_OF_FLIGHT, - ): cls.PSEUDO_VOIGT_IKEDA_CARPENTER, - ( - ScatteringTypeEnum.TOTAL, - BeamModeEnum.CONSTANT_WAVELENGTH, - ): cls.GAUSSIAN_DAMPED_SINC, - ( - ScatteringTypeEnum.TOTAL, - BeamModeEnum.TIME_OF_FLIGHT, - ): cls.GAUSSIAN_DAMPED_SINC, - }[(scattering_type, beam_mode)] - - def description(self) -> str: - if self is PeakProfileTypeEnum.PSEUDO_VOIGT: - return 'Pseudo-Voigt profile' - elif self is PeakProfileTypeEnum.SPLIT_PSEUDO_VOIGT: - return 'Split pseudo-Voigt profile with empirical asymmetry correction.' - elif self is PeakProfileTypeEnum.THOMPSON_COX_HASTINGS: - return 'Thompson-Cox-Hastings profile with FCJ asymmetry correction.' - elif self is PeakProfileTypeEnum.PSEUDO_VOIGT_IKEDA_CARPENTER: - return 'Pseudo-Voigt profile with Ikeda-Carpenter asymmetry correction.' - elif self is PeakProfileTypeEnum.PSEUDO_VOIGT_BACK_TO_BACK: - return 'Pseudo-Voigt profile with Back-to-Back Exponential asymmetry correction.' - elif self is PeakProfileTypeEnum.GAUSSIAN_DAMPED_SINC: - return 'Gaussian-damped sinc profile for pair distribution function (PDF) analysis.' - - -# --- Mixins --- -class ConstantWavelengthBroadeningMixin: - def _add_constant_wavelength_broadening(self) -> None: - self.broad_gauss_u: Parameter = Parameter( - value=0.01, - name='broad_gauss_u', - cif_name='broad_gauss_u', - units='deg²', - description='Gaussian broadening coefficient (dependent on ' - 'sample size and instrument resolution)', - ) - self.broad_gauss_v: Parameter = Parameter( - value=-0.01, - name='broad_gauss_v', - cif_name='broad_gauss_v', - units='deg²', - description='Gaussian broadening coefficient (instrumental broadening contribution)', - ) - self.broad_gauss_w: Parameter = Parameter( - value=0.02, - name='broad_gauss_w', - cif_name='broad_gauss_w', - units='deg²', - description='Gaussian broadening coefficient (instrumental broadening contribution)', - ) - self.broad_lorentz_x: Parameter = Parameter( - value=0.0, - name='broad_lorentz_x', - cif_name='broad_lorentz_x', - units='deg', - description='Lorentzian broadening coefficient (dependent on sample strain effects)', - ) - self.broad_lorentz_y: Parameter = Parameter( - value=0.0, - name='broad_lorentz_y', - cif_name='broad_lorentz_y', - units='deg', - description='Lorentzian broadening coefficient (dependent on ' - 'microstructural defects and strain)', - ) - - -class TimeOfFlightBroadeningMixin: - def _add_time_of_flight_broadening(self) -> None: - self.broad_gauss_sigma_0: Parameter = Parameter( - value=0.0, - name='gauss_sigma_0', - cif_name='gauss_sigma_0', - units='µs²', - description='Gaussian broadening coefficient (instrumental resolution)', - ) - self.broad_gauss_sigma_1: Parameter = Parameter( - value=0.0, - name='gauss_sigma_1', - cif_name='gauss_sigma_1', - units='µs/Å', - description='Gaussian broadening coefficient (dependent on d-spacing)', - ) - self.broad_gauss_sigma_2: Parameter = Parameter( - value=0.0, - name='gauss_sigma_2', - cif_name='gauss_sigma_2', - units='µs²/Ų', - description='Gaussian broadening coefficient (instrument-dependent term)', - ) - self.broad_lorentz_gamma_0: Parameter = Parameter( - value=0.0, - name='lorentz_gamma_0', - cif_name='lorentz_gamma_0', - units='µs', - description='Lorentzian broadening coefficient (dependent on microstrain effects)', - ) - self.broad_lorentz_gamma_1: Parameter = Parameter( - value=0.0, - name='lorentz_gamma_1', - cif_name='lorentz_gamma_1', - units='µs/Å', - description='Lorentzian broadening coefficient (dependent on d-spacing)', - ) - self.broad_lorentz_gamma_2: Parameter = Parameter( - value=0.0, - name='lorentz_gamma_2', - cif_name='lorentz_gamma_2', - units='µs²/Ų', - description='Lorentzian broadening coefficient (instrumental-dependent term)', - ) - self.broad_mix_beta_0: Parameter = Parameter( - value=0.0, - name='mix_beta_0', - cif_name='mix_beta_0', - units='deg', - description='Mixing parameter. Defines the ratio of Gaussian ' - 'to Lorentzian contributions in TOF profiles', - ) - self.broad_mix_beta_1: Parameter = Parameter( - value=0.0, - name='mix_beta_1', - cif_name='mix_beta_1', - units='deg', - description='Mixing parameter. Defines the ratio of Gaussian ' - 'to Lorentzian contributions in TOF profiles', - ) - - -class EmpiricalAsymmetryMixin: - def _add_empirical_asymmetry(self) -> None: - self.asym_empir_1: Parameter = Parameter( - value=0.1, - name='asym_empir_1', - cif_name='asym_empir_1', - units='', - description='Empirical asymmetry coefficient p1', - ) - self.asym_empir_2: Parameter = Parameter( - value=0.2, - name='asym_empir_2', - cif_name='asym_empir_2', - units='', - description='Empirical asymmetry coefficient p2', - ) - self.asym_empir_3: Parameter = Parameter( - value=0.3, - name='asym_empir_3', - cif_name='asym_empir_3', - units='', - description='Empirical asymmetry coefficient p3', - ) - self.asym_empir_4: Parameter = Parameter( - value=0.4, - name='asym_empir_4', - cif_name='asym_empir_4', - units='', - description='Empirical asymmetry coefficient p4', - ) - - -class FcjAsymmetryMixin: - def _add_fcj_asymmetry(self) -> None: - self.asym_fcj_1: Parameter = Parameter( - value=0.01, - name='asym_fcj_1', - cif_name='asym_fcj_1', - units='', - description='FCJ asymmetry coefficient 1', - ) - self.asym_fcj_2: Parameter = Parameter( - value=0.02, - name='asym_fcj_2', - cif_name='asym_fcj_2', - units='', - description='FCJ asymmetry coefficient 2', - ) - - -class IkedaCarpenterAsymmetryMixin: - def _add_ikeda_carpenter_asymmetry(self) -> None: - self.asym_alpha_0: Parameter = Parameter( - value=0.01, - name='asym_alpha_0', - cif_name='asym_alpha_0', - units='', - description='Ikeda-Carpenter asymmetry parameter α₀', - ) - self.asym_alpha_1: Parameter = Parameter( - value=0.02, - name='asym_alpha_1', - cif_name='asym_alpha_1', - units='', - description='Ikeda-Carpenter asymmetry parameter α₁', - ) - - -class PairDistributionFunctionBroadeningMixin: - def _add_pair_distribution_function_broadening(self): - self.damp_q = Parameter( - value=0.05, - name='damp_q', - cif_name='damp_q', - units='Å⁻¹', - description='Instrumental Q-resolution damping factor ' - '(affects high-r PDF peak amplitude)', - ) - self.broad_q = Parameter( - value=0.0, - name='broad_q', - cif_name='broad_q', - units='Å⁻²', - description='Quadratic PDF peak broadening coefficient ' - '(thermal and model uncertainty contribution)', - ) - self.cutoff_q = Parameter( - value=25.0, - name='cutoff_q', - cif_name='cutoff_q', - units='Å⁻¹', - description='Q-value cutoff applied to model PDF for Fourier ' - 'transform (controls real-space resolution)', - ) - self.sharp_delta_1 = Parameter( - value=0.0, - name='sharp_delta_1', - cif_name='sharp_delta_1', - units='Å', - description='PDF peak sharpening coefficient (1/r dependence)', - ) - self.sharp_delta_2 = Parameter( - value=0.0, - name='sharp_delta_2', - cif_name='sharp_delta_2', - units='Ų', - description='PDF peak sharpening coefficient (1/r² dependence)', - ) - self.damp_particle_diameter = Parameter( - value=0.0, - name='damp_particle_diameter', - cif_name='damp_particle_diameter', - units='Å', - description='Particle diameter for spherical envelope damping correction in PDF', - ) - - -# --- Base peak class --- -class PeakBase(Component): - @property - def category_key(self) -> str: - return 'peak' - - @property - def cif_category_key(self) -> str: - return 'peak' - - -# --- Derived peak classes --- -class ConstantWavelengthPseudoVoigt( - PeakBase, - ConstantWavelengthBroadeningMixin, -): - def __init__(self) -> None: - super().__init__() - - self._add_constant_wavelength_broadening() - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class ConstantWavelengthSplitPseudoVoigt( - PeakBase, - ConstantWavelengthBroadeningMixin, - EmpiricalAsymmetryMixin, -): - def __init__(self) -> None: - super().__init__() - - self._add_constant_wavelength_broadening() - self._add_empirical_asymmetry() - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class ConstantWavelengthThompsonCoxHastings( - PeakBase, - ConstantWavelengthBroadeningMixin, - FcjAsymmetryMixin, -): - def __init__(self) -> None: - super().__init__() - - self._add_constant_wavelength_broadening() - self._add_fcj_asymmetry() - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class TimeOfFlightPseudoVoigt( - PeakBase, - TimeOfFlightBroadeningMixin, -): - def __init__(self) -> None: - super().__init__() - - self._add_time_of_flight_broadening() - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class TimeOfFlightPseudoVoigtIkedaCarpenter( - PeakBase, - TimeOfFlightBroadeningMixin, - IkedaCarpenterAsymmetryMixin, -): - def __init__(self) -> None: - super().__init__() - - self._add_time_of_flight_broadening() - self._add_ikeda_carpenter_asymmetry() - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class TimeOfFlightPseudoVoigtBackToBack( - PeakBase, - TimeOfFlightBroadeningMixin, - IkedaCarpenterAsymmetryMixin, -): - def __init__(self) -> None: - super().__init__() - - self._add_time_of_flight_broadening() - self._add_ikeda_carpenter_asymmetry() - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class PairDistributionFunctionGaussianDampedSinc( - PeakBase, - PairDistributionFunctionBroadeningMixin, -): - def __init__(self): - super().__init__() - self._add_pair_distribution_function_broadening() - self._locked = True # Lock further attribute additions - - -# --- Peak factory --- -class PeakFactory: - ST = ScatteringTypeEnum - BM = BeamModeEnum - PPT = PeakProfileTypeEnum - _supported = { - ST.BRAGG: { - BM.CONSTANT_WAVELENGTH: { - PPT.PSEUDO_VOIGT: ConstantWavelengthPseudoVoigt, - PPT.SPLIT_PSEUDO_VOIGT: ConstantWavelengthSplitPseudoVoigt, - PPT.THOMPSON_COX_HASTINGS: ConstantWavelengthThompsonCoxHastings, - }, - BM.TIME_OF_FLIGHT: { - PPT.PSEUDO_VOIGT: TimeOfFlightPseudoVoigt, - PPT.PSEUDO_VOIGT_IKEDA_CARPENTER: TimeOfFlightPseudoVoigtIkedaCarpenter, - PPT.PSEUDO_VOIGT_BACK_TO_BACK: TimeOfFlightPseudoVoigtBackToBack, - }, - }, - ST.TOTAL: { - BM.CONSTANT_WAVELENGTH: { - PPT.GAUSSIAN_DAMPED_SINC: PairDistributionFunctionGaussianDampedSinc, - }, - BM.TIME_OF_FLIGHT: { - PPT.GAUSSIAN_DAMPED_SINC: PairDistributionFunctionGaussianDampedSinc, - }, - }, - } - - @classmethod - def create( - cls, - scattering_type: Optional[ScatteringTypeEnum] = None, - beam_mode: Optional[BeamModeEnum] = None, - profile_type: Optional[PeakProfileTypeEnum] = None, - ): - if beam_mode is None: - beam_mode = BeamModeEnum.default() - if scattering_type is None: - scattering_type = ScatteringTypeEnum.default() - if profile_type is None: - profile_type = PeakProfileTypeEnum.default(scattering_type, beam_mode) - - supported_scattering_types = list(cls._supported.keys()) - if scattering_type not in supported_scattering_types: - raise ValueError( - f"Unsupported scattering type: '{scattering_type}'.\n" - f'Supported scattering types: {supported_scattering_types}' - ) - - supported_beam_modes = list(cls._supported[scattering_type].keys()) - if beam_mode not in supported_beam_modes: - raise ValueError( - f"Unsupported beam mode: '{beam_mode}' for scattering type: " - f"'{scattering_type}'.\n Supported beam modes: '{supported_beam_modes}'" - ) - - supported_profile_types = list(cls._supported[scattering_type][beam_mode].keys()) - if profile_type not in supported_profile_types: - raise ValueError( - f"Unsupported profile type '{profile_type}' for beam mode '{beam_mode}'.\n" - f'Supported profile types: {supported_profile_types}' - ) - - peak_class = cls._supported[scattering_type][beam_mode][profile_type] - peak_obj = peak_class() - - return peak_obj diff --git a/src/easydiffraction/experiments/datastore.py b/src/easydiffraction/experiments/datastore.py deleted file mode 100644 index e957b122..00000000 --- a/src/easydiffraction/experiments/datastore.py +++ /dev/null @@ -1,254 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from __future__ import annotations - -from abc import abstractmethod -from typing import Optional - -import numpy as np - -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.components.experiment_type import SampleFormEnum -from easydiffraction.utils.decorators import enforce_type - - -class BaseDatastore: - """Base class for all data stores. - - Attributes: - meas (Optional[np.ndarray]): Measured intensities. - meas_su (Optional[np.ndarray]): Standard uncertainties of - measured intensities. - excluded (Optional[np.ndarray]): Flags for excluded points. - _calc (Optional[np.ndarray]): Stored calculated intensities. - """ - - def __init__(self) -> None: - self.meas: Optional[np.ndarray] = None - self.meas_su: Optional[np.ndarray] = None - self.excluded: Optional[np.ndarray] = None - self._calc: Optional[np.ndarray] = None - - @property - def calc(self) -> Optional[np.ndarray]: - """Access calculated intensities. Should be updated via external - calculation. - - Returns: - Optional[np.ndarray]: Calculated intensities array or None - if not set. - """ - return self._calc - - @calc.setter - @enforce_type - def calc(self, values: np.ndarray) -> None: - """Set calculated intensities (from - Analysis.calculate_pattern()). - - Args: - values (np.ndarray): Array of calculated intensities. - """ - self._calc = values - - @abstractmethod - def _cif_mapping(self) -> dict[str, str]: - """Must be implemented in subclasses to return a mapping from - attribute names to CIF tags. - - Returns: - dict[str, str]: Mapping from attribute names to CIF tags. - """ - pass - - def as_cif(self, max_points: Optional[int] = None) -> str: - """Generate a CIF-formatted string representing the datastore - data. - - Args: - max_points (Optional[int]): Maximum number of points to - include from start and end. If the total points exceed - twice this number, data in the middle is truncated with - '...'. - - Returns: - str: CIF-formatted string of the data. Empty string if no - data available. - """ - cif_lines = ['loop_'] - - # Add CIF tags from mapping - mapping = self._cif_mapping() - for cif_key in mapping.values(): - cif_lines.append(cif_key) - - # Collect data arrays according to mapping keys - data_arrays = [] - for attr_name in mapping: - attr_array = getattr(self, attr_name, None) - if attr_array is None: - data_arrays.append(np.array([])) - else: - data_arrays.append(attr_array) - - # Return empty string if no data - if not data_arrays or not data_arrays[0].size: - return '' - - # Determine number of points in the first data array - n_points = len(data_arrays[0]) - - # Function to format a single row of data - def _format_row(i: int) -> str: - return ' '.join(str(data_arrays[j][i]) for j in range(len(data_arrays))) - - # Add data lines, applying max_points truncation if needed - if max_points is not None and n_points > 2 * max_points: - for i in range(max_points): - cif_lines.append(_format_row(i)) - cif_lines.append('...') - for i in range(-max_points, 0): - cif_lines.append(_format_row(i)) - else: - for i in range(n_points): - cif_lines.append(_format_row(i)) - - cif_str = '\n'.join(cif_lines) - - return cif_str - - -class PowderDatastore(BaseDatastore): - """Class for powder diffraction data. - - Attributes: - x (Optional[np.ndarray]): Scan variable (e.g. 2θ or - time-of-flight values). - d (Optional[np.ndarray]): d-spacing values. - bkg (Optional[np.ndarray]): Background values. - """ - - def __init__(self, beam_mode: Optional[BeamModeEnum] = None) -> None: - """Initialize PowderDatastore. - - Args: - beam_mode (str): Beam mode, e.g. 'time-of-flight' or - 'constant wavelength'. - """ - super().__init__() - - if beam_mode is None: - beam_mode = BeamModeEnum.default() - - self.beam_mode = beam_mode - self.x: Optional[np.ndarray] = None - self.d: Optional[np.ndarray] = None - self.bkg: Optional[np.ndarray] = None - - def _cif_mapping(self) -> dict[str, str]: - """Return mapping from attribute names to CIF tags based on beam - mode. - - Returns: - dict[str, str]: Mapping dictionary. - """ - # TODO: Decide where to have validation for beam_mode, - # here or in Experiment class or somewhere else. - return { - 'time-of-flight': { - 'x': '_pd_meas.time_of_flight', - 'meas': '_pd_meas.intensity_total', - 'meas_su': '_pd_meas.intensity_total_su', - }, - 'constant wavelength': { - 'x': '_pd_meas.2theta_scan', - 'meas': '_pd_meas.intensity_total', - 'meas_su': '_pd_meas.intensity_total_su', - }, - }[self.beam_mode] - - -class SingleCrystalDatastore(BaseDatastore): - """Class for single crystal diffraction data. - - Attributes: - sin_theta_over_lambda (Optional[np.ndarray]): sin(θ)/λ values. - index_h (Optional[np.ndarray]): Miller index h. - index_k (Optional[np.ndarray]): Miller index k. - index_l (Optional[np.ndarray]): Miller index l. - """ - - def __init__(self) -> None: - """Initialize SingleCrystalDatastore.""" - super().__init__() - self.sin_theta_over_lambda: Optional[np.ndarray] = None - self.index_h: Optional[np.ndarray] = None - self.index_k: Optional[np.ndarray] = None - self.index_l: Optional[np.ndarray] = None - - def _cif_mapping(self) -> dict[str, str]: - """Return mapping from attribute names to CIF tags for single - crystal data. - - Returns: - dict[str, str]: Mapping dictionary. - """ - return { - 'index_h': '_refln.index_h', - 'index_k': '_refln.index_k', - 'index_l': '_refln.index_l', - 'meas': '_refln.intensity_meas', - 'meas_su': '_refln.intensity_meas_su', - } - - -class DatastoreFactory: - _supported = { - 'powder': PowderDatastore, - 'single crystal': SingleCrystalDatastore, - } - - @classmethod - def create( - cls, - sample_form: str = SampleFormEnum.default(), - beam_mode: str = BeamModeEnum.default(), - ) -> BaseDatastore: - """Create and return a datastore object for the given sample - form. - - Args: - sample_form (str): Sample form type, e.g. 'powder' or - 'single crystal'. - beam_mode (str): Beam mode for powder sample form. - - Returns: - BaseDatastore: Instance of a datastore class corresponding - to sample form. - - Raises: - ValueError: If the sample_form or beam_mode is not - supported. - """ - supported_sample_forms = list(cls._supported.keys()) - if sample_form not in supported_sample_forms: - raise ValueError( - f"Unsupported sample form: '{sample_form}'.\n" - f'Supported sample forms: {supported_sample_forms}' - ) - - supported_beam_modes = ['time-of-flight', 'constant wavelength'] - if beam_mode not in supported_beam_modes: - raise ValueError( - f"Unsupported beam mode: '{beam_mode}'.\n" - f'Supported beam modes: {supported_beam_modes}' - ) - - datastore_class = cls._supported[sample_form] - if sample_form == 'powder': - datastore_obj = datastore_class(beam_mode=beam_mode) - else: - datastore_obj = datastore_class() - - return datastore_obj diff --git a/src/easydiffraction/experiments/datastore/__init__.py b/src/easydiffraction/experiments/datastore/__init__.py new file mode 100644 index 00000000..3e95b5e9 --- /dev/null +++ b/src/easydiffraction/experiments/datastore/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/experiments/datastore/base.py b/src/easydiffraction/experiments/datastore/base.py new file mode 100644 index 00000000..43d8a46b --- /dev/null +++ b/src/easydiffraction/experiments/datastore/base.py @@ -0,0 +1,79 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING +from typing import Optional + +from typeguard import typechecked + +from easydiffraction.io.cif.serialize import datastore_to_cif + +if TYPE_CHECKING: + import numpy as np + + +class DatastoreBase: + """Base class for all data stores. + + Attributes: + meas (Optional[np.ndarray]): Measured intensities. + meas_su (Optional[np.ndarray]): Standard uncertainties of + measured intensities. + excluded (Optional[np.ndarray]): Flags for excluded points. + _calc (Optional[np.ndarray]): Stored calculated intensities. + """ + + def __init__(self) -> None: + self.meas: Optional[np.ndarray] = None + self.meas_su: Optional[np.ndarray] = None + self.excluded: Optional[np.ndarray] = None + self._calc: Optional[np.ndarray] = None + + @property + def calc(self) -> Optional[np.ndarray]: + """Access calculated intensities. Should be updated via external + calculation. + + Returns: + Optional[np.ndarray]: Calculated intensities array or None + if not set. + """ + return self._calc + + @calc.setter + @typechecked + def calc(self, values: np.ndarray) -> None: + """Set calculated intensities (from + Analysis.calculate_pattern()). + + Args: + values (np.ndarray): Array of calculated intensities. + """ + self._calc = values + + @abstractmethod + def _cif_mapping(self) -> dict[str, str]: + """Must be implemented in subclasses to return a mapping from + attribute names to CIF tags. + + Returns: + dict[str, str]: Mapping from attribute names to CIF tags. + """ + pass + + @property + def as_cif(self) -> str: + """Generate a CIF-formatted string representing the datastore + data. + """ + return datastore_to_cif(self) + + @property + def as_truncated_cif(self) -> str: + """Generate a CIF-formatted string representing the datastore + data. + """ + return datastore_to_cif(self, max_points=5) diff --git a/src/easydiffraction/experiments/datastore/factory.py b/src/easydiffraction/experiments/datastore/factory.py new file mode 100644 index 00000000..bf9842aa --- /dev/null +++ b/src/easydiffraction/experiments/datastore/factory.py @@ -0,0 +1,70 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Factory for experiment datastores based on sample form and beam +mode. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from easydiffraction.experiments.datastore.pd import PdDatastore +from easydiffraction.experiments.datastore.sc import ScDatastore +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum + +if TYPE_CHECKING: + from easydiffraction.experiments.datastore.base import DatastoreBase + + +class DatastoreFactory: + """Create PD or SC datastores depending on sample form.""" + + _supported = { + 'powder': PdDatastore, + 'single crystal': ScDatastore, + } + + @classmethod + def create( + cls, + sample_form: str = SampleFormEnum.default(), + beam_mode: str = BeamModeEnum.default(), + ) -> DatastoreBase: + """Create and return a datastore object for the given sample + form. + + Args: + sample_form (str): Sample form type, e.g. 'powder' or + 'single crystal'. + beam_mode (str): Beam mode for powder sample form. + + Returns: + DatastoreBase: Instance of a datastore class corresponding + to sample form. + + Raises: + ValueError: If the sample_form or beam_mode is not + supported. + """ + supported_sample_forms = list(cls._supported.keys()) + if sample_form not in supported_sample_forms: + raise ValueError( + f"Unsupported sample form: '{sample_form}'.\n" + f'Supported sample forms: {supported_sample_forms}' + ) + + supported_beam_modes = ['time-of-flight', 'constant wavelength'] + if beam_mode not in supported_beam_modes: + raise ValueError( + f"Unsupported beam mode: '{beam_mode}'.\n" + f'Supported beam modes: {supported_beam_modes}' + ) + + datastore_class = cls._supported[sample_form] + if sample_form == 'powder': + datastore_obj = datastore_class(beam_mode=beam_mode) + else: + datastore_obj = datastore_class() + + return datastore_obj diff --git a/src/easydiffraction/experiments/datastore/pd.py b/src/easydiffraction/experiments/datastore/pd.py new file mode 100644 index 00000000..1939f70a --- /dev/null +++ b/src/easydiffraction/experiments/datastore/pd.py @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing import Optional + +from easydiffraction.experiments.datastore.base import DatastoreBase +from easydiffraction.experiments.experiment.enums import BeamModeEnum + +if TYPE_CHECKING: + import numpy as np + + +class PdDatastore(DatastoreBase): + """Class for powder diffraction data. + + Attributes: + x (Optional[np.ndarray]): Scan variable (e.g. 2θ or + time-of-flight values). + d (Optional[np.ndarray]): d-spacing values. + bkg (Optional[np.ndarray]): Background values. + """ + + def __init__(self, beam_mode: Optional[BeamModeEnum] = None) -> None: + """Initialize PowderDatastore. + + Args: + beam_mode (str): Beam mode, e.g. 'time-of-flight' or + 'constant wavelength'. + """ + super().__init__() + + if beam_mode is None: + beam_mode = BeamModeEnum.default() + + self.beam_mode = beam_mode + self.x: Optional[np.ndarray] = None + self.d: Optional[np.ndarray] = None + self.bkg: Optional[np.ndarray] = None + + def _cif_mapping(self) -> dict[str, str]: + """Return mapping from attribute names to CIF tags based on beam + mode. + + Returns: + dict[str, str]: Mapping dictionary. + """ + # TODO: Decide where to have validation for beam_mode, + # here or in Experiment class or somewhere else. + return { + 'time-of-flight': { + 'x': '_pd_meas.time_of_flight', + 'meas': '_pd_meas.intensity_total', + 'meas_su': '_pd_meas.intensity_total_su', + }, + 'constant wavelength': { + 'x': '_pd_meas.2theta_scan', + 'meas': '_pd_meas.intensity_total', + 'meas_su': '_pd_meas.intensity_total_su', + }, + }[self.beam_mode] diff --git a/src/easydiffraction/experiments/datastore/sc.py b/src/easydiffraction/experiments/datastore/sc.py new file mode 100644 index 00000000..7182782b --- /dev/null +++ b/src/easydiffraction/experiments/datastore/sc.py @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing import Optional + +from easydiffraction.experiments.datastore.base import DatastoreBase + +if TYPE_CHECKING: + import numpy as np + + +class ScDatastore(DatastoreBase): + """Class for single crystal diffraction data. + + Attributes: + sin_theta_over_lambda (Optional[np.ndarray]): sin(θ)/λ values. + index_h (Optional[np.ndarray]): Miller index h. + index_k (Optional[np.ndarray]): Miller index k. + index_l (Optional[np.ndarray]): Miller index l. + """ + + def __init__(self) -> None: + """Initialize SingleCrystalDatastore.""" + super().__init__() + self.sin_theta_over_lambda: Optional[np.ndarray] = None + self.index_h: Optional[np.ndarray] = None + self.index_k: Optional[np.ndarray] = None + self.index_l: Optional[np.ndarray] = None + + def _cif_mapping(self) -> dict[str, str]: + """Return mapping from attribute names to CIF tags for single + crystal data. + + Returns: + dict[str, str]: Mapping dictionary. + """ + return { + 'index_h': '_refln.index_h', + 'index_k': '_refln.index_k', + 'index_l': '_refln.index_l', + 'meas': '_refln.intensity_meas', + 'meas_su': '_refln.intensity_meas_su', + } diff --git a/src/easydiffraction/experiments/experiment.py b/src/easydiffraction/experiments/experiment.py deleted file mode 100644 index 8abb80f7..00000000 --- a/src/easydiffraction/experiments/experiment.py +++ /dev/null @@ -1,572 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from abc import abstractmethod -from typing import List -from typing import Optional - -import numpy as np - -from easydiffraction.core.objects import Datablock -from easydiffraction.experiments.collections.background import BackgroundFactory -from easydiffraction.experiments.collections.background import BackgroundTypeEnum -from easydiffraction.experiments.collections.excluded_regions import ExcludedRegions -from easydiffraction.experiments.collections.linked_phases import LinkedPhases -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.components.experiment_type import ExperimentType -from easydiffraction.experiments.components.experiment_type import RadiationProbeEnum -from easydiffraction.experiments.components.experiment_type import SampleFormEnum -from easydiffraction.experiments.components.experiment_type import ScatteringTypeEnum -from easydiffraction.experiments.components.instrument import InstrumentBase -from easydiffraction.experiments.components.instrument import InstrumentFactory -from easydiffraction.experiments.components.peak import PeakFactory -from easydiffraction.experiments.components.peak import PeakProfileTypeEnum -from easydiffraction.experiments.datastore import DatastoreFactory -from easydiffraction.utils.decorators import enforce_type -from easydiffraction.utils.formatting import paragraph -from easydiffraction.utils.formatting import warning -from easydiffraction.utils.utils import render_cif -from easydiffraction.utils.utils import render_table - - -class InstrumentMixin: - def __init__(self, *args, **kwargs): - expt_type = kwargs.get('type') - super().__init__(*args, **kwargs) - self._instrument = InstrumentFactory.create( - scattering_type=expt_type.scattering_type.value, - beam_mode=expt_type.beam_mode.value, - ) - - @property - def instrument(self): - return self._instrument - - @instrument.setter - @enforce_type - def instrument(self, new_instrument: InstrumentBase): - self._instrument = new_instrument - - -class BaseExperiment(Datablock): - """Base class for all experiments with only core attributes. - - Wraps experiment type, instrument and datastore. - """ - - # TODO: Find better name for the attribute 'type'. - # 1. It shadows the built-in type() function. - # 2. It is not very clear what it refers to. - def __init__(self, name: str, type: ExperimentType): - self.name = name - self.type = type - self.datastore = DatastoreFactory.create( - sample_form=self.type.sample_form.value, - beam_mode=self.type.beam_mode.value, - ) - - # --------------- - # Experiment type - # --------------- - - @property - def type(self): - return self._type - - @type.setter - @enforce_type - def type(self, new_experiment_type: ExperimentType): - self._type = new_experiment_type - - # ---------------- - # Misc. Need to be sorted - # ---------------- - - def as_cif( - self, - max_points: Optional[int] = None, - ) -> str: - """Export the sample model to CIF format. - - Returns: - str: CIF string representation of the experiment. - """ - # Data block header - cif_lines: List[str] = [f'data_{self.name}'] - - # Experiment type - cif_lines += ['', self.type.as_cif()] - - # Instrument setup and calibration - if hasattr(self, 'instrument'): - cif_lines += ['', self.instrument.as_cif()] - - # Peak profile, broadening and asymmetry - if hasattr(self, 'peak'): - cif_lines += ['', self.peak.as_cif()] - - # Phase scale factors for powder experiments - if hasattr(self, 'linked_phases') and self.linked_phases._items: - cif_lines += ['', self.linked_phases.as_cif()] - - # Crystal scale factor for single crystal experiments - if hasattr(self, 'linked_crystal'): - cif_lines += ['', self.linked_crystal.as_cif()] - - # Background points - if hasattr(self, 'background') and self.background._items: - cif_lines += ['', self.background.as_cif()] - - # Excluded regions - if hasattr(self, 'excluded_regions') and self.excluded_regions._items: - cif_lines += ['', self.excluded_regions.as_cif()] - - # Measured data - if hasattr(self, 'datastore'): - cif_lines += ['', self.datastore.as_cif(max_points=max_points)] - - return '\n'.join(cif_lines) - - def show_as_cif(self) -> None: - cif_text: str = self.as_cif(max_points=5) - paragraph_title: str = paragraph(f"Experiment 🔬 '{self.name}' as cif") - render_cif(cif_text, paragraph_title) - - @abstractmethod - def _load_ascii_data_to_experiment(self, data_path: str) -> None: - pass - - -class BasePowderExperiment(BaseExperiment): - """Base class for all powder experiments.""" - - def __init__( - self, - name: str, - type: ExperimentType, - ) -> None: - super().__init__(name=name, type=type) - - self._peak_profile_type: str = PeakProfileTypeEnum.default( - self.type.scattering_type.value, - self.type.beam_mode.value, - ).value - self.peak = PeakFactory.create( - scattering_type=self.type.scattering_type.value, - beam_mode=self.type.beam_mode.value, - profile_type=self._peak_profile_type, - ) - - self.linked_phases: LinkedPhases = LinkedPhases() - self.excluded_regions: ExcludedRegions = ExcludedRegions(parent=self) - - @abstractmethod - def _load_ascii_data_to_experiment(self, data_path: str) -> None: - pass - - @property - def peak_profile_type(self): - return self._peak_profile_type - - @peak_profile_type.setter - def peak_profile_type(self, new_type: str): - if ( - new_type - not in PeakFactory._supported[self.type.scattering_type.value][ - self.type.beam_mode.value - ] - ): - supported_types = list( - PeakFactory._supported[self.type.scattering_type.value][ - self.type.beam_mode.value - ].keys() - ) - print(warning(f"Unsupported peak profile '{new_type}'")) - print(f'Supported peak profiles: {supported_types}') - print("For more information, use 'show_supported_peak_profile_types()'") - return - self.peak = PeakFactory.create( - scattering_type=self.type.scattering_type.value, - beam_mode=self.type.beam_mode.value, - profile_type=new_type, - ) - self._peak_profile_type = new_type - print(paragraph(f"Peak profile type for experiment '{self.name}' changed to")) - print(new_type) - - def show_supported_peak_profile_types(self): - columns_headers = ['Peak profile type', 'Description'] - columns_alignment = ['left', 'left'] - columns_data = [] - - scattering_type = self.type.scattering_type.value - beam_mode = self.type.beam_mode.value - - for profile_type in PeakFactory._supported[scattering_type][beam_mode]: - columns_data.append([profile_type.value, profile_type.description()]) - - print(paragraph('Supported peak profile types')) - render_table( - columns_headers=columns_headers, - columns_alignment=columns_alignment, - columns_data=columns_data, - ) - - def show_current_peak_profile_type(self): - print(paragraph('Current peak profile type')) - print(self.peak_profile_type) - - -class PowderExperiment( - InstrumentMixin, - BasePowderExperiment, -): - """Powder experiment class with specific attributes. - - Wraps background, peak profile, and linked phases. - """ - - def __init__( - self, - name: str, - type: ExperimentType, - ) -> None: - super().__init__(name=name, type=type) - - self._background_type: BackgroundTypeEnum = BackgroundTypeEnum.default() - self.background = BackgroundFactory.create(background_type=self.background_type) - - # ------------- - # Measured data - # ------------- - - def _load_ascii_data_to_experiment(self, data_path: str) -> None: - """Loads x, y, sy values from an ASCII data file into the - experiment. - - The file must be structured as: - x y sy - """ - try: - data = np.loadtxt(data_path) - except Exception as e: - raise IOError(f'Failed to read data from {data_path}: {e}') from e - - if data.shape[1] < 2: - raise ValueError('Data file must have at least two columns: x and y.') - - if data.shape[1] < 3: - print('Warning: No uncertainty (sy) column provided. Defaulting to sqrt(y).') - - # Extract x, y data - x: np.ndarray = data[:, 0] - y: np.ndarray = data[:, 1] - - # Round x to 4 decimal places - # TODO: This is needed for CrysPy, as otherwise it fails to - # match the size of the data arrays. - x = np.round(x, 4) - - # Determine sy from column 3 if available, otherwise use sqrt(y) - sy: np.ndarray = data[:, 2] if data.shape[1] > 2 else np.sqrt(y) - - # Replace values smaller than 0.0001 with 1.0 - # TODO: This is needed for minimization algorithms that fail - # with very small or zero uncertainties. - sy = np.where(sy < 0.0001, 1.0, sy) - - # Attach the data to the experiment's datastore - - # The full pattern data - self.datastore.full_x = x - self.datastore.full_meas = y - self.datastore.full_meas_su = sy - - # The pattern data used for fitting (without excluded points) - # This is the same as full_x, full_meas, full_meas_su by default - self.datastore.x = x - self.datastore.meas = y - self.datastore.meas_su = sy - - # Excluded mask - # No excluded points by default - self.datastore.excluded = np.full(x.shape, fill_value=False, dtype=bool) - - print(paragraph('Data loaded successfully')) - print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}") - - @property - def background_type(self): - return self._background_type - - @background_type.setter - def background_type(self, new_type): - if new_type not in BackgroundFactory._supported: - supported_types = list(BackgroundFactory._supported.keys()) - print(warning(f"Unknown background type '{new_type}'")) - print(f'Supported background types: {supported_types}') - print("For more information, use 'show_supported_background_types()'") - return - self.background = BackgroundFactory.create(new_type) - self._background_type = new_type - print(paragraph(f"Background type for experiment '{self.name}' changed to")) - print(new_type) - - def show_supported_background_types(self): - columns_headers = ['Background type', 'Description'] - columns_alignment = ['left', 'left'] - columns_data = [] - for bt in BackgroundFactory._supported: - columns_data.append([bt.value, bt.description()]) - - print(paragraph('Supported background types')) - render_table( - columns_headers=columns_headers, - columns_alignment=columns_alignment, - columns_data=columns_data, - ) - - def show_current_background_type(self): - print(paragraph('Current background type')) - print(self.background_type) - - -# TODO: Refactor this class to reuse PowderExperiment -# TODO: This is not a specific experiment, but rather processed data -# from PowderExperiment. So, we should think of a better design. -class PairDistributionFunctionExperiment(BasePowderExperiment): - """PDF experiment class with specific attributes.""" - - def __init__( - self, - name: str, - type: ExperimentType, - ): - 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. - - The file must be structured as: - x y sy - """ - try: - from diffpy.utils.parsers.loaddata import loadData - except ImportError: - raise ImportError('diffpy module not found.') from None - try: - data = loadData(data_path) - except Exception as e: - raise IOError(f'Failed to read data from {data_path}: {e}') from e - - if data.shape[1] < 2: - raise ValueError('Data file must have at least two columns: x and y.') - - default_sy = 0.03 - if data.shape[1] < 3: - print(f'Warning: No uncertainty (sy) column provided. Defaulting to {default_sy}.') - - # Extract x, y, and sy data - x = data[:, 0] - # We should also add sx = data[:, 2] to capture the e.s.d. of x. - # It might be useful in future. - y = data[:, 1] - # Using sqrt isn’t appropriate here, as the y-scale isn’t raw - # counts and includes both positive and negative values. For - # now, set the e.s.d. to a fixed value of 0.03 if it’s not - # included in the measured data file. We should improve this - # later. - # sy = data[:, 3] if data.shape[1] > 2 else np.sqrt(y) - sy = data[:, 2] if data.shape[1] > 2 else np.full_like(y, fill_value=default_sy) - - # Attach the data to the experiment's datastore - self.datastore.x = x - self.datastore.meas = y - self.datastore.meas_su = sy - - print(paragraph('Data loaded successfully')) - print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}") - - -class SingleCrystalExperiment(BaseExperiment): - """Single crystal experiment class with specific attributes.""" - - def __init__( - self, - name: str, - type: ExperimentType, - ) -> None: - super().__init__(name=name, type=type) - self.linked_crystal = None - - def show_meas_chart(self) -> None: - print('Showing measured data chart is not implemented yet.') - - -class ExperimentFactory: - """Creates Experiment instances with only relevant attributes.""" - - _valid_arg_sets = [ - { - 'required': ['cif_path'], - 'optional': [], - }, - { - 'required': ['cif_str'], - 'optional': [], - }, - { - 'required': [ - 'name', - 'data_path', - ], - 'optional': [ - 'sample_form', - 'beam_mode', - 'radiation_probe', - 'scattering_type', - ], - }, - { - 'required': ['name'], - 'optional': [ - 'sample_form', - 'beam_mode', - 'radiation_probe', - 'scattering_type', - ], - }, - ] - - _supported = { - ScatteringTypeEnum.BRAGG: { - SampleFormEnum.POWDER: PowderExperiment, - SampleFormEnum.SINGLE_CRYSTAL: SingleCrystalExperiment, - }, - ScatteringTypeEnum.TOTAL: { - SampleFormEnum.POWDER: PairDistributionFunctionExperiment, - }, - } - - @classmethod - def create(cls, **kwargs): - """Main factory method for creating an experiment instance. - - Validates argument combinations and dispatches to the - appropriate creation method. Raises ValueError if arguments are - invalid or no valid dispatch is found. - """ - # Check for valid argument combinations - user_args = [k for k, v in kwargs.items() if v is not None] - if not cls.is_valid_args(user_args): - raise ValueError(f'Invalid argument combination: {user_args}') - - # Validate enum arguments if provided - if 'sample_form' in kwargs: - SampleFormEnum(kwargs['sample_form']) - if 'beam_mode' in kwargs: - BeamModeEnum(kwargs['beam_mode']) - if 'radiation_probe' in kwargs: - RadiationProbeEnum(kwargs['radiation_probe']) - if 'scattering_type' in kwargs: - ScatteringTypeEnum(kwargs['scattering_type']) - - # Dispatch to the appropriate creation method - if 'cif_path' in kwargs: - return cls._create_from_cif_path(kwargs) - elif 'cif_str' in kwargs: - return cls._create_from_cif_str(kwargs) - elif 'data_path' in kwargs: - return cls._create_from_data_path(kwargs) - elif 'name' in kwargs: - return cls._create_without_data(kwargs) - - @staticmethod - def _create_from_cif_path(cif_path): - """Create an experiment from a CIF file path. - - Not yet implemented. - """ - # TODO: Implement CIF file loading logic - raise NotImplementedError('CIF file loading not implemented yet.') - - @staticmethod - def _create_from_cif_str(cif_str): - """Create an experiment from a CIF string. - - Not yet implemented. - """ - # TODO: Implement CIF string loading logic - raise NotImplementedError('CIF string loading not implemented yet.') - - @classmethod - def _create_from_data_path(cls, kwargs): - """Create an experiment from a raw data ASCII file. - - Loads the experiment and attaches measured data from the - specified file. - """ - expt_type = cls._make_experiment_type(kwargs) - scattering_type = expt_type.scattering_type.value - sample_form = expt_type.sample_form.value - expt_class = cls._supported[scattering_type][sample_form] - expt_name = kwargs['name'] - expt_obj = expt_class(name=expt_name, type=expt_type) - data_path = kwargs['data_path'] - expt_obj._load_ascii_data_to_experiment(data_path) - return expt_obj - - @classmethod - def _create_without_data(cls, kwargs): - """Create an experiment without measured data. - - Returns an experiment instance with only metadata and - configuration. - """ - expt_type = cls._make_experiment_type(kwargs) - scattering_type = expt_type.scattering_type.value - sample_form = expt_type.sample_form.value - expt_class = cls._supported[scattering_type][sample_form] - expt_name = kwargs['name'] - expt_obj = expt_class(name=expt_name, type=expt_type) - return expt_obj - - @classmethod - def _make_experiment_type(cls, kwargs): - """Helper to construct an ExperimentType from keyword arguments, - using defaults as needed. - """ - return ExperimentType( - sample_form=kwargs.get('sample_form', SampleFormEnum.default()), - beam_mode=kwargs.get('beam_mode', BeamModeEnum.default()), - radiation_probe=kwargs.get('radiation_probe', RadiationProbeEnum.default()), - scattering_type=kwargs.get('scattering_type', ScatteringTypeEnum.default()), - ) - - @staticmethod - def is_valid_args(user_args): - """Validate user argument set against allowed combinations. - - Returns True if the argument set matches any valid combination, - else False. - """ - user_arg_set = set(user_args) - for arg_set in ExperimentFactory._valid_arg_sets: - required = set(arg_set['required']) - optional = set(arg_set['optional']) - # Must have all required, and only required+optional - if required.issubset(user_arg_set) and user_arg_set <= (required | optional): - return True - return False - - -class Experiment: - """User-facing API for creating an experiment. - - Accepts keyword arguments and delegates validation and creation to - ExperimentFactory. - """ - - def __new__(cls, **kwargs): - return ExperimentFactory.create(**kwargs) diff --git a/src/easydiffraction/experiments/experiment/__init__.py b/src/easydiffraction/experiments/experiment/__init__.py new file mode 100644 index 00000000..23232375 --- /dev/null +++ b/src/easydiffraction/experiments/experiment/__init__.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.experiments.experiment.base import ExperimentBase +from easydiffraction.experiments.experiment.base import PdExperimentBase +from easydiffraction.experiments.experiment.bragg_pd import BraggPdExperiment +from easydiffraction.experiments.experiment.bragg_sc import BraggScExperiment +from easydiffraction.experiments.experiment.total_pd import TotalPdExperiment + +__all__ = [ + 'ExperimentBase', + 'PdExperimentBase', + 'BraggPdExperiment', + 'TotalPdExperiment', + 'BraggScExperiment', +] diff --git a/src/easydiffraction/experiments/experiment/base.py b/src/easydiffraction/experiments/experiment/base.py new file mode 100644 index 00000000..ae8d383f --- /dev/null +++ b/src/easydiffraction/experiments/experiment/base.py @@ -0,0 +1,217 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING + +from easydiffraction.core.datablock import DatablockItem +from easydiffraction.experiments.categories.excluded_regions import ExcludedRegions +from easydiffraction.experiments.categories.linked_phases import LinkedPhases +from easydiffraction.experiments.categories.peak.factory import PeakFactory +from easydiffraction.experiments.categories.peak.factory import PeakProfileTypeEnum +from easydiffraction.experiments.datastore.factory import DatastoreFactory +from easydiffraction.io.cif.serialize import experiment_to_cif +from easydiffraction.utils.formatting import paragraph +from easydiffraction.utils.formatting import warning +from easydiffraction.utils.utils import render_cif +from easydiffraction.utils.utils import render_table + +if TYPE_CHECKING: + from easydiffraction.experiments.categories.experiment_type import ExperimentType + + +class ExperimentBase(DatablockItem): + """Base class for all experiments with only core attributes. + + Wraps experiment type, instrument and datastore. + """ + + def __init__( + self, + *, + name: str, + type: ExperimentType, + ): + super().__init__() + self._name = name + self._type = type + self._datastore = DatastoreFactory.create( + sample_form=self.type.sample_form.value, + beam_mode=self.type.beam_mode.value, + ) + self._identity.datablock_entry_name = lambda: self.name + + @property + def name(self) -> str: + """Human-readable name of the experiment.""" + return self._name + + @name.setter + def name(self, new: str) -> None: + """Rename the experiment. + + Args: + new: New name for this experiment. + """ + self._name = new + + @property + def type(self): # TODO: Consider another name + """Experiment type descriptor (sample form, probe, beam + mode). + """ + return self._type + + @property + def datastore(self): + """Data container with x, y, error, calc and background + arrays. + """ + return self._datastore + + @property + def as_cif(self) -> str: + """Serialize this experiment to a CIF fragment.""" + return experiment_to_cif(self) + + def show_as_cif(self) -> None: + """Pretty-print the experiment and datastore as CIF text.""" + experiment_cif = super().as_cif + datastore_cif = self.datastore.as_truncated_cif + cif_text: str = f'{experiment_cif}\n\n{datastore_cif}' + paragraph_title: str = paragraph(f"Experiment 🔬 '{self.name}' as cif") + render_cif(cif_text, paragraph_title) + + @abstractmethod + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + """Load ASCII data from file into the experiment datastore. + + Args: + data_path: Path to the ASCII file to load. + """ + raise NotImplementedError() + + +class PdExperimentBase(ExperimentBase): + """Base class for all powder experiments.""" + + def __init__( + self, + *, + name: str, + type: ExperimentType, + ) -> None: + super().__init__(name=name, type=type) + + self._peak_profile_type: PeakProfileTypeEnum = PeakProfileTypeEnum.default( + self.type.scattering_type.value, + self.type.beam_mode.value, + ) + self._peak = PeakFactory.create( + scattering_type=self.type.scattering_type.value, + beam_mode=self.type.beam_mode.value, + profile_type=self._peak_profile_type, + ) + + self._linked_phases: LinkedPhases = LinkedPhases() + self._excluded_regions: ExcludedRegions = ExcludedRegions() + + @abstractmethod + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + """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). + """ + pass + + @property + def peak(self) -> str: + """Peak category object with profile parameters and mixins.""" + return self._peak + + @peak.setter + def peak(self, value): + """Replace the peak model used for this powder experiment. + + Args: + value: New peak object created by the `PeakFactory`. + """ + self._peak = value + + @property + def linked_phases(self) -> str: + """Collection of phases linked to this experiment.""" + return self._linked_phases + + @property + def excluded_regions(self) -> str: + """Collection of excluded regions for the x-grid.""" + return self._excluded_regions + + @property + def peak_profile_type(self): + """Currently selected peak profile type enum.""" + return self._peak_profile_type + + @peak_profile_type.setter + def peak_profile_type(self, new_type: str | PeakProfileTypeEnum): + """Change the active peak profile type, if supported. + + Args: + new_type: New profile type as enum or its string value. + """ + if isinstance(new_type, str): + try: + new_type = PeakProfileTypeEnum(new_type) + except ValueError: + print(warning(f"Unknown peak profile type '{new_type}'")) + return + + supported_types = list( + PeakFactory._supported[self.type.scattering_type.value][ + self.type.beam_mode.value + ].keys() + ) + + if new_type not in supported_types: + print(warning(f"Unsupported peak profile '{new_type.value}'")) + print(f'Supported peak profiles: {supported_types}') + print("For more information, use 'show_supported_peak_profile_types()'") + return + + self._peak = PeakFactory.create( + scattering_type=self.type.scattering_type.value, + beam_mode=self.type.beam_mode.value, + profile_type=new_type, + ) + self._peak_profile_type = new_type + print(paragraph(f"Peak profile type for experiment '{self.name}' changed to")) + print(new_type.value) + + def show_supported_peak_profile_types(self): + """Print available peak profile types for this experiment.""" + columns_headers = ['Peak profile type', 'Description'] + columns_alignment = ['left', 'left'] + columns_data = [] + + scattering_type = self.type.scattering_type.value + beam_mode = self.type.beam_mode.value + + for profile_type in PeakFactory._supported[scattering_type][beam_mode]: + columns_data.append([profile_type.value, profile_type.description()]) + + print(paragraph('Supported peak profile types')) + render_table( + columns_headers=columns_headers, + columns_alignment=columns_alignment, + columns_data=columns_data, + ) + + def show_current_peak_profile_type(self): + """Print the currently selected peak profile type.""" + print(paragraph('Current peak profile type')) + print(self.peak_profile_type) diff --git a/src/easydiffraction/experiments/experiment/bragg_pd.py b/src/easydiffraction/experiments/experiment/bragg_pd.py new file mode 100644 index 00000000..f69e1fbc --- /dev/null +++ b/src/easydiffraction/experiments/experiment/bragg_pd.py @@ -0,0 +1,135 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import numpy as np + +from easydiffraction.experiments.categories.background.enums import BackgroundTypeEnum +from easydiffraction.experiments.categories.background.factory import BackgroundFactory +from easydiffraction.experiments.experiment.base import PdExperimentBase +from easydiffraction.experiments.experiment.instrument_mixin import InstrumentMixin +from easydiffraction.utils.formatting import paragraph +from easydiffraction.utils.formatting import warning +from easydiffraction.utils.utils import render_table + +if TYPE_CHECKING: + from easydiffraction.experiments.categories.experiment_type import ExperimentType + + +class BraggPdExperiment(InstrumentMixin, PdExperimentBase): + """Powder diffraction experiment. + + Wraps background model, peak profile and linked phases for Bragg PD. + """ + + def __init__( + self, + *, + name: str, + type: ExperimentType, + ) -> None: + super().__init__(name=name, type=type) + + self._background_type: BackgroundTypeEnum = BackgroundTypeEnum.default() + self._background = BackgroundFactory.create(background_type=self.background_type) + + @property + def background(self): + return self._background + + @background.setter + def background(self, value): + self._background = value + + # ------------- + # Measured data + # ------------- + + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + """Load (x, y, sy) data from an ASCII file into the datastore. + + The file format is space/column separated with 2 or 3 columns: + ``x y [sy]``. If ``sy`` is missing, it is approximated as + ``sqrt(y)`` with small values clamped to ``1.0``. + """ + try: + data = np.loadtxt(data_path) + except Exception as e: + raise IOError(f'Failed to read data from {data_path}: {e}') from e + + if data.shape[1] < 2: + raise ValueError('Data file must have at least two columns: x and y.') + + if data.shape[1] < 3: + print('Warning: No uncertainty (sy) column provided. Defaulting to sqrt(y).') + + # Extract x, y data + x: np.ndarray = data[:, 0] + y: np.ndarray = data[:, 1] + + # Round x to 4 decimal places + x = np.round(x, 4) + + # Determine sy from column 3 if available, otherwise use sqrt(y) + sy: np.ndarray = data[:, 2] if data.shape[1] > 2 else np.sqrt(y) + + # Replace values smaller than 0.0001 with 1.0 + sy = np.where(sy < 0.0001, 1.0, sy) + + # Attach the data to the experiment's datastore + self.datastore.full_x = x + self.datastore.full_meas = y + self.datastore.full_meas_su = sy + self.datastore.x = x + self.datastore.meas = y + self.datastore.meas_su = sy + self.datastore.excluded = np.full(x.shape, fill_value=False, dtype=bool) + + print(paragraph('Data loaded successfully')) + print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}") + + @property + def background_type(self): + """Current background type enum value.""" + return self._background_type + + @background_type.setter + def background_type(self, new_type): + """Set and apply a new background type. + + Falls back to printing supported types if the new value is not + supported. + """ + if new_type not in BackgroundFactory._supported_map(): + supported_types = list(BackgroundFactory._supported_map().keys()) + print(warning(f"Unknown background type '{new_type}'")) + print(f'Supported background types: {supported_types}') + print("For more information, use 'show_supported_background_types()'") + return + self.background = BackgroundFactory.create(new_type) + self._background_type = new_type + print(paragraph(f"Background type for experiment '{self.name}' changed to")) + print(new_type) + + def show_supported_background_types(self): + """Print a table of supported background types.""" + columns_headers = ['Background type', 'Description'] + columns_alignment = ['left', 'left'] + columns_data = [] + for bt in BackgroundFactory._supported_map(): + columns_data.append([bt.value, bt.description()]) + + print(paragraph('Supported background types')) + render_table( + columns_headers=columns_headers, + columns_alignment=columns_alignment, + columns_data=columns_data, + ) + + def show_current_background_type(self): + """Print the currently used background type.""" + print(paragraph('Current background type')) + print(self.background_type) diff --git a/src/easydiffraction/experiments/experiment/bragg_sc.py b/src/easydiffraction/experiments/experiment/bragg_sc.py new file mode 100644 index 00000000..d5e069da --- /dev/null +++ b/src/easydiffraction/experiments/experiment/bragg_sc.py @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Single crystal experiment types and helpers.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from easydiffraction.experiments.experiment.base import ExperimentBase + +if TYPE_CHECKING: + from easydiffraction.experiments.categories.experiment_type import ExperimentType + + +class BraggScExperiment(ExperimentBase): + """Single crystal experiment class with specific attributes.""" + + def __init__( + self, + *, + name: str, + type: ExperimentType, + ) -> None: + super().__init__(name=name, type=type) + self.linked_crystal = None + + def show_meas_chart(self) -> None: + print('Showing measured data chart is not implemented yet.') diff --git a/src/easydiffraction/experiments/experiment/enums.py b/src/easydiffraction/experiments/experiment/enums.py new file mode 100644 index 00000000..ca622c3b --- /dev/null +++ b/src/easydiffraction/experiments/experiment/enums.py @@ -0,0 +1,118 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Enumerations for experiment configuration (forms, modes, types).""" + +from enum import Enum + + +class SampleFormEnum(str, Enum): + """Physical sample form supported by experiments.""" + + POWDER = 'powder' + SINGLE_CRYSTAL = 'single crystal' + + @classmethod + def default(cls) -> 'SampleFormEnum': + return cls.POWDER + + def description(self) -> str: + if self is SampleFormEnum.POWDER: + return 'Powdered or polycrystalline sample.' + elif self is SampleFormEnum.SINGLE_CRYSTAL: + return 'Single crystal sample.' + + +class ScatteringTypeEnum(str, Enum): + """Type of scattering modeled in an experiment.""" + + BRAGG = 'bragg' + TOTAL = 'total' + + @classmethod + def default(cls) -> 'ScatteringTypeEnum': + return cls.BRAGG + + def description(self) -> str: + if self is ScatteringTypeEnum.BRAGG: + return 'Bragg diffraction for conventional structure refinement.' + elif self is ScatteringTypeEnum.TOTAL: + return 'Total scattering for pair distribution function analysis (PDF).' + + +class RadiationProbeEnum(str, Enum): + """Incident radiation probe used in the experiment.""" + + NEUTRON = 'neutron' + XRAY = 'xray' + + @classmethod + def default(cls) -> 'RadiationProbeEnum': + return cls.NEUTRON + + def description(self) -> str: + if self is RadiationProbeEnum.NEUTRON: + return 'Neutron diffraction.' + elif self is RadiationProbeEnum.XRAY: + return 'X-ray diffraction.' + + +class BeamModeEnum(str, Enum): + """Beam delivery mode for the instrument.""" + + CONSTANT_WAVELENGTH = 'constant wavelength' + TIME_OF_FLIGHT = 'time-of-flight' + + @classmethod + def default(cls) -> 'BeamModeEnum': + return cls.CONSTANT_WAVELENGTH + + def description(self) -> str: + if self is BeamModeEnum.CONSTANT_WAVELENGTH: + return 'Constant wavelength (CW) diffraction.' + elif self is BeamModeEnum.TIME_OF_FLIGHT: + return 'Time-of-flight (TOF) diffraction.' + + +class PeakProfileTypeEnum(str, Enum): + """Available peak profile types per scattering and beam mode.""" + + PSEUDO_VOIGT = 'pseudo-voigt' + SPLIT_PSEUDO_VOIGT = 'split pseudo-voigt' + THOMPSON_COX_HASTINGS = 'thompson-cox-hastings' + PSEUDO_VOIGT_IKEDA_CARPENTER = 'pseudo-voigt * ikeda-carpenter' + PSEUDO_VOIGT_BACK_TO_BACK = 'pseudo-voigt * back-to-back' + GAUSSIAN_DAMPED_SINC = 'gaussian-damped-sinc' + + @classmethod + def default( + cls, + scattering_type: ScatteringTypeEnum | None = None, + beam_mode: BeamModeEnum | None = None, + ) -> 'PeakProfileTypeEnum': + if scattering_type is None: + scattering_type = ScatteringTypeEnum.default() + if beam_mode is None: + beam_mode = BeamModeEnum.default() + return { + (ScatteringTypeEnum.BRAGG, BeamModeEnum.CONSTANT_WAVELENGTH): cls.PSEUDO_VOIGT, + ( + ScatteringTypeEnum.BRAGG, + BeamModeEnum.TIME_OF_FLIGHT, + ): cls.PSEUDO_VOIGT_IKEDA_CARPENTER, + (ScatteringTypeEnum.TOTAL, BeamModeEnum.CONSTANT_WAVELENGTH): cls.GAUSSIAN_DAMPED_SINC, + (ScatteringTypeEnum.TOTAL, BeamModeEnum.TIME_OF_FLIGHT): cls.GAUSSIAN_DAMPED_SINC, + }[(scattering_type, beam_mode)] + + def description(self) -> str: + if self is PeakProfileTypeEnum.PSEUDO_VOIGT: + return 'Pseudo-Voigt profile' + elif self is PeakProfileTypeEnum.SPLIT_PSEUDO_VOIGT: + return 'Split pseudo-Voigt profile with empirical asymmetry correction.' + elif self is PeakProfileTypeEnum.THOMPSON_COX_HASTINGS: + return 'Thompson-Cox-Hastings profile with FCJ asymmetry correction.' + elif self is PeakProfileTypeEnum.PSEUDO_VOIGT_IKEDA_CARPENTER: + return 'Pseudo-Voigt profile with Ikeda-Carpenter asymmetry correction.' + elif self is PeakProfileTypeEnum.PSEUDO_VOIGT_BACK_TO_BACK: + return 'Pseudo-Voigt profile with Back-to-Back Exponential asymmetry correction.' + elif self is PeakProfileTypeEnum.GAUSSIAN_DAMPED_SINC: + return 'Gaussian-damped sinc profile for pair distribution function (PDF) analysis.' diff --git a/src/easydiffraction/experiments/experiment/factory.py b/src/easydiffraction/experiments/experiment/factory.py new file mode 100644 index 00000000..ca03ffde --- /dev/null +++ b/src/easydiffraction/experiments/experiment/factory.py @@ -0,0 +1,150 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.core.factory import FactoryBase +from easydiffraction.experiments.categories.experiment_type import ExperimentType +from easydiffraction.experiments.experiment import BraggPdExperiment +from easydiffraction.experiments.experiment import BraggScExperiment +from easydiffraction.experiments.experiment import TotalPdExperiment +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import RadiationProbeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + + +class ExperimentFactory(FactoryBase): + """Creates Experiment instances with only relevant attributes.""" + + _ALLOWED_ARG_SPECS = [ + {'required': ['cif_path'], 'optional': []}, + {'required': ['cif_str'], 'optional': []}, + { + 'required': ['name', 'data_path'], + 'optional': ['sample_form', 'beam_mode', 'radiation_probe', 'scattering_type'], + }, + { + 'required': ['name'], + 'optional': ['sample_form', 'beam_mode', 'radiation_probe', 'scattering_type'], + }, + ] + + _SUPPORTED = { + ScatteringTypeEnum.BRAGG: { + SampleFormEnum.POWDER: BraggPdExperiment, + SampleFormEnum.SINGLE_CRYSTAL: BraggScExperiment, + }, + ScatteringTypeEnum.TOTAL: { + SampleFormEnum.POWDER: TotalPdExperiment, + }, + } + + @classmethod + def create(cls, **kwargs): + """Create an `ExperimentBase` using a validated argument + combination. + """ + # Check for valid argument combinations + user_args = {k for k, v in kwargs.items() if v is not None} + cls._validate_args(user_args, cls._ALLOWED_ARG_SPECS, cls.__name__) + + # Validate enum arguments if provided + if 'sample_form' in kwargs: + SampleFormEnum(kwargs['sample_form']) + if 'beam_mode' in kwargs: + BeamModeEnum(kwargs['beam_mode']) + if 'radiation_probe' in kwargs: + RadiationProbeEnum(kwargs['radiation_probe']) + if 'scattering_type' in kwargs: + ScatteringTypeEnum(kwargs['scattering_type']) + + # Dispatch to the appropriate creation method + if 'cif_path' in kwargs: + return cls._create_from_cif_path(kwargs['cif_path']) + elif 'cif_str' in kwargs: + return cls._create_from_cif_str(kwargs['cif_str']) + elif 'data_path' in kwargs: + return cls._create_from_data_path(kwargs) + elif 'name' in kwargs: + return cls._create_without_data(kwargs) + + # ------------------------------- + # Private creation helper methods + # ------------------------------- + + @staticmethod + def _create_from_cif_path(cif_path): + """Create an experiment from a CIF file path. + + Not yet implemented. + """ + # TODO: Implement CIF file loading logic + raise NotImplementedError('CIF file loading not implemented yet.') + + @staticmethod + def _create_from_cif_str(cif_str): + """Create an experiment from a CIF string. + + Not yet implemented. + """ + # TODO: Implement CIF string loading logic + raise NotImplementedError('CIF string loading not implemented yet.') + + @classmethod + def _create_from_data_path(cls, kwargs): + """Create an experiment from a raw data ASCII file. + + Loads the experiment and attaches measured data from the + specified file. + """ + expt_type = cls._make_experiment_type(kwargs) + scattering_type = expt_type.scattering_type.value + sample_form = expt_type.sample_form.value + expt_class = cls._SUPPORTED[scattering_type][sample_form] + expt_name = kwargs['name'] + expt_obj = expt_class(name=expt_name, type=expt_type) + data_path = kwargs['data_path'] + expt_obj._load_ascii_data_to_experiment(data_path) + return expt_obj + + @classmethod + def _create_without_data(cls, kwargs): + """Create an experiment without measured data. + + Returns an experiment instance with only metadata and + configuration. + """ + expt_type = cls._make_experiment_type(kwargs) + scattering_type = expt_type.scattering_type.value + sample_form = expt_type.sample_form.value + expt_class = cls._SUPPORTED[scattering_type][sample_form] + expt_name = kwargs['name'] + expt_obj = expt_class(name=expt_name, type=expt_type) + return expt_obj + + @classmethod + def _make_experiment_type(cls, kwargs): + """Helper to construct an ExperimentType from keyword arguments, + using defaults as needed. + """ + return ExperimentType( + sample_form=kwargs.get('sample_form', SampleFormEnum.default()), + beam_mode=kwargs.get('beam_mode', BeamModeEnum.default()), + radiation_probe=kwargs.get('radiation_probe', RadiationProbeEnum.default()), + scattering_type=kwargs.get('scattering_type', ScatteringTypeEnum.default()), + ) + + @staticmethod + def _is_valid_args(user_args): + """Validate user argument set against allowed combinations. + + Returns True if the argument set matches any valid combination, + else False. + """ + user_arg_set = set(user_args) + for arg_set in ExperimentFactory._valid_arg_sets: + required = set(arg_set['required']) + optional = set(arg_set['optional']) + # Must have all required, and only required+optional + if required.issubset(user_arg_set) and user_arg_set <= (required | optional): + return True + return False diff --git a/src/easydiffraction/experiments/experiment/instrument_mixin.py b/src/easydiffraction/experiments/experiment/instrument_mixin.py new file mode 100644 index 00000000..0b32c769 --- /dev/null +++ b/src/easydiffraction/experiments/experiment/instrument_mixin.py @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from typeguard import typechecked + +from easydiffraction.experiments.categories.instrument.factory import InstrumentFactory + +if TYPE_CHECKING: + from easydiffraction.experiments.categories.instrument.base import InstrumentBase + + +class InstrumentMixin: + """Mixin that wires an experiment to an instrument category. + + Creates a default instrument via `InstrumentFactory` using the + experiment type (scattering type and beam mode) at initialization. + """ + + def __init__(self, *args, **kwargs): + expt_type = kwargs.get('type') + super().__init__(*args, **kwargs) + self._instrument = InstrumentFactory.create( + scattering_type=expt_type.scattering_type.value, + beam_mode=expt_type.beam_mode.value, + ) + + @property + def instrument(self): + """Instrument category object associated with the experiment.""" + return self._instrument + + @instrument.setter + @typechecked + def instrument(self, new_instrument: InstrumentBase): + """Replace the instrument and re-parent it to this experiment. + + Args: + new_instrument: Instrument instance compatible with the + experiment type. + """ + self._instrument = new_instrument + self._instrument._parent = self diff --git a/src/easydiffraction/experiments/experiment/total_pd.py b/src/easydiffraction/experiments/experiment/total_pd.py new file mode 100644 index 00000000..0364acfe --- /dev/null +++ b/src/easydiffraction/experiments/experiment/total_pd.py @@ -0,0 +1,59 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import numpy as np + +from easydiffraction.experiments.experiment.base import PdExperimentBase +from easydiffraction.utils.formatting import paragraph + +if TYPE_CHECKING: + from easydiffraction.experiments.categories.experiment_type import ExperimentType + + +class TotalPdExperiment(PdExperimentBase): + """PDF experiment class with specific attributes.""" + + def __init__( + self, + name: str, + type: ExperimentType, + ): + 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. + + The file must be structured as: + x y sy + """ + try: + from diffpy.utils.parsers.loaddata import loadData + except ImportError: + raise ImportError('diffpy module not found.') from None + try: + data = loadData(data_path) + except Exception as e: + raise IOError(f'Failed to read data from {data_path}: {e}') from e + + if data.shape[1] < 2: + raise ValueError('Data file must have at least two columns: x and y.') + + default_sy = 0.03 + if data.shape[1] < 3: + print(f'Warning: No uncertainty (sy) column provided. Defaulting to {default_sy}.') + + x = data[:, 0] + y = data[:, 1] + sy = data[:, 2] if data.shape[1] > 2 else np.full_like(y, fill_value=default_sy) + + self.datastore.x = x + self.datastore.meas = y + self.datastore.meas_su = sy + + print(paragraph('Data loaded successfully')) + print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}") diff --git a/src/easydiffraction/experiments/experiments.py b/src/easydiffraction/experiments/experiments.py index 0db524ed..c22c10a3 100644 --- a/src/easydiffraction/experiments/experiments.py +++ b/src/easydiffraction/experiments/experiments.py @@ -1,45 +1,53 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause -from typing import Dict -from typing import List - -from easydiffraction.core.objects import Collection -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.components.experiment_type import RadiationProbeEnum -from easydiffraction.experiments.components.experiment_type import SampleFormEnum -from easydiffraction.experiments.components.experiment_type import ScatteringTypeEnum -from easydiffraction.experiments.experiment import BaseExperiment -from easydiffraction.experiments.experiment import Experiment -from easydiffraction.utils.decorators import enforce_type +from typeguard import typechecked + +from easydiffraction.core.datablock import DatablockCollection +from easydiffraction.experiments.experiment.base import ExperimentBase +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import RadiationProbeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum +from easydiffraction.experiments.experiment.factory import ExperimentFactory from easydiffraction.utils.formatting import paragraph -class Experiments(Collection): - """Collection manager for multiple Experiment instances.""" +class Experiments(DatablockCollection): + """Collection of Experiment data blocks. - @property - def _child_class(self): - return BaseExperiment + Provides convenience constructors for common creation patterns and + helper methods for simple presentation of collection contents. + """ def __init__(self) -> None: - super().__init__() - self._experiments: Dict[str, BaseExperiment] = self._items # Alias for legacy support + super().__init__(item_type=ExperimentBase) - def add(self, experiment: BaseExperiment): - """Add a pre-built experiment instance.""" - self._add_prebuilt_experiment(experiment) + # -------------------- + # Add / Remove methods + # -------------------- + @typechecked def add_from_cif_path(self, cif_path: str): - """Add a new experiment from a CIF file path.""" - experiment = Experiment(cif_path=cif_path) - self._add_prebuilt_experiment(experiment) + """Add an experiment from a CIF file path. + Args: + cif_path: Path to a CIF document. + """ + experiment = ExperimentFactory.create(cif_path=cif_path) + self.add(experiment) + + @typechecked def add_from_cif_str(self, cif_str: str): - """Add a new experiment from CIF file content (string).""" - experiment = Experiment(cif_str=cif_str) - self._add_prebuilt_experiment(experiment) + """Add an experiment from a CIF string. + + Args: + cif_str: Full CIF document as a string. + """ + experiment = ExperimentFactory.create(cif_str=cif_str) + self.add(experiment) + @typechecked def add_from_data_path( self, name: str, @@ -49,8 +57,17 @@ def add_from_data_path( radiation_probe: str = RadiationProbeEnum.default().value, scattering_type: str = ScatteringTypeEnum.default().value, ): - """Add a new experiment from a data file path.""" - experiment = Experiment( + """Add an experiment from a data file path. + + Args: + name: Experiment identifier. + data_path: Path to the measured data file. + sample_form: Sample form (powder or single crystal). + beam_mode: Beam mode (constant wavelength or TOF). + radiation_probe: Radiation probe (neutron or xray). + scattering_type: Scattering type (bragg or total). + """ + experiment = ExperimentFactory.create( name=name, data_path=data_path, sample_form=sample_form, @@ -58,8 +75,9 @@ def add_from_data_path( radiation_probe=radiation_probe, scattering_type=scattering_type, ) - self._add_prebuilt_experiment(experiment) + self.add(experiment) + @typechecked def add_without_data( self, name: str, @@ -68,35 +86,40 @@ def add_without_data( radiation_probe: str = RadiationProbeEnum.default().value, scattering_type: str = ScatteringTypeEnum.default().value, ): - """Add a new experiment without any data file.""" - experiment = Experiment( + """Add an experiment without associating a data file. + + Args: + name: Experiment identifier. + sample_form: Sample form (powder or single crystal). + beam_mode: Beam mode (constant wavelength or TOF). + radiation_probe: Radiation probe (neutron or xray). + scattering_type: Scattering type (bragg or total). + """ + experiment = ExperimentFactory.create( name=name, sample_form=sample_form, beam_mode=beam_mode, radiation_probe=radiation_probe, scattering_type=scattering_type, ) - self._add_prebuilt_experiment(experiment) + self.add(experiment) - @enforce_type - def _add_prebuilt_experiment(self, experiment: BaseExperiment): - self._experiments[experiment.name] = experiment + @typechecked + def remove(self, name: str) -> None: + """Remove an experiment by name if it exists.""" + if name in self: + del self[name] - def remove(self, experiment_id: str) -> None: - if experiment_id in self._experiments: - del self._experiments[experiment_id] + # ------------ + # Show methods + # ------------ def show_names(self) -> None: + """Print the list of experiment names.""" print(paragraph('Defined experiments' + ' 🔬')) - print(self.ids) - - @property - def ids(self) -> List[str]: - return list(self._experiments.keys()) + print(self.names) def show_params(self) -> None: - for exp in self._experiments.values(): - print(exp) - - def as_cif(self) -> str: - return '\n\n'.join([exp.as_cif() for exp in self._experiments.values()]) + """Print parameters for each experiment in the collection.""" + for exp in self.values(): + exp.show_params() diff --git a/src/easydiffraction/io/cif/handler.py b/src/easydiffraction/io/cif/handler.py new file mode 100644 index 00000000..a21ae16e --- /dev/null +++ b/src/easydiffraction/io/cif/handler.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Minimal CIF tag handler used by descriptors/parameters.""" + +from __future__ import annotations + + +class CifHandler: + """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. + """ + + def __init__(self, *, names: list[str]) -> None: + self._names = names + self._owner = None # set by attach + + def attach(self, owner): + """Attach to a descriptor or parameter instance.""" + self._owner = owner + + @property + def names(self) -> list[str]: + """List of CIF tag names associated with the owner.""" + return self._names + + @property + def uid(self) -> str | None: + """Unique identifier taken from the owner, if attached.""" + if self._owner is None: + return None + return self._owner.unique_name diff --git a/src/easydiffraction/io/cif/serialize.py b/src/easydiffraction/io/cif/serialize.py new file mode 100644 index 00000000..09013a62 --- /dev/null +++ b/src/easydiffraction/io/cif/serialize.py @@ -0,0 +1,268 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typing import Any +from typing import Optional +from typing import Sequence + +import numpy as np + +from easydiffraction.utils.utils import str_to_ufloat + + +def format_value(value) -> str: + """Format a single CIF value, quoting strings with whitespace.""" + if isinstance(value, str) and (' ' in value or '\t' in value): + return f'"{value}"' + return str(value) + + +def param_to_cif(param) -> str: + """Render a single descriptor/parameter to a CIF line. + + Expects ``param`` to expose ``_cif_handler.names`` and ``value``. + """ + tags: Sequence[str] = param._cif_handler.names # type: ignore[attr-defined] + main_key: str = tags[0] + return f'{main_key} {format_value(param.value)}' + + +def category_item_to_cif(item) -> str: + """Render a CategoryItem-like object to CIF text. + + Expects ``item.parameters`` iterable of params with + ``_cif_handler.names`` and ``value``. + """ + lines: list[str] = [] + for p in item.parameters: + lines.append(param_to_cif(p)) + return '\n'.join(lines) + + +def category_collection_to_cif(collection) -> str: + """Render a CategoryCollection-like object to CIF text. + + Uses first item to build loop header, then emits rows for each item. + """ + if not len(collection): + return '' + + lines: list[str] = [] + + # Header + first_item = list(collection.values())[0] + lines.append('loop_') + for p in first_item.parameters: + tags = p._cif_handler.names # type: ignore[attr-defined] + lines.append(tags[0]) + + # Rows + for item in collection.values(): + row_vals = [format_value(p.value) for p in item.parameters] + lines.append(' '.join(row_vals)) + + return '\n'.join(lines) + + +def datastore_to_cif(datastore, max_points: Optional[int] = None) -> str: + """Render a datastore to CIF text. + + Expects ``datastore`` to have ``_cif_mapping()`` and attributes per + mapping keys. + """ + cif_lines: list[str] = ['loop_'] + + mapping: dict[str, str] = datastore._cif_mapping() # type: ignore[attr-defined] + for cif_key in mapping.values(): + cif_lines.append(cif_key) + + data_arrays: list[np.ndarray] = [] + for attr_name in mapping: + arr = getattr(datastore, attr_name, None) + data_arrays.append(np.array([]) if arr is None else arr) + + if not data_arrays or not data_arrays[0].size: + return '' + + n_points = len(data_arrays[0]) + + def format_row(i: int) -> str: + return ' '.join(str(data_arrays[j][i]) for j in range(len(data_arrays))) + + if max_points is not None and n_points > 2 * max_points: + for i in range(max_points): + cif_lines.append(format_row(i)) + cif_lines.append('...') + for i in range(-max_points, 0): + cif_lines.append(format_row(i)) + else: + for i in range(n_points): + cif_lines.append(format_row(i)) + + return '\n'.join(cif_lines) + + +def datablock_item_to_cif(datablock) -> str: + """Render a DatablockItem-like object to CIF text. + + Emits a data_ header and then concatenates category CIF sections. + """ + # Local imports to avoid import-time cycles + from easydiffraction.core.category import CategoryCollection + from easydiffraction.core.category import CategoryItem + + header = f'data_{datablock._identity.datablock_entry_name}' + parts: list[str] = [header] + for v in vars(datablock).values(): + if isinstance(v, CategoryItem): + parts.append(v.as_cif) + for v in vars(datablock).values(): + if isinstance(v, CategoryCollection): + parts.append(v.as_cif) + return '\n\n'.join(parts) + + +def datablock_collection_to_cif(collection) -> 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). + """ + from textwrap import wrap + + wrapped_title = wrap(info.title, width=46) + wrapped_description = wrap(info.description, width=46) + + title_str = f"_project.title '{wrapped_title[0]}'" if wrapped_title else '' + for line in wrapped_title[1:]: + title_str += f"\n{' ' * 27}'{line}'" + + if wrapped_description: + base = '_project.description ' + indent = ' ' * len(base) + desc_str = f"{base}'{wrapped_description[0]}" + for line in wrapped_description[1:]: + desc_str += f'\n{indent}{line}' + desc_str += "'" + else: + desc_str = "_project.description ''" + + created = info._created.strftime('%d %b %Y %H:%M:%S') + modified = info._last_modified.strftime('%d %b %Y %H:%M:%S') + + return ( + f'_project.id {info.name}\n' + f'{title_str}\n' + f'{desc_str}\n' + f"_project.created '{created}'\n" + f"_project.last_modified '{modified}'\n" + ) + + +def project_to_cif(project) -> str: + """Render a whole project by concatenating sections when present.""" + parts: list[str] = [] + if hasattr(project, 'info'): + parts.append(project.info.as_cif) + if getattr(project, 'sample_models', None): + parts.append(project.sample_models.as_cif) + if getattr(project, 'experiments', None): + parts.append(project.experiments.as_cif) + if getattr(project, 'analysis', None): + parts.append(project.analysis.as_cif()) + if getattr(project, 'summary', None): + parts.append(project.summary.as_cif()) + return '\n\n'.join([p for p in parts if p]) + + +def experiment_to_cif(experiment) -> str: + """Render an experiment: datablock part plus measured data.""" + block = datablock_item_to_cif(experiment) + data = experiment.datastore.as_cif + return f'{block}\n\n{data}' if data else block + + +def analysis_to_cif(analysis) -> str: + """Render analysis metadata, aliases, and constraints to CIF.""" + cur_min = format_value(analysis.current_minimizer) + lines: list[str] = [] + lines.append(f'_analysis.calculator_engine {format_value(analysis.current_calculator)}') + lines.append(f'_analysis.fitting_engine {cur_min}') + lines.append(f'_analysis.fit_mode {format_value(analysis.fit_mode)}') + lines.append('') + lines.append(analysis.aliases.as_cif) + lines.append('') + lines.append(analysis.constraints.as_cif) + return '\n'.join(lines) + + +def summary_to_cif(_summary) -> str: + """Render a summary CIF block (placeholder for now).""" + return 'To be added...' + + +# TODO: Check the following methods: + + +def param_from_cif(self, block: Any, idx: int = 0) -> None: + found_values: list[Any] = [] + for tag in self.full_cif_names: + candidate = list(block.find_values(tag)) + if candidate: + found_values = candidate + break + if not found_values: + self.value = self.default_value + return + raw = found_values[idx] + if self.value_type is float: + u = str_to_ufloat(raw) + self.value = u.n + if hasattr(self, 'uncertainty'): + self.uncertainty = u.s # type: ignore[attr-defined] + elif self.value_type is str: + if len(raw) >= 2 and raw[0] == raw[-1] and raw[0] in {"'", '"'}: + self.value = raw[1:-1] + else: + self.value = raw + else: + self.value = raw + + +def category_item_from_cif(self, block, idx: int = 0) -> None: + """Populate each parameter from CIF block at given loop index.""" + for param in self.parameters: + param.from_cif(block, idx=idx) + + +# TODO: from_cif or add_from_cif as in collections? +def category_collection_from_cif(self, block): + # Derive loop size using category_entry_name first CIF tag alias + if self._item_type is None: + raise ValueError('Child class is not defined.') + # TODO: Find a better way and then remove TODO in the AtomSite + # class + # Create a temporary instance to access category_entry_name + # attribute used as ID column for the items in this collection + child_obj = self._item_type() + entry_attr = getattr(child_obj, child_obj._category_entry_attr_name) + # Try to find the value(s) from the CIF block iterating over + # the possible cif names in order of preference. + size = 0 + for name in entry_attr.full_cif_names: + size = len(block.find_values(name)) + break + # If no values found, nothing to do + if not size: + return + # If values found, delegate to child class to parse each + # row and add to collection + for row_idx in range(size): + child_obj = self._item_type() + child_obj.from_cif(block, idx=row_idx) + self.add(child_obj) diff --git a/src/easydiffraction/plotting/plotters/plotter_ascii.py b/src/easydiffraction/plotting/plotters/plotter_ascii.py index 7b257283..2694eace 100644 --- a/src/easydiffraction/plotting/plotters/plotter_ascii.py +++ b/src/easydiffraction/plotting/plotters/plotter_ascii.py @@ -16,6 +16,8 @@ class AsciiPlotter(PlotterBase): + """Terminal-based plotter using ASCII art.""" + def _get_legend_item(self, label): color_start = DEFAULT_COLORS[label] color_end = asciichartpy.reset @@ -33,6 +35,17 @@ def plot( title, height=None, ): + """Render a compact ASCII chart in the terminal. + + 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. + """ # Intentionally unused; kept for a consistent plotting API del axes_labels title = paragraph(title) diff --git a/src/easydiffraction/plotting/plotters/plotter_base.py b/src/easydiffraction/plotting/plotters/plotter_base.py index 98d02d39..45bf1068 100644 --- a/src/easydiffraction/plotting/plotters/plotter_base.py +++ b/src/easydiffraction/plotting/plotters/plotter_base.py @@ -6,8 +6,8 @@ import numpy as np -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.components.experiment_type import ScatteringTypeEnum +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum from easydiffraction.utils.utils import is_notebook DEFAULT_ENGINE = 'plotly' if is_notebook() else 'asciichartpy' @@ -55,6 +55,13 @@ class PlotterBase(ABC): + """Abstract base for plotting backends. + + Concrete implementations should accept x values, multiple y-series + and a small set of labeling arguments and render a plot to the + chosen medium. + """ + @abstractmethod def plot( self, @@ -65,4 +72,14 @@ def plot( title, height, ): + """Render a plot. + + 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 axis titles. + title: Figure title. + height: Backend-specific height in text or pixels. + """ pass diff --git a/src/easydiffraction/plotting/plotters/plotter_plotly.py b/src/easydiffraction/plotting/plotters/plotter_plotly.py index 6e7d95fc..544b991d 100644 --- a/src/easydiffraction/plotting/plotters/plotter_plotly.py +++ b/src/easydiffraction/plotting/plotters/plotter_plotly.py @@ -24,7 +24,11 @@ class PlotlyPlotter(PlotterBase): + """Interactive plotter using Plotly for notebooks and browsers.""" + pio.templates.default = 'plotly_dark' if darkdetect.isDark() else 'plotly_white' + if is_pycharm(): + pio.renderers.default = 'browser' def _get_trace(self, x, y, label): mode = SERIES_CONFIG[label]['mode'] @@ -51,6 +55,16 @@ def plot( title, height=None, ): + """Render an interactive Plotly figure. + + 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. + """ # Intentionally unused; accepted for API compatibility del height data = [] @@ -105,14 +119,13 @@ def plot( layout=layout, ) - # Format the axes ticks - # Keeps decimals for small numbers; - # groups thousands for large ones + # Format the axes ticks. + # Keeps decimals for small numbers; groups thousands for large + # ones fig.update_xaxes(tickformat=',.6~g', separatethousands=True) fig.update_yaxes(tickformat=',.6~g', separatethousands=True) # Show the figure - if is_pycharm() or display is None or HTML is None: fig.show(config=config) else: diff --git a/src/easydiffraction/plotting/plotting.py b/src/easydiffraction/plotting/plotting.py index e68000c6..93e388d7 100644 --- a/src/easydiffraction/plotting/plotting.py +++ b/src/easydiffraction/plotting/plotting.py @@ -1,5 +1,8 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause +"""Plotting facade for measured and calculated patterns.""" + +import numpy as np from easydiffraction.plotting.plotters.plotter_ascii import AsciiPlotter from easydiffraction.plotting.plotters.plotter_base import DEFAULT_AXES_LABELS @@ -14,6 +17,8 @@ class Plotter: + """User-facing plotting facade backed by concrete plotters.""" + def __init__(self): # Plotting engine self._engine = DEFAULT_ENGINE @@ -127,6 +132,7 @@ def plot_meas( x_max=None, d_spacing=False, ): + """Plot measured pattern using current engine.""" if pattern.x is None: error(f'No data available for experiment {expt_name}') return @@ -184,6 +190,7 @@ def plot_calc( x_max=None, d_spacing=False, ): + """Plot calculated pattern using current engine.""" if pattern.x is None: error(f'No data available for experiment {expt_name}') return @@ -242,6 +249,7 @@ def plot_meas_vs_calc( show_residual=False, d_spacing=False, ): + """Plot measured and calculated series and optional residual.""" if pattern.x is None: print(error(f'No data available for experiment {expt_name}')) return @@ -252,7 +260,18 @@ def plot_meas_vs_calc( print(error(f'No calculated data available for experiment {expt_name}')) return + # Select x-axis data based on d-spacing or original x values x_array = pattern.d if d_spacing else pattern.x + + # For asciichartpy, if x_min or x_max is not provided, center + # around the maximum intensity peak + if self._engine == 'asciichartpy' and (x_min is None or x_max is None): + max_intensity_pos = np.argmax(pattern.meas) + half_range = 50 + x_min = x_array[max_intensity_pos - half_range] + x_max = x_array[max_intensity_pos + half_range] + + # Filter x, y_meas, and y_calc based on x_min and x_max x = self._filtered_y_array( y_array=x_array, x_array=x_array, @@ -323,6 +342,8 @@ def _filtered_y_array( class PlotterFactory: + """Factory for plotter implementations.""" + _SUPPORTED_ENGINES_DICT = { 'asciichartpy': { 'description': 'Console ASCII line charts', @@ -336,12 +357,14 @@ class PlotterFactory: @classmethod def supported_engines(cls): + """Return list of supported engine names.""" keys = cls._SUPPORTED_ENGINES_DICT.keys() engines = list(keys) return engines @classmethod def create_plotter(cls, engine_name): + """Create a concrete plotter by engine name.""" config = cls._SUPPORTED_ENGINES_DICT.get(engine_name) if not config: supported_engines = cls.supported_engines() diff --git a/src/easydiffraction/project/__init__.py b/src/easydiffraction/project/__init__.py new file mode 100644 index 00000000..3e95b5e9 --- /dev/null +++ b/src/easydiffraction/project/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/project.py b/src/easydiffraction/project/project.py similarity index 59% rename from src/easydiffraction/project.py rename to src/easydiffraction/project/project.py index 732ef96a..38252a5a 100644 --- a/src/easydiffraction/project.py +++ b/src/easydiffraction/project/project.py @@ -1,155 +1,136 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause +"""Project facade to orchestrate models, experiments, and analysis.""" -import datetime import pathlib import tempfile -from textwrap import wrap -from typing import List +from typeguard import typechecked from varname import varname from easydiffraction.analysis.analysis import Analysis -from easydiffraction.experiments.components.experiment_type import BeamModeEnum +from easydiffraction.core.guard import GuardedBase +from easydiffraction.experiments.experiment.enums import BeamModeEnum from easydiffraction.experiments.experiments import Experiments +from easydiffraction.io.cif.serialize import project_to_cif from easydiffraction.plotting.plotting import Plotter +from easydiffraction.project.project_info import ProjectInfo from easydiffraction.sample_models.sample_models import SampleModels -from easydiffraction.summary import Summary +from easydiffraction.summary.summary import Summary from easydiffraction.utils.formatting import error from easydiffraction.utils.formatting import paragraph -from easydiffraction.utils.utils import render_cif from easydiffraction.utils.utils import tof_to_d from easydiffraction.utils.utils import twotheta_to_d -class ProjectInfo: - """Stores metadata about the project, such as name, title, - description, and file paths. +class Project(GuardedBase): + """Central API for managing a diffraction data analysis project. + + Provides access to sample models, experiments, analysis, and + summary. """ - def __init__(self) -> None: - self._name: str = 'untitled_project' - self._title: str = 'Untitled Project' - self._description: str = '' - self._path: pathlib.Path = pathlib.Path.cwd() - self._created: datetime.datetime = datetime.datetime.now() - self._last_modified: datetime.datetime = datetime.datetime.now() + # ------------------------------------------------------------------ + # Initialization + # ------------------------------------------------------------------ + def __init__( + self, + name: str = 'untitled_project', + title: str = 'Untitled Project', + description: str = '', + ) -> None: + super().__init__() + + self._info: ProjectInfo = ProjectInfo(name, title, description) + self._sample_models = SampleModels() + self._experiments = Experiments() + self._plotter = Plotter() + self._analysis = Analysis(self) + self._summary = Summary(self) + self._saved = False + self._varname = varname() - @property - def name(self) -> str: - """Return the project name.""" - return self._name + # ------------------------------------------------------------------ + # Dunder methods + # ------------------------------------------------------------------ + def __str__(self) -> str: + """Human-readable representation.""" + class_name = self.__class__.__name__ + project_name = self.name + sample_models_count = len(self.sample_models) + experiments_count = len(self.experiments) + return ( + f"{class_name} '{project_name}' " + f'({sample_models_count} sample models, ' + f'{experiments_count} experiments)' + ) - @name.setter - def name(self, value: str) -> None: - self._name = value + # ------------------------------------------------------------------ + # Public read-only properties + # ------------------------------------------------------------------ @property - def title(self) -> str: - """Return the project title.""" - return self._title - - @title.setter - def title(self, value: str) -> None: - self._title = value + def info(self) -> ProjectInfo: + """Project metadata container.""" + return self._info @property - def description(self) -> str: - """Return sanitized description with single spaces.""" - return ' '.join(self._description.split()) - - @description.setter - def description(self, value: str) -> None: - self._description = ' '.join(value.split()) + def name(self) -> str: + """Convenience property to access the project's name + directly. + """ + return self._info.name @property - def path(self) -> pathlib.Path: - """Return the project path as a Path object.""" - return self._path - - @path.setter - def path(self, value) -> None: - # Accept str or Path; normalize to Path - self._path = pathlib.Path(value) + def full_name(self) -> str: + return self.name @property - def created(self) -> datetime.datetime: - """Return the creation timestamp.""" - return self._created + def sample_models(self) -> SampleModels: + """Collection of sample models in the project.""" + return self._sample_models - @property - def last_modified(self) -> datetime.datetime: - """Return the last modified timestamp.""" - return self._last_modified - - def update_last_modified(self) -> None: - """Update the last modified timestamp.""" - self._last_modified = datetime.datetime.now() - - def as_cif(self) -> str: - """Export project metadata to CIF.""" - wrapped_title: List[str] = wrap(self.title, width=46) - wrapped_description: List[str] = wrap(self.description, width=46) - - title_str: str = f"_project.title '{wrapped_title[0]}'" - for line in wrapped_title[1:]: - title_str += f"\n{' ' * 27}'{line}'" - - if wrapped_description: - base_indent: str = '_project.description ' - indent_spaces: str = ' ' * len(base_indent) - formatted_description: str = f"{base_indent}'{wrapped_description[0]}" - for line in wrapped_description[1:]: - formatted_description += f'\n{indent_spaces}{line}' - formatted_description += "'" - else: - formatted_description: str = "_project.description ''" + @sample_models.setter + @typechecked + def sample_models(self, sample_models: SampleModels) -> None: + self._sample_models = sample_models - return ( - f'_project.id {self.name}\n' - f'{title_str}\n' - f'{formatted_description}\n' - f"_project.created '{self._created.strftime('%d %b %Y %H:%M:%S')}'\n" - f"_project.last_modified '{self._last_modified.strftime('%d %b %Y %H:%M:%S')}'\n" - ) + @property + def experiments(self): + """Collection of experiments in the project.""" + return self._experiments - def show_as_cif(self) -> None: - cif_text: str = self.as_cif() - paragraph_title: str = paragraph(f"Project 📦 '{self.name}' info as cif") - render_cif(cif_text, paragraph_title) + @experiments.setter + @typechecked + def experiments(self, experiments: Experiments): + self._experiments = experiments + @property + def plotter(self): + """Plotting facade bound to the project.""" + return self._plotter -class Project: - """Central API for managing a diffraction data analysis project. + @property + def analysis(self): + """Analysis entry-point bound to the project.""" + return self._analysis - Provides access to sample models, experiments, analysis, and - summary. - """ + @property + def summary(self): + """Summary report builder bound to the project.""" + return self._summary - def __init__( - self, - name: str = 'untitled_project', - title: str = 'Untitled Project', - description: str = '', - ) -> None: - self.info: ProjectInfo = ProjectInfo() - self.info.name = name - self.info.title = title - self.info.description = description - self.sample_models = SampleModels() - self.experiments = Experiments() - self.plotter = Plotter() - self.analysis = Analysis(self) - self.summary = Summary(self) - self._saved = False - self._varname = varname() + @property + def parameters(self): + """Return parameters from all components (TBD).""" + # To be implemented: return all parameters in the project + return [] @property - def name(self) -> str: - """Convenience property to access the project's name - directly. - """ - return self.info.name + def as_cif(self): + """Export whole project as CIF text.""" + # Concatenate sections using centralized CIF serializers + return project_to_cif(self) # ------------------------------------------ # Project File I/O @@ -162,84 +143,74 @@ def load(self, dir_path: str) -> None: """ print(paragraph(f'Loading project 📦 from {dir_path}')) print(dir_path) - self.info.path = dir_path + self._info.path = dir_path # TODO: load project components from files inside dir_path print('Loading project is not implemented yet.') self._saved = True - def save_as( - self, - dir_path: str, - temporary: bool = False, - ) -> None: - """Save the project into a new directory.""" - if temporary: - tmp: str = tempfile.gettempdir() - dir_path = pathlib.Path(tmp) / dir_path - self.info.path = dir_path - self.save() - def save(self) -> None: """Save the project into the existing project directory.""" - if not self.info.path: + if not self._info.path: print(error('Project path not specified. Use save_as() to define the path first.')) return print(paragraph(f"Saving project 📦 '{self.name}' to")) - print(self.info.path.resolve()) + print(self._info.path.resolve()) # Ensure project directory exists - self.info.path.mkdir(parents=True, exist_ok=True) + self._info.path.mkdir(parents=True, exist_ok=True) # Save project info - with (self.info.path / 'project.cif').open('w') as f: - f.write(self.info.as_cif()) + with (self._info.path / 'project.cif').open('w') as f: + f.write(self._info.as_cif()) print('✅ project.cif') # Save sample models - sm_dir = self.info.path / 'sample_models' + sm_dir = self._info.path / 'sample_models' sm_dir.mkdir(parents=True, exist_ok=True) - for model in self.sample_models: + # Iterate over sample model objects (MutableMapping iter gives + # keys) + for model in self.sample_models.values(): file_name: str = f'{model.name}.cif' file_path = sm_dir / file_name with file_path.open('w') as f: - f.write(model.as_cif()) + f.write(model.as_cif) print(f'✅ sample_models/{file_name}') # Save experiments - expt_dir = self.info.path / 'experiments' + expt_dir = self._info.path / 'experiments' expt_dir.mkdir(parents=True, exist_ok=True) - for experiment in self.experiments: + for experiment in self.experiments.values(): file_name: str = f'{experiment.name}.cif' file_path = expt_dir / file_name with file_path.open('w') as f: - f.write(experiment.as_cif()) + f.write(experiment.as_cif) print(f'✅ experiments/{file_name}') # Save analysis - with (self.info.path / 'analysis.cif').open('w') as f: + with (self._info.path / 'analysis.cif').open('w') as f: f.write(self.analysis.as_cif()) print('✅ analysis.cif') # Save summary - with (self.info.path / 'summary.cif').open('w') as f: + with (self._info.path / 'summary.cif').open('w') as f: f.write(self.summary.as_cif()) print('✅ summary.cif') - self.info.update_last_modified() + self._info.update_last_modified() self._saved = True - # ------------------------------------------ - # Sample Models API Convenience Methods - # ------------------------------------------ - - def set_sample_models(self, sample_models: SampleModels) -> None: - """Attach a collection of sample models to the project.""" - self.sample_models = sample_models - - def set_experiments(self, experiments: Experiments) -> None: - """Attach a collection of experiments to the project.""" - self.experiments = experiments + def save_as( + self, + dir_path: str, + temporary: bool = False, + ) -> None: + """Save the project into a new directory.""" + if temporary: + tmp: str = tempfile.gettempdir() + dir_path = pathlib.Path(tmp) / dir_path + self._info.path = dir_path + self.save() # ------------------------------------------ # Plotting diff --git a/src/easydiffraction/project/project_info.py b/src/easydiffraction/project/project_info.py new file mode 100644 index 00000000..56a4cdd1 --- /dev/null +++ b/src/easydiffraction/project/project_info.py @@ -0,0 +1,104 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Project metadata container used by Project.""" + +import datetime +import pathlib + +from easydiffraction import paragraph +from easydiffraction.core.guard import GuardedBase +from easydiffraction.io.cif.serialize import project_info_to_cif +from easydiffraction.utils.utils import render_cif + + +class ProjectInfo(GuardedBase): + """Stores metadata about the project, such as name, title, + description, and file paths. + """ + + def __init__( + self, + name: str = 'untitled_project', + title: str = 'Untitled Project', + description: str = '', + ) -> None: + super().__init__() + + self._name = name + self._title = title + self._description = description + self._path: pathlib.Path = pathlib.Path.cwd() + self._created: datetime.datetime = datetime.datetime.now() + self._last_modified: datetime.datetime = datetime.datetime.now() + + @property + def name(self) -> str: + """Return the project name.""" + return self._name + + @name.setter + def name(self, value: str) -> None: + self._name = value + + @property + def unique_name(self) -> str: + """Unique name for GuardedBase diagnostics.""" + return self.name + + @property + def title(self) -> str: + """Return the project title.""" + return self._title + + @title.setter + def title(self, value: str) -> None: + self._title = value + + @property + def description(self) -> str: + """Return sanitized description with single spaces.""" + return ' '.join(self._description.split()) + + @description.setter + def description(self, value: str) -> None: + self._description = ' '.join(value.split()) + + @property + def path(self) -> pathlib.Path: + """Return the project path as a Path object.""" + return self._path + + @path.setter + def path(self, value) -> None: + # Accept str or Path; normalize to Path + self._path = pathlib.Path(value) + + @property + def created(self) -> datetime.datetime: + """Return the creation timestamp.""" + return self._created + + @property + def last_modified(self) -> datetime.datetime: + """Return the last modified timestamp.""" + return self._last_modified + + def update_last_modified(self) -> None: + """Update the last modified timestamp.""" + self._last_modified = datetime.datetime.now() + + def parameters(self): + """Placeholder for parameter listing.""" + pass + + # TODO: Consider moving to io.cif.serialize + def as_cif(self) -> str: + """Export project metadata to CIF.""" + return project_info_to_cif(self) + + # TODO: Consider moving to io.cif.serialize + def show_as_cif(self) -> None: + """Pretty-print CIF via shared utilities.""" + cif_text: str = self.as_cif() + paragraph_title: str = paragraph(f"Project 📦 '{self.name}' info as cif") + render_cif(cif_text, paragraph_title) diff --git a/src/easydiffraction/sample_models/categories/__init__.py b/src/easydiffraction/sample_models/categories/__init__.py new file mode 100644 index 00000000..3e95b5e9 --- /dev/null +++ b/src/easydiffraction/sample_models/categories/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/sample_models/categories/atom_sites.py b/src/easydiffraction/sample_models/categories/atom_sites.py new file mode 100644 index 00000000..5535b27f --- /dev/null +++ b/src/easydiffraction/sample_models/categories/atom_sites.py @@ -0,0 +1,295 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Atom site category. + +Defines AtomSite items and AtomSites collection used in sample models. +Only documentation was added; behavior remains unchanged. +""" + +from cryspy.A_functions_base.database import DATABASE + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import MembershipValidator +from easydiffraction.core.validation import RangeValidator +from easydiffraction.core.validation import RegexValidator +from easydiffraction.io.cif.handler import CifHandler + + +class AtomSite(CategoryItem): + """Single atom site with fractional coordinates and ADP. + + Attributes are represented by descriptors to support validation and + CIF serialization. + """ + + def __init__( + self, + *, + label=None, + type_symbol=None, + fract_x=None, + fract_y=None, + fract_z=None, + wyckoff_letter=None, + occupancy=None, + b_iso=None, + adp_type=None, + ) -> None: + super().__init__() + + self._label: StringDescriptor = StringDescriptor( + name='label', + description='Unique identifier for the atom site.', + value_spec=AttributeSpec( + value=label, + type_=DataTypes.STRING, + default='Si', + # TODO: the following pattern is valid for dict key + # (keywords are not checked). CIF label is less strict. + # Do we need conversion between CIF and internal label? + content_validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.label', + ] + ), + ) + self._type_symbol: StringDescriptor = StringDescriptor( + name='type_symbol', + description='Chemical symbol of the atom at this site.', + value_spec=AttributeSpec( + value=type_symbol, + type_=DataTypes.STRING, + default='Tb', + content_validator=MembershipValidator(allowed=self._type_symbol_allowed_values), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.type_symbol', + ] + ), + ) + self._fract_x: Parameter = Parameter( + name='fract_x', + description='Fractional x-coordinate of the atom site within the unit cell.', + value_spec=AttributeSpec( + value=fract_x, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.fract_x', + ] + ), + ) + self._fract_y: Parameter = Parameter( + name='fract_y', + description='Fractional y-coordinate of the atom site within the unit cell.', + value_spec=AttributeSpec( + value=fract_y, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.fract_y', + ] + ), + ) + self._fract_z: Parameter = Parameter( + name='fract_z', + description='Fractional z-coordinate of the atom site within the unit cell.', + value_spec=AttributeSpec( + value=fract_z, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.fract_z', + ] + ), + ) + self._wyckoff_letter: StringDescriptor = StringDescriptor( + name='wyckoff_letter', + description='Wyckoff letter indicating the symmetry of the ' + 'atom site within the space group.', + value_spec=AttributeSpec( + value=wyckoff_letter, + type_=DataTypes.STRING, + default=self._wyckoff_letter_default_value, + content_validator=MembershipValidator(allowed=self._wyckoff_letter_allowed_values), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.Wyckoff_letter', + '_atom_site.Wyckoff_symbol', + ] + ), + ) + self._occupancy: Parameter = Parameter( + name='occupancy', + description='Occupancy of the atom site, representing the ' + 'fraction of the site occupied by the atom type.', + value_spec=AttributeSpec( + value=occupancy, + type_=DataTypes.NUMERIC, + default=1.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.occupancy', + ] + ), + ) + self._b_iso: Parameter = Parameter( + name='b_iso', + description='Isotropic atomic displacement parameter (ADP) for the atom site.', + value_spec=AttributeSpec( + value=b_iso, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='Ų', + cif_handler=CifHandler( + names=[ + '_atom_site.B_iso_or_equiv', + ] + ), + ) + self._adp_type: StringDescriptor = StringDescriptor( + name='adp_type', + description='Type of atomic displacement parameter (ADP) ' + 'used (e.g., Biso, Uiso, Uani, Bani).', + value_spec=AttributeSpec( + value=adp_type, + type_=DataTypes.STRING, + default='Biso', + content_validator=MembershipValidator(allowed=['Biso']), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.adp_type', + ] + ), + ) + + self._identity.category_code = 'atom_site' + self._identity.category_entry_name = lambda: self.label.value + + @property + def _type_symbol_allowed_values(self): + return list({key[1] for key in DATABASE['Isotopes']}) + + @property + def _wyckoff_letter_allowed_values(self): + # TODO: Need to now current space group. How to access it? Via + # parent Cell? Then letters = + # list(SPACE_GROUPS[62, 'cab']['Wyckoff_positions'].keys()) + # Temporarily return hardcoded list: + return ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'] + + @property + def _wyckoff_letter_default_value(self): + # TODO: What to pass as default? + return self._wyckoff_letter_allowed_values[0] + + @property + def label(self): + """Label descriptor for the site (unique key).""" + return self._label + + @label.setter + def label(self, value): + self._label.value = value + + @property + def type_symbol(self): + """Chemical symbol descriptor (e.g. 'Si').""" + return self._type_symbol + + @type_symbol.setter + def type_symbol(self, value): + self._type_symbol.value = value + + @property + def adp_type(self): + """ADP type descriptor (e.g. 'Biso').""" + return self._adp_type + + @adp_type.setter + def adp_type(self, value): + self._adp_type.value = value + + @property + def wyckoff_letter(self): + """Wyckoff letter descriptor (space-group position).""" + return self._wyckoff_letter + + @wyckoff_letter.setter + def wyckoff_letter(self, value): + self._wyckoff_letter.value = value + + @property + def fract_x(self): + """Fractional x coordinate descriptor.""" + return self._fract_x + + @fract_x.setter + def fract_x(self, value): + self._fract_x.value = value + + @property + def fract_y(self): + """Fractional y coordinate descriptor.""" + return self._fract_y + + @fract_y.setter + def fract_y(self, value): + self._fract_y.value = value + + @property + def fract_z(self): + """Fractional z coordinate descriptor.""" + return self._fract_z + + @fract_z.setter + def fract_z(self, value): + self._fract_z.value = value + + @property + def occupancy(self): + """Occupancy descriptor (0..1).""" + return self._occupancy + + @occupancy.setter + def occupancy(self, value): + self._occupancy.value = value + + @property + def b_iso(self): + """Isotropic ADP descriptor in Ų.""" + return self._b_iso + + @b_iso.setter + def b_iso(self, value): + self._b_iso.value = value + + +class AtomSites(CategoryCollection): + """Collection of AtomSite instances.""" + + def __init__(self): + super().__init__(item_type=AtomSite) diff --git a/src/easydiffraction/sample_models/categories/cell.py b/src/easydiffraction/sample_models/categories/cell.py new file mode 100644 index 00000000..0e132749 --- /dev/null +++ b/src/easydiffraction/sample_models/categories/cell.py @@ -0,0 +1,157 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Unit cell parameters category for sample models.""" + +from typing import Optional + +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.io.cif.handler import CifHandler + + +class Cell(CategoryItem): + """Unit cell with lengths a, b, c and angles alpha, beta, gamma.""" + + def __init__( + self, + *, + length_a: Optional[int | float] = None, + length_b: Optional[int | float] = None, + length_c: Optional[int | float] = None, + angle_alpha: Optional[int | float] = None, + angle_beta: Optional[int | float] = None, + angle_gamma: Optional[int | float] = None, + ) -> None: + super().__init__() + + self._length_a: Parameter = Parameter( + name='length_a', + description='Length of the a axis of the unit cell.', + value_spec=AttributeSpec( + value=length_a, + type_=DataTypes.NUMERIC, + default=10.0, + content_validator=RangeValidator(ge=0, le=1000), + ), + units='Å', + cif_handler=CifHandler(names=['_cell.length_a']), + ) + self._length_b: Parameter = Parameter( + name='length_b', + description='Length of the b axis of the unit cell.', + value_spec=AttributeSpec( + value=length_b, + type_=DataTypes.NUMERIC, + default=10.0, + content_validator=RangeValidator(ge=0, le=1000), + ), + units='Å', + cif_handler=CifHandler(names=['_cell.length_b']), + ) + self._length_c: Parameter = Parameter( + name='length_c', + description='Length of the c axis of the unit cell.', + value_spec=AttributeSpec( + value=length_c, + type_=DataTypes.NUMERIC, + default=10.0, + content_validator=RangeValidator(ge=0, le=1000), + ), + units='Å', + cif_handler=CifHandler(names=['_cell.length_c']), + ) + self._angle_alpha: Parameter = Parameter( + name='angle_alpha', + description='Angle between edges b and c.', + value_spec=AttributeSpec( + value=angle_alpha, + type_=DataTypes.NUMERIC, + default=90.0, + content_validator=RangeValidator(ge=0, le=180), + ), + units='deg', + cif_handler=CifHandler(names=['_cell.angle_alpha']), + ) + self._angle_beta: Parameter = Parameter( + name='angle_beta', + description='Angle between edges a and c.', + value_spec=AttributeSpec( + value=angle_beta, + type_=DataTypes.NUMERIC, + default=90.0, + content_validator=RangeValidator(ge=0, le=180), + ), + units='deg', + cif_handler=CifHandler(names=['_cell.angle_beta']), + ) + self._angle_gamma: Parameter = Parameter( + name='angle_gamma', + description='Angle between edges a and b.', + value_spec=AttributeSpec( + value=angle_gamma, + type_=DataTypes.NUMERIC, + default=90.0, + content_validator=RangeValidator(ge=0, le=180), + ), + units='deg', + cif_handler=CifHandler(names=['_cell.angle_gamma']), + ) + + self._identity.category_code = 'cell' + + @property + def length_a(self): + """Descriptor for a-axis length in Å.""" + return self._length_a + + @length_a.setter + def length_a(self, value): + self._length_a.value = value + + @property + def length_b(self): + """Descriptor for b-axis length in Å.""" + return self._length_b + + @length_b.setter + def length_b(self, value): + self._length_b.value = value + + @property + def length_c(self): + """Descriptor for c-axis length in Å.""" + return self._length_c + + @length_c.setter + def length_c(self, value): + self._length_c.value = value + + @property + def angle_alpha(self): + """Descriptor for angle alpha in degrees.""" + return self._angle_alpha + + @angle_alpha.setter + def angle_alpha(self, value): + self._angle_alpha.value = value + + @property + def angle_beta(self): + """Descriptor for angle beta in degrees.""" + return self._angle_beta + + @angle_beta.setter + def angle_beta(self, value): + self._angle_beta.value = value + + @property + def angle_gamma(self): + """Descriptor for angle gamma in degrees.""" + return self._angle_gamma + + @angle_gamma.setter + def angle_gamma(self, value): + self._angle_gamma.value = value diff --git a/src/easydiffraction/sample_models/categories/space_group.py b/src/easydiffraction/sample_models/categories/space_group.py new file mode 100644 index 00000000..5b5f2f45 --- /dev/null +++ b/src/easydiffraction/sample_models/categories/space_group.py @@ -0,0 +1,107 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Space group category for crystallographic sample models.""" + +from cryspy.A_functions_base.function_2_space_group import ACCESIBLE_NAME_HM_SHORT +from cryspy.A_functions_base.function_2_space_group import ( + get_it_coordinate_system_codes_by_it_number, +) +from cryspy.A_functions_base.function_2_space_group import get_it_number_by_name_hm_short + +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import MembershipValidator +from easydiffraction.io.cif.handler import CifHandler + + +class SpaceGroup(CategoryItem): + """Space group with Hermann–Mauguin symbol and IT code.""" + + def __init__( + self, + *, + name_h_m: str = None, + it_coordinate_system_code: str = None, + ) -> None: + super().__init__() + self._name_h_m: StringDescriptor = StringDescriptor( + name='name_h_m', + description='Hermann-Mauguin symbol of the space group.', + value_spec=AttributeSpec( + value=name_h_m, + type_=DataTypes.STRING, + default='P 1', + content_validator=MembershipValidator( + allowed=lambda: self._name_h_m_allowed_values + ), + ), + cif_handler=CifHandler( + names=[ + '_space_group.name_H-M_alt', + '_space_group_name_H-M_alt', + '_symmetry.space_group_name_H-M', + '_symmetry_space_group_name_H-M', + ] + ), + ) + self._it_coordinate_system_code: StringDescriptor = StringDescriptor( + name='it_coordinate_system_code', + description='A qualifier identifying which setting in IT is used.', + value_spec=AttributeSpec( + value=it_coordinate_system_code, + type_=DataTypes.STRING, + default=lambda: self._it_coordinate_system_code_default_value, + content_validator=MembershipValidator( + allowed=lambda: self._it_coordinate_system_code_allowed_values + ), + ), + cif_handler=CifHandler( + names=[ + '_space_group.IT_coordinate_system_code', + '_space_group_IT_coordinate_system_code', + '_symmetry.IT_coordinate_system_code', + '_symmetry_IT_coordinate_system_code', + ] + ), + ) + self._identity.category_code = 'space_group' + + def _reset_it_coordinate_system_code(self): + self._it_coordinate_system_code.value = self._it_coordinate_system_code_default_value + + @property + def _name_h_m_allowed_values(self): + return ACCESIBLE_NAME_HM_SHORT + + @property + def _it_coordinate_system_code_allowed_values(self): + name = self.name_h_m.value + it_number = get_it_number_by_name_hm_short(name) + codes = get_it_coordinate_system_codes_by_it_number(it_number) + codes = [str(code) for code in codes] + return codes if codes else [''] + + @property + def _it_coordinate_system_code_default_value(self): + return self._it_coordinate_system_code_allowed_values[0] + + @property + def name_h_m(self): + """Descriptor for Hermann–Mauguin symbol.""" + return self._name_h_m + + @name_h_m.setter + def name_h_m(self, value): + self._name_h_m.value = value + self._reset_it_coordinate_system_code() + + @property + def it_coordinate_system_code(self): + """Descriptor for IT coordinate system code.""" + return self._it_coordinate_system_code + + @it_coordinate_system_code.setter + def it_coordinate_system_code(self, value): + self._it_coordinate_system_code.value = value diff --git a/src/easydiffraction/sample_models/collections/atom_sites.py b/src/easydiffraction/sample_models/collections/atom_sites.py deleted file mode 100644 index fc8af2ed..00000000 --- a/src/easydiffraction/sample_models/collections/atom_sites.py +++ /dev/null @@ -1,113 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor -from easydiffraction.core.objects import Parameter - - -class AtomSite(Component): - """Represents a single atom site within the crystal structure.""" - - @property - def category_key(self): - return 'atom_sites' - - @property - def cif_category_key(self): - return 'atom_site' - - def __init__( - self, - label: str, - type_symbol: str, - fract_x: float, - fract_y: float, - fract_z: float, - wyckoff_letter: str = None, - occupancy: float = 1.0, - b_iso: float = 0.0, - adp_type: str = 'Biso', - ): # TODO: add support for Uiso, Uani and Bani - super().__init__() - - self.label = Descriptor( - value=label, - name='label', - cif_name='label', - description='Unique identifier for the atom site.', - ) - self.type_symbol = Descriptor( - value=type_symbol, - name='type_symbol', - cif_name='type_symbol', - description='Chemical symbol of the atom at this site.', - ) - self.adp_type = Descriptor( - value=adp_type, - name='adp_type', - cif_name='ADP_type', - description='Type of atomic displacement parameter (ADP) ' - 'used (e.g., Biso, Uiso, Uani, Bani).', - ) - self.wyckoff_letter = Descriptor( - value=wyckoff_letter, - name='wyckoff_letter', - cif_name='Wyckoff_letter', - description='Wyckoff letter indicating the symmetry of the ' - 'atom site within the space group.', - ) - self.fract_x = Parameter( - value=fract_x, - name='fract_x', - cif_name='fract_x', - description='Fractional x-coordinate of the atom site within the unit cell.', - ) - self.fract_y = Parameter( - value=fract_y, - name='fract_y', - cif_name='fract_y', - description='Fractional y-coordinate of the atom site within the unit cell.', - ) - self.fract_z = Parameter( - value=fract_z, - name='fract_z', - cif_name='fract_z', - description='Fractional z-coordinate of the atom site within the unit cell.', - ) - self.occupancy = Parameter( - value=occupancy, - name='occupancy', - cif_name='occupancy', - description='Occupancy of the atom site, representing the ' - 'fraction of the site occupied by the atom type.', - ) - self.b_iso = Parameter( - value=b_iso, - name='b_iso', - units='Ų', - cif_name='B_iso_or_equiv', - description='Isotropic atomic displacement parameter (ADP) for the atom site.', - ) - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = label - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class AtomSites(Collection): - """Collection of AtomSite instances.""" - - # TODO: Check, if we can get rid of this property - # We could use class name instead - @property - def _type(self): - return 'category' # datablock or category - - @property - def _child_class(self): - return AtomSite diff --git a/src/easydiffraction/sample_models/components/cell.py b/src/easydiffraction/sample_models/components/cell.py deleted file mode 100644 index a40f784e..00000000 --- a/src/easydiffraction/sample_models/components/cell.py +++ /dev/null @@ -1,75 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Parameter - - -class Cell(Component): - """Represents the unit cell parameters of a sample model.""" - - @property - def category_key(self) -> str: - return 'cell' - - @property - def cif_category_key(self) -> str: - return 'cell' - - def __init__( - self, - length_a: float = 10.0, - length_b: float = 10.0, - length_c: float = 10.0, - angle_alpha: float = 90.0, - angle_beta: float = 90.0, - angle_gamma: float = 90.0, - ) -> None: - super().__init__() - - self.length_a = Parameter( - value=length_a, - name='length_a', - cif_name='length_a', - units='Å', - description='Length of the unit cell edge a.', - ) - self.length_b = Parameter( - value=length_b, - name='length_b', - cif_name='length_b', - units='Å', - description='Length of the unit cell edge b.', - ) - self.length_c = Parameter( - value=length_c, - name='length_c', - cif_name='length_c', - units='Å', - description='Length of the unit cell edge c.', - ) - self.angle_alpha = Parameter( - value=angle_alpha, - name='angle_alpha', - cif_name='angle_alpha', - units='deg', - description='Angle between edges b and c.', - ) - self.angle_beta = Parameter( - value=angle_beta, - name='angle_beta', - cif_name='angle_beta', - units='deg', - description='Angle between edges a and c.', - ) - self.angle_gamma = Parameter( - value=angle_gamma, - name='angle_gamma', - cif_name='angle_gamma', - units='deg', - description='Angle between edges a and b.', - ) - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True diff --git a/src/easydiffraction/sample_models/components/space_group.py b/src/easydiffraction/sample_models/components/space_group.py deleted file mode 100644 index b300cea0..00000000 --- a/src/easydiffraction/sample_models/components/space_group.py +++ /dev/null @@ -1,43 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from typing import Optional - -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor - - -class SpaceGroup(Component): - """Represents the space group of a sample model.""" - - @property - def category_key(self) -> str: - return 'space_group' - - @property - def cif_category_key(self) -> str: - return 'space_group' - - def __init__( - self, - name_h_m: str = 'P 1', - it_coordinate_system_code: Optional[int] = None, - ) -> None: - super().__init__() - - self.name_h_m = Descriptor( - value=name_h_m, - name='name_h_m', - cif_name='name_H-M_alt', - description='Hermann-Mauguin symbol of the space group.', - ) - self.it_coordinate_system_code = Descriptor( - value=it_coordinate_system_code, - name='it_coordinate_system_code', - cif_name='IT_coordinate_system_code', - description='A qualifier identifying which setting in IT is used.', - ) - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True diff --git a/src/easydiffraction/sample_models/sample_model/__init__.py b/src/easydiffraction/sample_models/sample_model/__init__.py new file mode 100644 index 00000000..3e95b5e9 --- /dev/null +++ b/src/easydiffraction/sample_models/sample_model/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/sample_models/sample_model.py b/src/easydiffraction/sample_models/sample_model/base.py similarity index 57% rename from src/easydiffraction/sample_models/sample_model.py rename to src/easydiffraction/sample_models/sample_model/base.py index 6ce82842..302af96d 100644 --- a/src/easydiffraction/sample_models/sample_model.py +++ b/src/easydiffraction/sample_models/sample_model/base.py @@ -1,86 +1,99 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause -from easydiffraction.core.objects import Datablock +from easydiffraction import paragraph +from easydiffraction.core.datablock import DatablockItem from easydiffraction.crystallography import crystallography as ecr -from easydiffraction.sample_models.collections.atom_sites import AtomSites -from easydiffraction.sample_models.components.cell import Cell -from easydiffraction.sample_models.components.space_group import SpaceGroup -from easydiffraction.utils.decorators import enforce_type -from easydiffraction.utils.formatting import paragraph +from easydiffraction.sample_models.categories.atom_sites import AtomSites +from easydiffraction.sample_models.categories.cell import Cell +from easydiffraction.sample_models.categories.space_group import SpaceGroup from easydiffraction.utils.utils import render_cif -class SampleModel(Datablock): - """Represents an individual structural model of a sample. +class SampleModelBase(DatablockItem): + """Base sample model and container for structural information. - Wraps crystallographic information including space group, cell, and - atomic sites. + Holds space group, unit cell and atom-site categories. The + factory is responsible for creating rich instances from CIF; + this base accepts just the ``name`` and exposes helpers for + applying symmetry. """ - # TODO: Move cif_path and cif_str out of __init__ and into separate - # methods def __init__( self, - name: str, - cif_path: str = None, - cif_str: str = None, - ): + *, + name, + ) -> None: super().__init__() self._name = name - self.space_group = SpaceGroup() - self.cell = Cell() - self.atom_sites = AtomSites() - - if cif_path: - self.load_from_cif_file(cif_path) - elif cif_str: - self.load_from_cif_string(cif_str) - - # ----------- - # Space group - # ----------- + self._cell: Cell = Cell() + self._space_group: SpaceGroup = SpaceGroup() + self._atom_sites: AtomSites = AtomSites() + self._identity.datablock_entry_name = lambda: self.name + + def __str__(self) -> str: + """Human-readable representation of this component.""" + name = self._log_name + items = ', '.join( + f'{k}={v}' + for k, v in { + 'cell': self.cell, + 'space_group': self.space_group, + 'atom_sites': self.atom_sites, + }.items() + ) + return f'<{name} ({items})>' @property - def space_group(self): - return self._space_group + def name(self) -> str: + """Model name. - @space_group.setter - @enforce_type - def space_group(self, new_space_group: SpaceGroup): - self._space_group = new_space_group + Returns: + The user-facing identifier for this model. + """ + return self._name - # ---- - # Cell - # ---- + @name.setter + def name(self, new: str) -> None: + """Update model name.""" + self._name = new @property - def cell(self): + def cell(self) -> Cell: + """Unit-cell category object.""" return self._cell @cell.setter - @enforce_type - def cell(self, new_cell: Cell): - self._cell = new_cell + def cell(self, new: Cell) -> None: + """Replace the unit-cell category object.""" + self._cell = new + + @property + def space_group(self) -> SpaceGroup: + """Space-group category object.""" + return self._space_group - # ---------- - # Atom sites - # ---------- + @space_group.setter + def space_group(self, new: SpaceGroup) -> None: + """Replace the space-group category object.""" + self._space_group = new @property - def atom_sites(self): + def atom_sites(self) -> AtomSites: + """Atom-sites collection for this model.""" return self._atom_sites @atom_sites.setter - @enforce_type - def atom_sites(self, new_atom_sites: AtomSites): - self._atom_sites = new_atom_sites + def atom_sites(self, new: AtomSites) -> None: + """Replace the atom-sites collection.""" + self._atom_sites = new # -------------------- # Symmetry constraints # -------------------- def _apply_cell_symmetry_constraints(self): + """Apply symmetry rules to unit-cell parameters in place.""" dummy_cell = { 'lattice_a': self.cell.length_a.value, 'lattice_b': self.cell.length_b.value, @@ -99,6 +112,9 @@ def _apply_cell_symmetry_constraints(self): self.cell.angle_gamma.value = dummy_cell['angle_gamma'] def _apply_atomic_coordinates_symmetry_constraints(self): + """Apply symmetry rules to fractional coordinates of atom + sites. + """ space_group_name = self.space_group.name_h_m.value space_group_coord_code = self.space_group.it_coordinate_system_code.value for atom in self.atom_sites: @@ -127,56 +143,17 @@ def _apply_atomic_coordinates_symmetry_constraints(self): atom.fract_z.value = dummy_atom['fract_z'] def _apply_atomic_displacement_symmetry_constraints(self): + """Placeholder for ADP symmetry constraints (not + implemented). + """ pass def apply_symmetry_constraints(self): + """Apply all available symmetry constraints to this model.""" self._apply_cell_symmetry_constraints() self._apply_atomic_coordinates_symmetry_constraints() self._apply_atomic_displacement_symmetry_constraints() - # ----------------- - # Creation from CIF - # ----------------- - - def load_from_cif_file(self, cif_path: str): - """Load model data from a CIF file.""" - # TODO: Implement CIF parsing here - print(f'Loading SampleModel from CIF file: {cif_path}') - print('Not implemented yet.') - - def load_from_cif_string(self, cif_str: str): - """Load model data from a CIF string.""" - # TODO: Implement CIF parsing from a string - print('Loading SampleModel from CIF string.') - print('Not implemented yet.') - # Intentionally unused, to avoid linter warning unless - # implemented later - del cif_str - - # ----------------- - # Convertion to CIF - # ----------------- - - def as_cif(self) -> str: - """Export the sample model to CIF format. - - Returns: - str: CIF string representation of the sample model. - """ - # Data block header - cif_lines = [f'data_{self.name}'] - - # Space Group - cif_lines += ['', self.space_group.as_cif()] - - # Unit Cell - cif_lines += ['', self.cell.as_cif()] - - # Atom Sites - cif_lines += ['', self.atom_sites.as_cif()] - - return '\n'.join(cif_lines) - # ------------ # Show methods # ------------ @@ -187,16 +164,19 @@ def show_structure(self): print('Not implemented yet.') def show_params(self): - """Display structural parameters (space group, unit cell, atomic + """Display structural parameters (space group, cell, atom sites). """ print(f'\nSampleModel ID: {self.name}') print(f'Space group: {self.space_group.name_h_m}') - print(f'Cell parameters: {self.cell.as_dict()}') + print(f'Cell parameters: {self.cell.as_dict}') print('Atom sites:') self.atom_sites.show() def show_as_cif(self) -> None: - cif_text: str = self.as_cif() + """Render the CIF text for this model in a terminal-friendly + view. + """ + cif_text: str = self.as_cif paragraph_title: str = paragraph(f"Sample model 🧩 '{self.name}' as cif") render_cif(cif_text, paragraph_title) diff --git a/src/easydiffraction/sample_models/sample_model/factory.py b/src/easydiffraction/sample_models/sample_model/factory.py new file mode 100644 index 00000000..9346e97c --- /dev/null +++ b/src/easydiffraction/sample_models/sample_model/factory.py @@ -0,0 +1,170 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Factory for creating sample models from simple inputs or CIF. + +Supports three argument combinations: ``name``, ``cif_path``, or +``cif_str``. Returns a minimal ``SampleModelBase`` populated from CIF +when provided, or an empty model with the given name. +""" + +from __future__ import annotations + +import gemmi + +from easydiffraction.core.factory import FactoryBase +from easydiffraction.sample_models.sample_model.base import SampleModelBase + + +class SampleModelFactory(FactoryBase): + """Create ``SampleModelBase`` instances from supported inputs.""" + + _ALLOWED_ARG_SPECS = [ + {'required': ['name'], 'optional': []}, + {'required': ['cif_path'], 'optional': []}, + {'required': ['cif_str'], 'optional': []}, + ] + + @classmethod + def create(cls, **kwargs) -> SampleModelBase: + """Create a model based on a validated argument combination. + + Keyword Args: + name: Name of the sample model to create. + cif_path: Path to a CIF file to parse. + cif_str: Raw CIF string to parse. + **kwargs: Extra args are ignored if None; only the above + three keys are supported. + + Returns: + SampleModelBase: A populated or empty model instance. + """ + # Check for valid argument combinations + user_args = {k for k, v in kwargs.items() if v is not None} + cls._validate_args(user_args, cls._ALLOWED_ARG_SPECS, cls.__name__) + + if 'cif_path' in kwargs: + return cls._create_from_cif_path(kwargs['cif_path']) + elif 'cif_str' in kwargs: + return cls._create_from_cif_str(kwargs['cif_str']) + elif 'name' in kwargs: + return SampleModelBase(name=kwargs['name']) + + # ------------------------------- + # Private creation helper methods + # ------------------------------- + + @classmethod + def _create_from_cif_path( + cls, + cif_path: str, + ) -> SampleModelBase: + """Create a model by reading and parsing a CIF file.""" + # Parse CIF and build model + doc = cls._read_cif_document_from_path(cif_path) + block = cls._pick_first_structural_block(doc) + return cls._create_model_from_block(block) + + @classmethod + def _create_from_cif_str( + cls, + cif_str: str, + ) -> SampleModelBase: + """Create a model by parsing a CIF string.""" + # Parse CIF string and build model + doc = cls._read_cif_document_from_string(cif_str) + block = cls._pick_first_structural_block(doc) + return cls._create_model_from_block(block) + + # TODO: Move to io.cif.parse? + + # ------------- + # gemmi helpers + # ------------- + + @staticmethod + def _read_cif_document_from_path(path: str) -> gemmi.cif.Document: + """Read a CIF document from a file path.""" + return gemmi.cif.read_file(path) + + @staticmethod + def _read_cif_document_from_string(text: str) -> gemmi.cif.Document: + """Read a CIF document from a raw text string.""" + return gemmi.cif.read_string(text) + + @staticmethod + def _has_structural_content(block: gemmi.cif.Block) -> bool: + """Return True if the CIF block contains structural content.""" + # Basic heuristics: atom_site loop or full set of cell params + loop = block.find_loop('_atom_site.fract_x') + if loop is not None: + return True + required_cell = [ + '_cell.length_a', + '_cell.length_b', + '_cell.length_c', + '_cell.angle_alpha', + '_cell.angle_beta', + '_cell.angle_gamma', + ] + return all(block.find_value(tag) for tag in required_cell) + + @classmethod + def _pick_first_structural_block( + cls, + doc: gemmi.cif.Document, + ) -> gemmi.cif.Block: + """Pick the most likely structural block from a CIF document.""" + # Prefer blocks with atom_site loop; else first block with cell + for block in doc: + if cls._has_structural_content(block): + return block + # As a fallback, return the sole or first block + try: + return doc.sole_block() + except Exception: + return doc[0] + + @classmethod + def _create_model_from_block( + cls, + block: gemmi.cif.Block, + ) -> SampleModelBase: + """Build a model instance from a single CIF block.""" + name = cls._extract_name_from_block(block) + model = SampleModelBase(name=name) + cls._set_space_group_from_cif_block(model, block) + cls._set_cell_from_cif_block(model, block) + cls._set_atom_sites_from_cif_block(model, block) + return model + + @classmethod + def _extract_name_from_block(cls, block: gemmi.cif.Block) -> str: + """Extract a model name from the CIF block name.""" + return block.name or 'model' + + @classmethod + def _set_space_group_from_cif_block( + cls, + model: SampleModelBase, + block: gemmi.cif.Block, + ) -> None: + """Populate the model's space group from a CIF block.""" + model.space_group.from_cif(block) + + @classmethod + def _set_cell_from_cif_block( + cls, + model: SampleModelBase, + block: gemmi.cif.Block, + ) -> None: + """Populate the model's unit cell from a CIF block.""" + model.cell.from_cif(block) + + @classmethod + def _set_atom_sites_from_cif_block( + cls, + model: SampleModelBase, + block: gemmi.cif.Block, + ) -> None: + """Populate the model's atom sites from a CIF block.""" + model.atom_sites.from_cif(block) diff --git a/src/easydiffraction/sample_models/sample_models.py b/src/easydiffraction/sample_models/sample_models.py index 0f845073..858d982f 100644 --- a/src/easydiffraction/sample_models/sample_models.py +++ b/src/easydiffraction/sample_models/sample_models.py @@ -1,124 +1,74 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause -from typing import List -from typing import Optional +from typeguard import typechecked -from easydiffraction.core.objects import Collection -from easydiffraction.sample_models.sample_model import SampleModel -from easydiffraction.utils.decorators import enforce_type +from easydiffraction.core.datablock import DatablockCollection +from easydiffraction.sample_models.sample_model.base import SampleModelBase +from easydiffraction.sample_models.sample_model.factory import SampleModelFactory from easydiffraction.utils.formatting import paragraph -class SampleModels(Collection): +class SampleModels(DatablockCollection): """Collection manager for multiple SampleModel instances.""" - @property - def _child_class(self): - return SampleModel - def __init__(self) -> None: - super().__init__() # Initialize Collection - self._models = self._items # Alias for legacy support - - def add( - self, - model: Optional[SampleModel] = None, - name: Optional[str] = None, - cif_path: Optional[str] = None, - cif_str: Optional[str] = None, - ) -> None: - """Add a new sample model to the collection. - Dispatches based on input type: pre-built model or parameters - for new creation. + super().__init__(item_type=SampleModelBase) - Args: - model: An existing SampleModel instance. - name: Name for a new model if created from scratch. - cif_path: Path to a CIF file to create a model from. - cif_str: CIF content as string to create a model from. - """ - if model: - self._add_prebuilt_sample_model(model) - else: - self._create_and_add_sample_model(name, cif_path, cif_str) + # -------------------- + # Add / Remove methods + # -------------------- - def remove(self, name: str) -> None: - """Remove a sample model by its ID. + @typechecked + def add_from_cif_path(self, cif_path: str) -> None: + """Create and add a model from a CIF file path. Args: - name: ID of the model to remove. + cif_path: Path to a CIF file. """ - if name in self._models: - del self._models[name] + sample_model = SampleModelFactory.create(cif_path=cif_path) + self.add(sample_model) - def get_ids(self) -> List[str]: - """Return a list of all model IDs in the collection. + @typechecked + def add_from_cif_str(self, cif_str: str) -> None: + """Create and add a model from CIF content (string). - Returns: - List of model IDs. + Args: + cif_str: CIF file content. """ - return list(self._models.keys()) + sample_model = SampleModelFactory.create(cif_str=cif_str) + self.add(sample_model) - @property - def ids(self) -> List[str]: - """Property accessor for model IDs.""" - return self.get_ids() - - def show_names(self) -> None: - """List all model IDs in the collection.""" - print(paragraph('Defined sample models' + ' 🧩')) - print(self.get_ids()) - - def show_params(self) -> None: - """Show parameters of all sample models in the collection.""" - for model in self._models.values(): - model.show_params() + @typechecked + def add_minimal(self, name: str) -> None: + """Create and add a minimal model (defaults, no atoms). - def as_cif(self) -> str: - """Export all sample models to CIF format. - - Returns: - CIF string representation of all sample models. + Args: + name: Identifier to assign to the new model. """ - return '\n'.join([model.as_cif() for model in self._models.values()]) + sample_model = SampleModelFactory.create(name=name) + self.add(sample_model) - @enforce_type - def _add_prebuilt_sample_model(self, sample_model: SampleModel) -> None: - """Add a pre-built SampleModel instance. + @typechecked + def remove(self, name: str) -> None: + """Remove a sample model by its ID. Args: - sample_model: The SampleModel instance to add. - - Raises: - TypeError: If model is not a SampleModel instance. + name: ID of the model to remove. """ - self._models[sample_model.name] = sample_model + if name in self: + del self[name] - def _create_and_add_sample_model( - self, - name: Optional[str] = None, - cif_path: Optional[str] = None, - cif_str: Optional[str] = None, - ) -> None: - """Create a SampleModel instance and add it to the collection. + # ------------ + # Show methods + # ------------ - Args: - name: Name for the new model. - cif_path: Path to a CIF file. - cif_str: CIF content as string. + def show_names(self) -> None: + """List all model names in the collection.""" + print(paragraph('Defined sample models' + ' 🧩')) + print(self.names) - Raises: - ValueError: If neither name, cif_path, nor cif_str is - provided. - """ - if cif_path: - model = SampleModel(cif_path=cif_path) - elif cif_str: - model = SampleModel(cif_str=cif_str) - elif name: - model = SampleModel(name=name) - else: - raise ValueError('You must provide a name, cif_path, or cif_str.') - - self._models[model.name] = model + def show_params(self) -> None: + """Show parameters of all sample models in the collection.""" + for model in self.values(): + model.show_params() diff --git a/src/easydiffraction/summary/__init__.py b/src/easydiffraction/summary/__init__.py new file mode 100644 index 00000000..3e95b5e9 --- /dev/null +++ b/src/easydiffraction/summary/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/summary.py b/src/easydiffraction/summary/summary.py similarity index 86% rename from src/easydiffraction/summary.py rename to src/easydiffraction/summary/summary.py index a9cce642..4482f818 100644 --- a/src/easydiffraction/summary.py +++ b/src/easydiffraction/summary/summary.py @@ -51,7 +51,7 @@ def show_crystallographic_data(self) -> None: """ print(section('Crystallographic data')) - for model in self.project.sample_models._models.values(): + for model in self.project.sample_models.values(): print(paragraph('Phase datablock')) print(f'🧩 {model.name}') @@ -61,8 +61,8 @@ def show_crystallographic_data(self) -> None: print(paragraph('Cell parameters')) columns_alignment: List[str] = ['left', 'right'] cell_data = [ - [k.replace('length_', '').replace('angle_', ''), f'{v:.5f}'] - for k, v in model.cell.as_dict().items() + [p.name.replace('length_', '').replace('angle_', ''), f'{p.value:.5f}'] + for p in model.cell.parameters ] render_table( columns_alignment=columns_alignment, @@ -71,13 +71,13 @@ def show_crystallographic_data(self) -> None: print(paragraph('Atom sites')) columns_headers = [ - 'Label', - 'Type', - 'fract_x', - 'fract_y', - 'fract_z', - 'Occupancy', - 'B_iso', + 'label', + 'type', + 'x', + 'y', + 'z', + 'occ', + 'Biso', ] columns_alignment = [ 'left', @@ -111,7 +111,7 @@ def show_experimental_data(self) -> None: """ print(section('Experiments')) - for expt in self.project.experiments._experiments.values(): + for expt in self.project.experiments.values(): print(paragraph('Experiment datablock')) print(f'🔬 {expt.name}') @@ -122,20 +122,20 @@ def show_experimental_data(self) -> None: f'{expt.type.beam_mode.value}' ) - if hasattr(expt, 'instrument'): - if hasattr(expt.instrument, 'setup_wavelength'): + if 'instrument' in expt._public_attrs(): + if 'setup_wavelength' in expt.instrument._public_attrs(): print(paragraph('Wavelength')) print(f'{expt.instrument.setup_wavelength.value:.5f}') - if hasattr(expt.instrument, 'calib_twotheta_offset'): + if 'calib_twotheta_offset' in expt.instrument._public_attrs(): print(paragraph('2θ offset')) print(f'{expt.instrument.calib_twotheta_offset.value:.5f}') - if hasattr(expt, 'peak_profile_type'): + if 'peak_profile_type' in expt._public_attrs(): print(paragraph('Profile type')) print(expt.peak_profile_type) - if hasattr(expt, 'peak'): - if hasattr(expt.peak, 'broad_gauss_u'): + if 'peak' in expt._public_attrs(): + if 'broad_gauss_u' in expt.peak._public_attrs(): print(paragraph('Peak broadening (Gaussian)')) columns_alignment = ['left', 'right'] columns_data = [ @@ -147,7 +147,7 @@ def show_experimental_data(self) -> None: columns_alignment=columns_alignment, columns_data=columns_data, ) - if hasattr(expt.peak, 'broad_lorentz_x'): + if 'broad_lorentz_x' in expt.peak._public_attrs(): print(paragraph('Peak broadening (Lorentzian)')) columns_alignment = ['left', 'right'] columns_data = [ @@ -192,4 +192,6 @@ def as_cif(self) -> str: """Export the final fitted data and analysis results as CIF format. """ - return 'To be added...' + from easydiffraction.io.cif.serialize import summary_to_cif + + return summary_to_cif(self) diff --git a/src/easydiffraction/utils/decorators.py b/src/easydiffraction/utils/decorators.py deleted file mode 100644 index 7bc23c2c..00000000 --- a/src/easydiffraction/utils/decorators.py +++ /dev/null @@ -1,59 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -import inspect -import typing - -import numpy as np - - -def enforce_type(func): - sig = inspect.signature(func) - - def wrapper(self, *args, **kwargs): - bound_args = sig.bind(self, *args, **kwargs) - bound_args.apply_defaults() - - for name, value in list(bound_args.arguments.items())[1:]: # skip 'self' - expected_type = sig.parameters[name].annotation - if expected_type is inspect._empty: - continue # no annotation, skip - - origin = typing.get_origin(expected_type) - if origin is not None: - args_types = typing.get_args(expected_type) - valid_types = tuple(t for t in args_types if isinstance(t, type)) - if not any(isinstance(value, t) for t in valid_types): - raise TypeError( - f"Parameter '{name}': expected {expected_type}, " - f'got {type(value).__name__}.' - ) - else: - if isinstance(expected_type, type): - if not isinstance(value, expected_type): - raise TypeError( - f"Parameter '{name}': expected {expected_type.__name__}, " - f'got {type(value).__name__}.' - ) - elif isinstance(expected_type, str) and expected_type == 'np.ndarray': - if not isinstance(value, np.ndarray): - raise TypeError( - f"Parameter '{name}': expected np.ndarray, got {type(value).__name__}." - ) - else: - if hasattr(expected_type, '__name__'): - if type(value).__name__ != expected_type.__name__: - raise TypeError( - f"Parameter '{name}': expected {expected_type}, " - f'got {type(value).__name__}.' - ) - else: - if type(value).__name__ != str(expected_type): - raise TypeError( - f"Parameter '{name}': expected {expected_type}, " - f'got {type(value).__name__}.' - ) - - return func(self, *args, **kwargs) - - return wrapper diff --git a/src/easydiffraction/utils/logging.py b/src/easydiffraction/utils/logging.py new file mode 100644 index 00000000..b059ae3d --- /dev/null +++ b/src/easydiffraction/utils/logging.py @@ -0,0 +1,310 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import logging +import os +import warnings +from contextlib import suppress +from enum import Enum +from enum import IntEnum +from enum import auto +from typing import TYPE_CHECKING + +if TYPE_CHECKING: # pragma: no cover + from types import TracebackType + +from rich.logging import RichHandler + + +class Logger: + """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.) + """ + + class Mode(Enum): + """Output modes (see :class:`Logger`).""" + + VERBOSE = 'verbose' # rich traceback panel + COMPACT = 'compact' # single line; no traceback + + class Level(IntEnum): + """Mirror stdlib logging levels.""" + + DEBUG = logging.DEBUG + INFO = logging.INFO + WARNING = logging.WARNING + ERROR = logging.ERROR + CRITICAL = logging.CRITICAL + + class Reaction(Enum): + """Reaction to errors (see :class:`Logger`).""" + + RAISE = auto() + WARN = auto() + + _logger = logging.getLogger('easydiffraction') + _configured = False + _mode: 'Logger.Mode' = Mode.VERBOSE + _reaction: 'Logger.Reaction' = Reaction.RAISE # TODO: not default? + + # ---------------- environment detection ---------------- + @staticmethod + def _in_jupyter() -> bool: # pragma: no cover - heuristic + try: + from IPython import get_ipython # type: ignore[import-not-found] + + return get_ipython() is not None + except Exception: # noqa: BLE001 + return False + + @staticmethod + def _in_pytest() -> bool: + import sys + + return 'pytest' in sys.modules + + # ---------------- configuration ---------------- + @classmethod + def configure( + cls, + *, + mode: 'Logger.Mode' | None = None, + level: 'Logger.Level' | None = None, + reaction: 'Logger.Reaction' | None = None, + rich_tracebacks: bool | None = None, + ) -> None: + """Configure logger. + + 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.) + """ + env_mode = os.getenv('ED_LOG_MODE') + env_level = os.getenv('ED_LOG_LEVEL') + env_reaction = os.getenv('ED_LOG_REACTION') + + if mode is None and env_mode is not None: + with suppress(ValueError): + mode = cls.Mode(env_mode.lower()) + + if level is None and env_level is not None: + with suppress(KeyError): + level = cls.Level[env_level.upper()] + + if reaction is None and env_reaction is not None: + with suppress(KeyError): + reaction = cls.Reaction[env_reaction.upper()] + + if mode is None: + # Default to VERBOSE even in Jupyter unless explicitly set + mode = cls.Mode.VERBOSE + if level is None: + level = cls.Level.INFO + if reaction is None: + reaction = cls.Reaction.RAISE + cls._mode = mode + cls._reaction = reaction + + if rich_tracebacks is None: + rich_tracebacks = mode == cls.Mode.VERBOSE + + log = cls._logger + log.handlers.clear() + log.propagate = False + log.setLevel(int(level)) + + from rich.console import Console + + # Enable rich tracebacks inside Jupyter environments + if cls._in_jupyter(): + from rich import traceback + + traceback.install( + show_locals=False, + suppress=['easydiffraction'], + # max_frames=10 if mode == cls.Mode.VERBOSE else 1, + # word_wrap=False, + # extra_lines=0, # no extra context lines + # locals_max_length=0, # no local vars shown + # locals_max_string=0, # no local string previews + ) + console = Console( + width=120, + # color_system="truecolor", + force_jupyter=False, + # force_terminal=False, + # force_interactive=True, + # legacy_windows=False, + # soft_wrap=True, + ) + handler = RichHandler( + rich_tracebacks=rich_tracebacks, + markup=True, + show_time=False, + show_path=False, + tracebacks_show_locals=False, + tracebacks_suppress=['easydiffraction'], + tracebacks_max_frames=10, + console=console, + ) + handler.setFormatter(logging.Formatter('%(message)s')) + log.addHandler(handler) + cls._configured = True + + import sys + + if rich_tracebacks and mode == cls.Mode.VERBOSE: + if not hasattr(cls, '_orig_excepthook'): + cls._orig_excepthook = sys.excepthook # type: ignore[attr-defined] + + def _aligned_excepthook( + exc_type: type[BaseException], + exc: BaseException, + tb: TracebackType | None, + ) -> None: + original_args = getattr(exc, 'args', tuple()) + message = str(exc) + with suppress(Exception): + exc.args = tuple() + try: + cls._logger.error(message, exc_info=(exc_type, exc, tb)) + except Exception: # pragma: no cover + cls._logger.error('Unhandled exception (logging failure)') + with suppress(Exception): + exc.args = original_args + + sys.excepthook = _aligned_excepthook # type: ignore[assignment] + elif mode == cls.Mode.COMPACT: + import sys + + if not hasattr(cls, '_orig_excepthook'): + cls._orig_excepthook = sys.excepthook # type: ignore[attr-defined] + + def _compact_excepthook( + _exc_type: type[BaseException], + exc: BaseException, + _tb: TracebackType | None, + ) -> None: + # Use Rich logger to keep formatting in terminal + cls._logger.error(str(exc)) + raise SystemExit(1) + + sys.excepthook = _compact_excepthook # type: ignore[assignment] + + # Disable Jupyter/IPython tracebacks properly + cls._install_jupyter_traceback_suppressor() + else: + if hasattr(cls, '_orig_excepthook'): + sys.excepthook = cls._orig_excepthook # type: ignore[attr-defined] + + @classmethod + def _install_jupyter_traceback_suppressor(cls) -> None: + """Install traceback suppressor in Jupyter, safely and lint- + clean. + """ + try: + from IPython import get_ipython + + ip = get_ipython() + if ip is not None: + + def _suppress_jupyter_traceback( + _shell, + _etype, + _evalue, + _tb, + _tb_offset=None, + ): + cls._logger.error(str(_evalue)) + return None + + ip.set_custom_exc((BaseException,), _suppress_jupyter_traceback) + except Exception as err: + msg = f'Failed to install Jupyter traceback suppressor: {err!r}' + cls._logger.debug(msg) + + # ---------------- helpers ---------------- + @classmethod + def set_mode(cls, mode: 'Logger.Mode') -> None: + cls.configure(mode=mode, level=cls.Level(cls._logger.level)) + + @classmethod + def set_level(cls, level: 'Logger.Level') -> None: + cls.configure(mode=cls._mode, level=level) + + @classmethod + def mode(cls) -> 'Logger.Mode': + return cls._mode + + @classmethod + def _lazy_config(cls) -> None: + if not cls._configured: # pragma: no cover - trivial + cls.configure() + + # ---------------- core routing ---------------- + @classmethod + def handle( + cls, + message: str, + *, + level: 'Logger.Level' = Level.ERROR, + exc_type: type[BaseException] | None = AttributeError, + ) -> None: + """Route a log message (see class docs for policy).""" + cls._lazy_config() + if exc_type is not None: + if exc_type is UserWarning: + if cls._in_pytest(): + # Always issue a real warning so pytest can catch it + warnings.warn(message, UserWarning, stacklevel=2) + else: + # Outside pytest → normal Rich logging + cls._logger.warning(message) + return + if cls._mode is cls.Mode.VERBOSE: + raise exc_type(message) + if cls._mode is cls.Mode.COMPACT: + raise exc_type(message) from None + cls._logger.log(int(level), message) + + # ---------------- convenience API ---------------- + @classmethod + def debug(cls, message: str) -> None: + cls.handle(message, level=cls.Level.DEBUG, exc_type=None) + + @classmethod + def info(cls, message: str) -> None: + cls.handle(message, level=cls.Level.INFO, exc_type=None) + + @classmethod + def warning(cls, message: str, exc_type: type[BaseException] | None = None) -> None: + cls.handle(message, level=cls.Level.WARNING, exc_type=exc_type) + + @classmethod + def error(cls, message: str, exc_type: type[BaseException] = AttributeError) -> None: + if cls._reaction is cls.Reaction.RAISE: + cls.handle(message, level=cls.Level.ERROR, exc_type=exc_type) + elif cls._reaction is cls.Reaction.WARN: + cls.handle(message, level=cls.Level.WARNING, exc_type=UserWarning) + + @classmethod + def critical(cls, message: str, exc_type: type[BaseException] = RuntimeError) -> None: + cls.handle(message, level=cls.Level.CRITICAL, exc_type=exc_type) + + @classmethod + def exception(cls, message: str) -> None: + """Log current exception from inside ``except`` block.""" + cls._lazy_config() + cls._logger.error(message, exc_info=True) + + +log = Logger # ergonomic alias diff --git a/src/easydiffraction/utils/utils.py b/src/easydiffraction/utils/utils.py index bf21835a..5e3b7de2 100644 --- a/src/easydiffraction/utils/utils.py +++ b/src/easydiffraction/utils/utils.py @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause -import importlib import io import json import os @@ -10,7 +9,9 @@ import zipfile from importlib.metadata import PackageNotFoundError from importlib.metadata import version +from importlib.util import find_spec from typing import List +from typing import Optional from urllib.parse import urlparse import numpy as np @@ -18,6 +19,9 @@ import pooch from packaging.version import Version from tabulate import tabulate +from uncertainties import UFloat +from uncertainties import ufloat +from uncertainties import ufloat_fromstr try: import IPython @@ -405,7 +409,7 @@ def is_colab() -> bool: bool: True if running in Google Colab PyCharm, False otherwise. """ try: - return importlib.util.find_spec('google.colab') is not None + return find_spec('google.colab') is not None except ModuleNotFoundError: return False @@ -689,3 +693,47 @@ def get_value_from_xye_header(file_path, key): return float(match.group(1)) else: raise ValueError(f'{key} not found in the header.') + + +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. + + Parameters + ---------- + s : str or None + 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. + + Returns: + ------- + UFloat + An `uncertainties.UFloat` object with the parsed value and + uncertainty. The uncertainty will be NaN if not specified or + parsing failed. + """ + if s is None: + return ufloat(default, np.nan) + + if '(' not in s and ')' not in s: + s = f'{s}(nan)' + try: + return ufloat_fromstr(s) + except Exception: + return ufloat(default, np.nan) diff --git a/tests/functional/fitting/test_pair-distribution-function.py b/tests/functional/fitting/test_pair-distribution-function.py index 70f03d7a..a1df4ff8 100644 --- a/tests/functional/fitting/test_pair-distribution-function.py +++ b/tests/functional/fitting/test_pair-distribution-function.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + import os import tempfile @@ -13,16 +16,28 @@ def test_single_fit_pdf_xray_pd_cw_nacl() -> None: project = ed.Project() # Set sample model - project.sample_models.add(name='nacl') + project.sample_models.add_minimal(name='nacl') sample_model = project.sample_models['nacl'] sample_model.space_group.name_h_m = 'F m -3 m' sample_model.space_group.it_coordinate_system_code = '1' sample_model.cell.length_a = 5.6018 - sample_model.atom_sites.add( - label='Na', type_symbol='Na', fract_x=0, fract_y=0, fract_z=0, wyckoff_letter='a', b_iso=1.1053 + sample_model.atom_sites.add_from_args( + label='Na', + type_symbol='Na', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=1.1053, ) - sample_model.atom_sites.add( - label='Cl', type_symbol='Cl', fract_x=0.5, fract_y=0.5, fract_z=0.5, wyckoff_letter='b', b_iso=0.5708 + sample_model.atom_sites.add_from_args( + label='Cl', + type_symbol='Cl', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.5708, ) # Set experiment @@ -44,7 +59,7 @@ def test_single_fit_pdf_xray_pd_cw_nacl() -> None: experiment.peak.sharp_delta_1 = 0 experiment.peak.sharp_delta_2 = 3.5041 experiment.peak.damp_particle_diameter = 0 - experiment.linked_phases.add(id='nacl', scale=0.4254) + experiment.linked_phases.add_from_args(id='nacl', scale=0.4254) # Select fitting parameters sample_model.cell.length_a.free = True @@ -59,7 +74,8 @@ def test_single_fit_pdf_xray_pd_cw_nacl() -> None: project.analysis.fit() # Compare fit quality - assert_almost_equal(project.analysis.fit_results.reduced_chi_square, desired=1.48, decimal=2) + chi2 = project.analysis.fit_results.reduced_chi_square + assert_almost_equal(chi2, desired=1.48, decimal=2) @pytest.mark.fast @@ -67,13 +83,19 @@ def test_single_fit_pdf_neutron_pd_cw_ni(): project = ed.Project() # Set sample model - project.sample_models.add(name='ni') + project.sample_models.add_minimal(name='ni') sample_model = project.sample_models['ni'] sample_model.space_group.name_h_m.value = 'F m -3 m' sample_model.space_group.it_coordinate_system_code = '1' sample_model.cell.length_a = 3.526 - sample_model.atom_sites.add( - label='Ni', type_symbol='Ni', fract_x=0, fract_y=0, fract_z=0, wyckoff_letter='a', b_iso=0.4281 + sample_model.atom_sites.add_from_args( + label='Ni', + type_symbol='Ni', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.4281, ) # Set experiment @@ -94,7 +116,7 @@ def test_single_fit_pdf_neutron_pd_cw_ni(): experiment.peak.sharp_delta_1 = 0 experiment.peak.sharp_delta_2 = 2.5587 experiment.peak.damp_particle_diameter = 0 - experiment.linked_phases.add(id='ni', scale=0.9892) + experiment.linked_phases.add_from_args(id='ni', scale=0.9892) # Select fitting parameters sample_model.cell.length_a.free = True @@ -108,19 +130,28 @@ def test_single_fit_pdf_neutron_pd_cw_ni(): project.analysis.fit() # Compare fit quality - assert_almost_equal(project.analysis.fit_results.reduced_chi_square, desired=207.1, decimal=1) + chi2 = project.analysis.fit_results.reduced_chi_square + assert_almost_equal(chi2, desired=207.1, decimal=1) def test_single_fit_pdf_neutron_pd_tof_si(): project = ed.Project() # Set sample model - project.sample_models.add(name='si') + project.sample_models.add_minimal(name='si') sample_model = project.sample_models['si'] sample_model.space_group.name_h_m.value = 'F d -3 m' sample_model.space_group.it_coordinate_system_code = '1' sample_model.cell.length_a = 5.4306 - sample_model.atom_sites.add(label='Si', type_symbol='Si', fract_x=0, fract_y=0, fract_z=0, wyckoff_letter='a', b_iso=0.717) + sample_model.atom_sites.add_from_args( + label='Si', + type_symbol='Si', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.717, + ) # Set experiment data_file = 'NOM_9999_Si_640g_PAC_50_ff_ftfrgr_up-to-50.gr' @@ -140,7 +171,7 @@ def test_single_fit_pdf_neutron_pd_tof_si(): experiment.peak.sharp_delta_1 = 2.54 experiment.peak.sharp_delta_2 = -1.7525 experiment.peak.damp_particle_diameter = 0 - experiment.linked_phases.add(id='si', scale=1.2728) + experiment.linked_phases.add_from_args(id='si', scale=1.2728) # Select fitting parameters project.sample_models['si'].cell.length_a.free = True @@ -156,7 +187,8 @@ def test_single_fit_pdf_neutron_pd_tof_si(): project.analysis.fit() # Compare fit quality - assert_almost_equal(project.analysis.fit_results.reduced_chi_square, desired=170.54, decimal=1) + chi2 = project.analysis.fit_results.reduced_chi_square + assert_almost_equal(chi2, desired=170.54, decimal=1) if __name__ == '__main__': diff --git a/tests/functional/fitting/test_powder-diffraction_constant-wavelength.py b/tests/functional/fitting/test_powder-diffraction_constant-wavelength.py index 98c0a5fc..81ba8ef2 100644 --- a/tests/functional/fitting/test_powder-diffraction_constant-wavelength.py +++ b/tests/functional/fitting/test_powder-diffraction_constant-wavelength.py @@ -1,12 +1,15 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + import os import tempfile import pytest from numpy.testing import assert_almost_equal -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository TEMP_DIR = tempfile.gettempdir() @@ -14,18 +17,52 @@ def test_single_fit_neutron_pd_cwl_lbco() -> None: # Set sample model - model = SampleModel('lbco') + model = SampleModelFactory.create(name='lbco') model.space_group.name_h_m = 'P m -3 m' model.cell.length_a = 3.88 - model.atom_sites.add('La', 'La', 0, 0, 0, occupancy=0.5, b_iso=0.1) - model.atom_sites.add('Ba', 'Ba', 0, 0, 0, occupancy=0.5, b_iso=0.1) - model.atom_sites.add('Co', 'Co', 0.5, 0.5, 0.5, b_iso=0.1) - model.atom_sites.add('O', 'O', 0, 0.5, 0.5, b_iso=0.1) + model.atom_sites.add_from_args( + label='La', + type_symbol='La', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + occupancy=0.5, + b_iso=0.1, + ) + model.atom_sites.add_from_args( + label='Ba', + type_symbol='Ba', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + occupancy=0.5, + b_iso=0.1, + ) + model.atom_sites.add_from_args( + label='Co', + type_symbol='Co', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.1, + ) + model.atom_sites.add_from_args( + label='O', + type_symbol='O', + fract_x=0, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='c', + b_iso=0.1, + ) # Set experiment data_file = 'hrpt_lbco.xye' download_from_repository(data_file, destination=TEMP_DIR) - expt = Experiment(name='hrpt', data_path=os.path.join(TEMP_DIR, data_file)) + expt = ExperimentFactory.create(name='hrpt', data_path=os.path.join(TEMP_DIR, data_file)) expt.instrument.setup_wavelength = 1.494 expt.instrument.calib_twotheta_offset = 0 expt.peak.broad_gauss_u = 0.1 @@ -33,9 +70,9 @@ def test_single_fit_neutron_pd_cwl_lbco() -> None: expt.peak.broad_gauss_w = 0.2 expt.peak.broad_lorentz_x = 0 expt.peak.broad_lorentz_y = 0 - expt.linked_phases.add('lbco', scale=5.0) - expt.background.add(x=10, y=170) - expt.background.add(x=165, y=170) + expt.linked_phases.add_from_args(id='lbco', scale=5.0) + expt.background.add_from_args(x=10, y=170) + expt.background.add_from_args(x=165, y=170) # Create project project = Project() @@ -93,7 +130,7 @@ def test_single_fit_neutron_pd_cwl_lbco() -> None: @pytest.mark.fast def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None: # Set sample model - model = SampleModel('lbco') + model = SampleModelFactory.create(name='lbco') space_group = model.space_group space_group.name_h_m = 'P m -3 m' @@ -102,16 +139,50 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None: cell.length_a = 3.8909 atom_sites = model.atom_sites - atom_sites.add('La', 'La', 0, 0, 0, b_iso=1.0, occupancy=0.5) - atom_sites.add('Ba', 'Ba', 0, 0, 0, b_iso=1.0, occupancy=0.5) - atom_sites.add('Co', 'Co', 0.5, 0.5, 0.5, b_iso=1.0) - atom_sites.add('O', 'O', 0, 0.5, 0.5, b_iso=1.0) + atom_sites.add_from_args( + label='La', + type_symbol='La', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=1.0, + occupancy=0.5, + ) + atom_sites.add_from_args( + label='Ba', + type_symbol='Ba', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=1.0, + occupancy=0.5, + ) + atom_sites.add_from_args( + label='Co', + type_symbol='Co', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=1.0, + ) + atom_sites.add_from_args( + label='O', + type_symbol='O', + fract_x=0, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='c', + b_iso=1.0, + ) # Set experiment data_file = 'hrpt_lbco.xye' download_from_repository(data_file, destination=TEMP_DIR) - expt = Experiment(name='hrpt', data_path=os.path.join(TEMP_DIR, data_file)) + expt = ExperimentFactory.create(name='hrpt', data_path=os.path.join(TEMP_DIR, data_file)) instrument = expt.instrument instrument.setup_wavelength = 1.494 @@ -125,18 +196,18 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None: peak.broad_lorentz_y = 0.0797 background = expt.background - background.add(x=10, y=174.3) - background.add(x=20, y=159.8) - background.add(x=30, y=167.9) - background.add(x=50, y=166.1) - background.add(x=70, y=172.3) - background.add(x=90, y=171.1) - background.add(x=110, y=172.4) - background.add(x=130, y=182.5) - background.add(x=150, y=173.0) - background.add(x=165, y=171.1) - - expt.linked_phases.add('lbco', scale=9.0976) + background.add_from_args(x=10, y=174.3) + background.add_from_args(x=20, y=159.8) + background.add_from_args(x=30, y=167.9) + background.add_from_args(x=50, y=166.1) + background.add_from_args(x=70, y=172.3) + background.add_from_args(x=90, y=171.1) + background.add_from_args(x=110, y=172.4) + background.add_from_args(x=130, y=182.5) + background.add_from_args(x=150, y=173.0) + background.add_from_args(x=165, y=171.1) + + expt.linked_phases.add_from_args(id='lbco', scale=9.0976) # Create project project = Project() @@ -182,14 +253,18 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None: # ------------ 2nd fitting ------------ # Set aliases for parameters - project.analysis.aliases.add(label='biso_La', param_uid=atom_sites['La'].b_iso.uid) - project.analysis.aliases.add(label='biso_Ba', param_uid=atom_sites['Ba'].b_iso.uid) - project.analysis.aliases.add(label='occ_La', param_uid=atom_sites['La'].occupancy.uid) - project.analysis.aliases.add(label='occ_Ba', param_uid=atom_sites['Ba'].occupancy.uid) + project.analysis.aliases.add_from_args(label='biso_La', param_uid=atom_sites['La'].b_iso.uid) + project.analysis.aliases.add_from_args(label='biso_Ba', param_uid=atom_sites['Ba'].b_iso.uid) + project.analysis.aliases.add_from_args( + label='occ_La', param_uid=atom_sites['La'].occupancy.uid + ) + project.analysis.aliases.add_from_args( + label='occ_Ba', param_uid=atom_sites['Ba'].occupancy.uid + ) # Set constraints - project.analysis.constraints.add(lhs_alias='biso_Ba', rhs_expr='biso_La') - project.analysis.constraints.add(lhs_alias='occ_Ba', rhs_expr='1 - occ_La') + project.analysis.constraints.add_from_args(lhs_alias='biso_Ba', rhs_expr='biso_La') + project.analysis.constraints.add_from_args(lhs_alias='occ_Ba', rhs_expr='1 - occ_La') # Apply constraints project.analysis.apply_constraints() @@ -211,22 +286,62 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None: def test_fit_neutron_pd_cwl_hs() -> None: # Set sample model - model = SampleModel('hs') + model = SampleModelFactory.create(name='hs') model.space_group.name_h_m = 'R -3 m' model.space_group.it_coordinate_system_code = 'h' model.cell.length_a = 6.8615 model.cell.length_c = 14.136 - model.atom_sites.add('Zn', 'Zn', 0, 0, 0.5, wyckoff_letter='b', b_iso=0.1) - model.atom_sites.add('Cu', 'Cu', 0.5, 0, 0, wyckoff_letter='e', b_iso=1.2) - model.atom_sites.add('O', 'O', 0.206, -0.206, 0.061, wyckoff_letter='h', b_iso=0.7) - model.atom_sites.add('Cl', 'Cl', 0, 0, 0.197, wyckoff_letter='c', b_iso=1.1) - model.atom_sites.add('H', '2H', 0.132, -0.132, 0.09, wyckoff_letter='h', b_iso=2.3) + model.atom_sites.add_from_args( + label='Zn', + type_symbol='Zn', + fract_x=0, + fract_y=0, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.1, + ) + model.atom_sites.add_from_args( + label='Cu', + type_symbol='Cu', + fract_x=0.5, + fract_y=0, + fract_z=0, + wyckoff_letter='e', + b_iso=1.2, + ) + model.atom_sites.add_from_args( + label='O', + type_symbol='O', + fract_x=0.206, + fract_y=-0.206, + fract_z=0.061, + wyckoff_letter='h', + b_iso=0.7, + ) + model.atom_sites.add_from_args( + label='Cl', + type_symbol='Cl', + fract_x=0, + fract_y=0, + fract_z=0.197, + wyckoff_letter='c', + b_iso=1.1, + ) + model.atom_sites.add_from_args( + label='H', + type_symbol='2H', + fract_x=0.132, + fract_y=-0.132, + fract_z=0.09, + wyckoff_letter='h', + b_iso=2.3, + ) model.apply_symmetry_constraints() # Set experiment data_file = 'hrpt_hs.xye' download_from_repository(data_file, destination=TEMP_DIR) - expt = Experiment(name='hrpt', data_path=os.path.join(TEMP_DIR, data_file)) + expt = ExperimentFactory.create(name='hrpt', data_path=os.path.join(TEMP_DIR, data_file)) expt.instrument.setup_wavelength = 1.89 expt.instrument.calib_twotheta_offset = 0.0 expt.peak.broad_gauss_u = 0.1579 @@ -234,16 +349,16 @@ def test_fit_neutron_pd_cwl_hs() -> None: expt.peak.broad_gauss_w = 0.3498 expt.peak.broad_lorentz_x = 0.2927 expt.peak.broad_lorentz_y = 0 - expt.background.add(x=4.4196, y=648.413) - expt.background.add(x=6.6207, y=523.788) - expt.background.add(x=10.4918, y=454.938) - expt.background.add(x=15.4634, y=435.913) - expt.background.add(x=45.6041, y=472.972) - expt.background.add(x=74.6844, y=486.606) - expt.background.add(x=103.4187, y=472.409) - expt.background.add(x=121.6311, y=496.734) - expt.background.add(x=159.4116, y=473.146) - expt.linked_phases.add('hs', scale=0.492) + expt.background.add_from_args(x=4.4196, y=648.413) + expt.background.add_from_args(x=6.6207, y=523.788) + expt.background.add_from_args(x=10.4918, y=454.938) + expt.background.add_from_args(x=15.4634, y=435.913) + expt.background.add_from_args(x=45.6041, y=472.972) + expt.background.add_from_args(x=74.6844, y=486.606) + expt.background.add_from_args(x=103.4187, y=472.409) + expt.background.add_from_args(x=121.6311, y=496.734) + expt.background.add_from_args(x=159.4116, y=473.146) + expt.linked_phases.add_from_args(id='hs', scale=0.492) # Create project project = Project() diff --git a/tests/functional/fitting/test_powder-diffraction_joint-fit.py b/tests/functional/fitting/test_powder-diffraction_joint-fit.py index a9b505a6..2cfb97f7 100644 --- a/tests/functional/fitting/test_powder-diffraction_joint-fit.py +++ b/tests/functional/fitting/test_powder-diffraction_joint-fit.py @@ -1,12 +1,15 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + import os import tempfile import pytest from numpy.testing import assert_almost_equal -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository TEMP_DIR = tempfile.gettempdir() @@ -15,21 +18,61 @@ @pytest.mark.fast def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None: # Set sample model - model = SampleModel('pbso4') - model.space_group.name_h_m.value = 'P n m a' - model.cell.length_a.value = 8.47 - model.cell.length_b.value = 5.39 - model.cell.length_c.value = 6.95 - model.atom_sites.add('Pb', 'Pb', 0.1876, 0.25, 0.167, b_iso=1.37) - model.atom_sites.add('S', 'S', 0.0654, 0.25, 0.684, b_iso=0.3777) - model.atom_sites.add('O1', 'O', 0.9082, 0.25, 0.5954, b_iso=1.9764) - model.atom_sites.add('O2', 'O', 0.1935, 0.25, 0.5432, b_iso=1.4456) - model.atom_sites.add('O3', 'O', 0.0811, 0.0272, 0.8086, b_iso=1.2822) + model = SampleModelFactory.create(name='pbso4') + model.space_group.name_h_m = 'P n m a' + model.cell.length_a = 8.47 + model.cell.length_b = 5.39 + model.cell.length_c = 6.95 + model.atom_sites.add_from_args( + label='Pb', + type_symbol='Pb', + fract_x=0.1876, + fract_y=0.25, + fract_z=0.167, + wyckoff_letter='c', + b_iso=1.37, + ) + model.atom_sites.add_from_args( + label='S', + type_symbol='S', + fract_x=0.0654, + fract_y=0.25, + fract_z=0.684, + wyckoff_letter='c', + b_iso=0.3777, + ) + model.atom_sites.add_from_args( + label='O1', + type_symbol='O', + fract_x=0.9082, + fract_y=0.25, + fract_z=0.5954, + wyckoff_letter='c', + b_iso=1.9764, + ) + model.atom_sites.add_from_args( + label='O2', + type_symbol='O', + fract_x=0.1935, + fract_y=0.25, + fract_z=0.5432, + wyckoff_letter='c', + b_iso=1.4456, + ) + model.atom_sites.add_from_args( + label='O3', + type_symbol='O', + fract_x=0.0811, + fract_y=0.0272, + fract_z=0.8086, + wyckoff_letter='d', + b_iso=1.2822, + ) # Set experiments data_file = 'd1a_pbso4_first-half.dat' download_from_repository(data_file, destination=TEMP_DIR) - expt1 = Experiment(name='npd1', data_path=os.path.join(TEMP_DIR, data_file)) + expt1 = ExperimentFactory.create(name='npd1', data_path=os.path.join(TEMP_DIR, data_file)) expt1.instrument.setup_wavelength = 1.91 expt1.instrument.calib_twotheta_offset = -0.1406 expt1.peak.broad_gauss_u = 0.139 @@ -37,7 +80,7 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None: expt1.peak.broad_gauss_w = 0.386 expt1.peak.broad_lorentz_x = 0 expt1.peak.broad_lorentz_y = 0.0878 - expt1.linked_phases.add('pbso4', scale=1.46) + expt1.linked_phases.add_from_args(id='pbso4', scale=1.46) expt1.background_type = 'line-segment' for x, y in [ (11.0, 206.1624), @@ -49,11 +92,11 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None: (120.0, 244.4525), (153.0, 226.0595), ]: - expt1.background.add(x, y) + expt1.background.add_from_args(x=x, y=y) data_file = 'd1a_pbso4_second-half.dat' download_from_repository(data_file, destination=TEMP_DIR) - expt2 = Experiment(name='npd2', data_path=os.path.join(TEMP_DIR, data_file)) + expt2 = ExperimentFactory.create(name='npd2', data_path=os.path.join(TEMP_DIR, data_file)) expt2.instrument.setup_wavelength = 1.91 expt2.instrument.calib_twotheta_offset = -0.1406 expt2.peak.broad_gauss_u = 0.139 @@ -61,7 +104,7 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None: expt2.peak.broad_gauss_w = 0.386 expt2.peak.broad_lorentz_x = 0 expt2.peak.broad_lorentz_y = 0.0878 - expt2.linked_phases.add('pbso4', scale=1.46) + expt2.linked_phases.add_from_args(id='pbso4', scale=1.46) expt2.background_type = 'line-segment' for x, y in [ (11.0, 206.1624), @@ -73,7 +116,7 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None: (120.0, 244.4525), (153.0, 226.0595), ]: - expt2.background.add(x, y) + expt2.background.add_from_args(x=x, y=y) # Create project project = Project() @@ -101,21 +144,61 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None: @pytest.mark.fast def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None: # Set sample model - model = SampleModel('pbso4') + model = SampleModelFactory.create(name='pbso4') model.space_group.name_h_m = 'P n m a' model.cell.length_a = 8.47 model.cell.length_b = 5.39 model.cell.length_c = 6.95 - model.atom_sites.add('Pb', 'Pb', 0.1876, 0.25, 0.167, b_iso=1.37) - model.atom_sites.add('S', 'S', 0.0654, 0.25, 0.684, b_iso=0.3777) - model.atom_sites.add('O1', 'O', 0.9082, 0.25, 0.5954, b_iso=1.9764) - model.atom_sites.add('O2', 'O', 0.1935, 0.25, 0.5432, b_iso=1.4456) - model.atom_sites.add('O3', 'O', 0.0811, 0.0272, 0.8086, b_iso=1.2822) + model.atom_sites.add_from_args( + label='Pb', + type_symbol='Pb', + fract_x=0.1876, + fract_y=0.25, + fract_z=0.167, + wyckoff_letter='c', + b_iso=1.37, + ) + model.atom_sites.add_from_args( + label='S', + type_symbol='S', + fract_x=0.0654, + fract_y=0.25, + fract_z=0.684, + wyckoff_letter='c', + b_iso=0.3777, + ) + model.atom_sites.add_from_args( + label='O1', + type_symbol='O', + fract_x=0.9082, + fract_y=0.25, + fract_z=0.5954, + wyckoff_letter='c', + b_iso=1.9764, + ) + model.atom_sites.add_from_args( + label='O2', + type_symbol='O', + fract_x=0.1935, + fract_y=0.25, + fract_z=0.5432, + wyckoff_letter='c', + b_iso=1.4456, + ) + model.atom_sites.add_from_args( + label='O3', + type_symbol='O', + fract_x=0.0811, + fract_y=0.0272, + fract_z=0.8086, + wyckoff_letter='d', + b_iso=1.2822, + ) # Set experiments data_file = 'd1a_pbso4.dat' download_from_repository(data_file, destination=TEMP_DIR) - expt1 = Experiment( + expt1 = ExperimentFactory.create( name='npd', data_path=os.path.join(TEMP_DIR, data_file), radiation_probe='neutron', @@ -127,7 +210,7 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None: expt1.peak.broad_gauss_w = 0.386 expt1.peak.broad_lorentz_x = 0 expt1.peak.broad_lorentz_y = 0.088 - expt1.linked_phases.add('pbso4', scale=1.5) + expt1.linked_phases.add_from_args(id='pbso4', scale=1.5) for x, y in [ (11.0, 206.1624), (15.0, 194.75), @@ -138,11 +221,11 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None: (120.0, 244.4525), (153.0, 226.0595), ]: - expt1.background.add(x, y) + expt1.background.add_from_args(x=x, y=y) data_file = 'lab_pbso4.dat' download_from_repository(data_file, destination=TEMP_DIR) - expt2 = Experiment( + expt2 = ExperimentFactory.create( name='xrd', data_path=os.path.join(TEMP_DIR, data_file), radiation_probe='xray', @@ -154,7 +237,7 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None: expt2.peak.broad_gauss_w = 0.021272 expt2.peak.broad_lorentz_x = 0 expt2.peak.broad_lorentz_y = 0.057691 - expt2.linked_phases.add('pbso4', scale=0.001) + expt2.linked_phases.add_from_args(id='pbso4', scale=0.001) for x, y in [ (11.0, 141.8516), (13.0, 102.8838), @@ -165,7 +248,7 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None: (90.0, 113.7473), (110.0, 132.4643), ]: - expt2.background.add(x, y) + expt2.background.add_from_args(x=x, y=y) # Create project project = Project() diff --git a/tests/functional/fitting/test_powder-diffraction_multiphase.py b/tests/functional/fitting/test_powder-diffraction_multiphase.py index c75bc110..c3595cba 100644 --- a/tests/functional/fitting/test_powder-diffraction_multiphase.py +++ b/tests/functional/fitting/test_powder-diffraction_multiphase.py @@ -1,11 +1,14 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + import os import tempfile from numpy.testing import assert_almost_equal -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository TEMP_DIR = tempfile.gettempdir() @@ -13,25 +16,67 @@ def test_single_fit_neutron_pd_tof_mcstas_lbco_si() -> None: # Set sample models - model_1 = SampleModel('lbco') + model_1 = SampleModelFactory.create(name='lbco') model_1.space_group.name_h_m = 'P m -3 m' model_1.space_group.it_coordinate_system_code = '1' model_1.cell.length_a = 3.8909 - model_1.atom_sites.add('La', 'La', 0, 0, 0, wyckoff_letter='a', b_iso=0.2, occupancy=0.5) - model_1.atom_sites.add('Ba', 'Ba', 0, 0, 0, wyckoff_letter='a', b_iso=0.2, occupancy=0.5) - model_1.atom_sites.add('Co', 'Co', 0.5, 0.5, 0.5, wyckoff_letter='b', b_iso=0.2567) - model_1.atom_sites.add('O', 'O', 0, 0.5, 0.5, wyckoff_letter='c', b_iso=1.4041) + model_1.atom_sites.add_from_args( + label='La', + type_symbol='La', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.2, + occupancy=0.5, + ) + model_1.atom_sites.add_from_args( + label='Ba', + type_symbol='Ba', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.2, + occupancy=0.5, + ) + model_1.atom_sites.add_from_args( + label='Co', + type_symbol='Co', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.2567, + ) + model_1.atom_sites.add_from_args( + label='O', + type_symbol='O', + fract_x=0, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='c', + b_iso=1.4041, + ) - model_2 = SampleModel('si') + model_2 = SampleModelFactory.create(name='si') model_2.space_group.name_h_m = 'F d -3 m' model_2.space_group.it_coordinate_system_code = '2' model_2.cell.length_a = 5.43146 - model_2.atom_sites.add('Si', 'Si', 0.0, 0.0, 0.0, wyckoff_letter='a', b_iso=0.0) + model_2.atom_sites.add_from_args( + label='Si', + type_symbol='Si', + fract_x=0.0, + fract_y=0.0, + fract_z=0.0, + wyckoff_letter='a', + b_iso=0.0, + ) # Set experiment data_file = 'mcstas_lbco-si.xys' download_from_repository(data_file, destination=TEMP_DIR) - expt = Experiment( + expt = ExperimentFactory.create( name='mcstas', data_path=os.path.join(TEMP_DIR, data_file), beam_mode='time-of-flight', @@ -48,10 +93,10 @@ def test_single_fit_neutron_pd_tof_mcstas_lbco_si() -> None: expt.peak.broad_mix_beta_1 = 0.0041 expt.peak.asym_alpha_0 = 0.0 expt.peak.asym_alpha_1 = 0.0097 - expt.linked_phases.add('lbco', scale=4.0) - expt.linked_phases.add('si', scale=0.2) + expt.linked_phases.add_from_args(id='lbco', scale=4.0) + expt.linked_phases.add_from_args(id='si', scale=0.2) for x in range(45000, 115000, 5000): - expt.background.add(x=x, y=0.2) + expt.background.add_from_args(x=x, y=0.2) # Create project project = Project() @@ -60,7 +105,7 @@ def test_single_fit_neutron_pd_tof_mcstas_lbco_si() -> None: project.experiments.add(expt) # Exclude regions from fitting - project.experiments['mcstas'].excluded_regions.add(start=108000, end=200000) + project.experiments['mcstas'].excluded_regions.add_from_args(start=108000, end=200000) # Prepare for fitting project.analysis.current_calculator = 'cryspy' diff --git a/tests/functional/fitting/test_powder-diffraction_time-of-flight.py b/tests/functional/fitting/test_powder-diffraction_time-of-flight.py index f77fba0a..b40c4369 100644 --- a/tests/functional/fitting/test_powder-diffraction_time-of-flight.py +++ b/tests/functional/fitting/test_powder-diffraction_time-of-flight.py @@ -1,11 +1,14 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + import os import tempfile from numpy.testing import assert_almost_equal -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository TEMP_DIR = tempfile.gettempdir() @@ -13,16 +16,24 @@ def test_single_fit_neutron_pd_tof_si() -> None: # Set sample model - model = SampleModel('si') + model = SampleModelFactory.create(name='si') model.space_group.name_h_m = 'F d -3 m' model.space_group.it_coordinate_system_code = '2' model.cell.length_a = 5.4315 - model.atom_sites.add('Si', 'Si', 0.125, 0.125, 0.125, b_iso=0.529) + model.atom_sites.add_from_args( + label='Si', + type_symbol='Si', + fract_x=0.125, + fract_y=0.125, + fract_z=0.125, + wyckoff_letter='a', + b_iso=0.529, + ) # Set experiment data_file = 'sepd_si.xye' download_from_repository(data_file, destination=TEMP_DIR) - expt = Experiment( + expt = ExperimentFactory.create( name='sepd', data_path=os.path.join(TEMP_DIR, data_file), beam_mode='time-of-flight', @@ -39,9 +50,9 @@ def test_single_fit_neutron_pd_tof_si() -> None: expt.peak.broad_mix_beta_1 = 0.00946 expt.peak.asym_alpha_0 = 0.0 expt.peak.asym_alpha_1 = 0.5971 - expt.linked_phases.add('si', scale=14.92) + expt.linked_phases.add_from_args(id='si', scale=14.92) for x in range(0, 35000, 5000): - expt.background.add(x=x, y=200) + expt.background.add_from_args(x=x, y=200) expt.show_as_cif() # Create project @@ -70,21 +81,69 @@ def test_single_fit_neutron_pd_tof_si() -> None: def test_single_fit_neutron_pd_tof_ncaf() -> None: # Set sample model - model = SampleModel('ncaf') + model = SampleModelFactory.create(name='ncaf') model.space_group.name_h_m = 'I 21 3' model.space_group.it_coordinate_system_code = '1' model.cell.length_a = 10.250256 - model.atom_sites.add('Ca', 'Ca', 0.4661, 0.0, 0.25, wyckoff_letter='b', b_iso=0.9) - model.atom_sites.add('Al', 'Al', 0.25171, 0.25171, 0.25171, wyckoff_letter='a', b_iso=0.66) - model.atom_sites.add('Na', 'Na', 0.08481, 0.08481, 0.08481, wyckoff_letter='a', b_iso=1.9) - model.atom_sites.add('F1', 'F', 0.1375, 0.3053, 0.1195, wyckoff_letter='c', b_iso=0.9) - model.atom_sites.add('F2', 'F', 0.3626, 0.3634, 0.1867, wyckoff_letter='c', b_iso=1.28) - model.atom_sites.add('F3', 'F', 0.4612, 0.4612, 0.4612, wyckoff_letter='a', b_iso=0.79) + model.atom_sites.add_from_args( + label='Ca', + type_symbol='Ca', + fract_x=0.4661, + fract_y=0.0, + fract_z=0.25, + wyckoff_letter='b', + b_iso=0.9, + ) + model.atom_sites.add_from_args( + label='Al', + type_symbol='Al', + fract_x=0.25171, + fract_y=0.25171, + fract_z=0.25171, + wyckoff_letter='a', + b_iso=0.66, + ) + model.atom_sites.add_from_args( + label='Na', + type_symbol='Na', + fract_x=0.08481, + fract_y=0.08481, + fract_z=0.08481, + wyckoff_letter='a', + b_iso=1.9, + ) + model.atom_sites.add_from_args( + label='F1', + type_symbol='F', + fract_x=0.1375, + fract_y=0.3053, + fract_z=0.1195, + wyckoff_letter='c', + b_iso=0.9, + ) + model.atom_sites.add_from_args( + label='F2', + type_symbol='F', + fract_x=0.3626, + fract_y=0.3634, + fract_z=0.1867, + wyckoff_letter='c', + b_iso=1.28, + ) + model.atom_sites.add_from_args( + label='F3', + type_symbol='F', + fract_x=0.4612, + fract_y=0.4612, + fract_z=0.4612, + wyckoff_letter='a', + b_iso=0.79, + ) # Set experiment data_file = 'wish_ncaf.xye' download_from_repository(data_file, destination=TEMP_DIR) - expt = Experiment( + expt = ExperimentFactory.create( name='wish', data_path=os.path.join(TEMP_DIR, data_file), beam_mode='time-of-flight', @@ -101,7 +160,7 @@ def test_single_fit_neutron_pd_tof_ncaf() -> None: expt.peak.broad_mix_beta_1 = 0.0099 expt.peak.asym_alpha_0 = -0.009 expt.peak.asym_alpha_1 = 0.1085 - expt.linked_phases.add('ncaf', scale=1.0928) + expt.linked_phases.add_from_args(id='ncaf', scale=1.0928) for x, y in [ (9162, 465), (11136, 593), @@ -132,7 +191,7 @@ def test_single_fit_neutron_pd_tof_ncaf() -> None: (91958, 268), (102712, 262), ]: - expt.background.add(x, y) + expt.background.add_from_args(x=x, y=y) # Create project project = Project() diff --git a/tests/unit/analysis/calculators/test_calculator_base.py b/tests/unit/analysis/calculators/test_calculator_base.py deleted file mode 100644 index 7c9df501..00000000 --- a/tests/unit/analysis/calculators/test_calculator_base.py +++ /dev/null @@ -1,83 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import numpy as np -import pytest - -from easydiffraction.analysis.calculators.calculator_base import CalculatorBase - - -# Mock subclass of CalculatorBase to test its concrete methods -class MockCalculator(CalculatorBase): - @property - def name(self): - return 'MockCalculator' - - @property - def engine_imported(self): - return True - - def calculate_structure_factors(self, sample_model, experiment): - return np.array([1.0, 2.0, 3.0]) - - def _calculate_single_model_pattern(self, sample_model, experiment, called_by_minimizer): - return np.array([1.0, 2.0, 3.0]) - - -@pytest.fixture -def mock_sample_models(): - sample_models = MagicMock() - sample_models.get_all_params.return_value = {'param1': 1, 'param2': 2} - sample_models.get_ids.return_value = ['phase1', 'phase2'] - sample_models.__getitem__.side_effect = lambda key: MagicMock(apply_symmetry_constraints=MagicMock()) - return sample_models - - -@pytest.fixture -def mock_experiment(): - experiment = MagicMock() - experiment.datastore.x = np.array([1.0, 2.0, 3.0]) - experiment.datastore.bkg = None - experiment.datastore.calc = None - experiment.linked_phases = [ - MagicMock(_entry_id='phase1', scale=MagicMock(value=2.0)), - MagicMock(_entry_id='phase2', scale=MagicMock(value=1.5)), - ] - experiment.background.calculate.return_value = np.array([0.1, 0.2, 0.3]) - return experiment - - -@patch('easydiffraction.core.singletons.ConstraintsHandler.get') -def test_calculate_pattern(mock_constraints_handler, mock_sample_models, mock_experiment): - mock_constraints_handler.return_value.apply = MagicMock() - - calculator = MockCalculator() - calculator.calculate_pattern(mock_sample_models, mock_experiment) - result = mock_experiment.datastore.calc - - # Assertions - assert np.allclose(result, np.array([3.6, 7.2, 10.8])) - mock_constraints_handler.return_value.apply.assert_called_once_with() - assert mock_experiment.datastore.bkg is not None - assert mock_experiment.datastore.calc is not None - - -def test_get_valid_linked_phases(mock_sample_models, mock_experiment): - calculator = MockCalculator() - - valid_phases = calculator._get_valid_linked_phases(mock_sample_models, mock_experiment) - - # Assertions - assert len(valid_phases) == 2 - assert valid_phases[0]._entry_id == 'phase1' - assert valid_phases[1]._entry_id == 'phase2' - - -def test_calculate_structure_factors(mock_sample_models, mock_experiment): - calculator = MockCalculator() - - # Mock the method's behavior if necessary - result = calculator.calculate_structure_factors(mock_sample_models, mock_experiment) - - # Assertions - assert np.allclose(result, np.array([1.0, 2.0, 3.0])) diff --git a/tests/unit/analysis/calculators/test_calculator_cryspy.py b/tests/unit/analysis/calculators/test_calculator_cryspy.py deleted file mode 100644 index 1d611854..00000000 --- a/tests/unit/analysis/calculators/test_calculator_cryspy.py +++ /dev/null @@ -1,103 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import numpy as np -import pytest - -from easydiffraction.analysis.calculators.calculator_cryspy import CryspyCalculator - - -@pytest.fixture -def mock_sample_model(): - sample_model = MagicMock() - sample_model.name = 'sample1' - sample_model.cell.length_a.value = 1.0 - sample_model.cell.length_b.value = 2.0 - sample_model.cell.length_c.value = 3.0 - sample_model.cell.angle_alpha.value = 90.0 - sample_model.cell.angle_beta.value = 90.0 - sample_model.cell.angle_gamma.value = 90.0 - sample_model.atom_sites = [ - MagicMock( - fract_x=MagicMock(value=0.1), - fract_y=MagicMock(value=0.2), - fract_z=MagicMock(value=0.3), - occupancy=MagicMock(value=1.0), - b_iso=MagicMock(value=0.5), - ) - ] - return sample_model - - -@pytest.fixture -def mock_experiment(): - experiment = MagicMock() - experiment.name = 'experiment1' - experiment.type.beam_mode.value = 'constant wavelength' - experiment.datastore.x = np.array([1.0, 2.0, 3.0]) - experiment.datastore.meas = np.array([10.0, 20.0, 30.0]) - experiment.datastore.meas_su = np.array([0.1, 0.2, 0.3]) - experiment.instrument.calib_twotheta_offset.value = 0.0 - experiment.instrument.setup_wavelength.value = 1.54 - experiment.peak.broad_gauss_u.value = 0.1 - experiment.peak.broad_gauss_v.value = 0.2 - experiment.peak.broad_gauss_w.value = 0.3 - experiment.peak.broad_lorentz_x.value = 0.4 - experiment.peak.broad_lorentz_y.value = 0.5 - return experiment - - -@patch('easydiffraction.analysis.calculators.calculator_cryspy.str_to_globaln') -def test_recreate_cryspy_obj(mock_str_to_globaln, mock_sample_model, mock_experiment): - mock_str_to_globaln.return_value = MagicMock(add_items=MagicMock()) - - calculator = CryspyCalculator() - cryspy_obj = calculator._recreate_cryspy_obj(mock_sample_model, mock_experiment) - - # Assertions - mock_str_to_globaln.assert_called() - assert cryspy_obj.add_items.called - - -@patch('easydiffraction.analysis.calculators.calculator_cryspy.rhochi_calc_chi_sq_by_dictionary') -def test_calculate_single_model_pattern(mock_rhochi_calc, mock_sample_model, mock_experiment): - mock_rhochi_calc.return_value = None - - calculator = CryspyCalculator() - calculator._cryspy_dicts = {'experiment1': {'mock_key': 'mock_value'}} - - result = calculator._calculate_single_model_pattern(mock_sample_model, mock_experiment, called_by_minimizer=False) - - # Assertions - assert isinstance(result, np.ndarray) or result == [] - mock_rhochi_calc.assert_called() - - -def test_recreate_cryspy_dict(mock_sample_model, mock_experiment): - calculator = CryspyCalculator() - calculator._cryspy_dicts = { - 'sample1_experiment1': { - 'pd_experiment1': { - 'offset_ttheta': [0.1], - 'wavelength': [1.54], - 'resolution_parameters': [0.1, 0.2, 0.3, 0.4, 0.5], - }, - 'crystal_sample1': { - 'unit_cell_parameters': [0, 0, 0, 0, 0, 0], - 'atom_fract_xyz': [[0], [0], [0]], - 'atom_occupancy': [0], - 'atom_b_iso': [0], - }, - } - } - - cryspy_dict = calculator._recreate_cryspy_dict(mock_sample_model, mock_experiment) - - # Assertions - assert cryspy_dict['crystal_sample1']['unit_cell_parameters'][:3] == [1.0, 2.0, 3.0] - assert cryspy_dict['crystal_sample1']['atom_fract_xyz'][0][0] == 0.1 - assert cryspy_dict['crystal_sample1']['atom_occupancy'][0] == 1.0 - assert cryspy_dict['crystal_sample1']['atom_b_iso'][0] == 0.5 - assert cryspy_dict['pd_experiment1']['offset_ttheta'][0] == 0.0 - assert cryspy_dict['pd_experiment1']['wavelength'][0] == 1.54 - assert cryspy_dict['pd_experiment1']['resolution_parameters'] == [0.1, 0.2, 0.3, 0.4, 0.5] diff --git a/tests/unit/analysis/calculators/test_calculator_factory.py b/tests/unit/analysis/calculators/test_calculator_factory.py deleted file mode 100644 index 2a35d0b1..00000000 --- a/tests/unit/analysis/calculators/test_calculator_factory.py +++ /dev/null @@ -1,65 +0,0 @@ -from unittest.mock import patch - -import pytest - -from easydiffraction.analysis.calculators.calculator_crysfml import CrysfmlCalculator -from easydiffraction.analysis.calculators.calculator_cryspy import CryspyCalculator -from easydiffraction.analysis.calculators.calculator_factory import CalculatorFactory -from easydiffraction.analysis.calculators.calculator_pdffit import PdffitCalculator -from easydiffraction.utils.formatting import paragraph - - -@pytest.fixture -def mock_calculators(): - with ( - patch.object(CrysfmlCalculator, 'engine_imported', True), - patch.object(CryspyCalculator, 'engine_imported', True), - patch.object(PdffitCalculator, 'engine_imported', False), - ): - yield - - -def test_supported_calculators(mock_calculators): - supported = CalculatorFactory._supported_calculators() - - # Assertions - assert 'crysfml' in supported - assert 'cryspy' in supported - assert 'pdffit' not in supported # Engine not imported - - -def test_list_supported_calculators(mock_calculators): - supported_list = CalculatorFactory.list_supported_calculators() - - # Assertions - assert 'crysfml' in supported_list - assert 'cryspy' in supported_list - assert 'pdffit' not in supported_list # Engine not imported - - -@patch('builtins.print') -def test_show_supported_calculators(mock_print, mock_calculators): - CalculatorFactory.show_supported_calculators() - - # Assertions - mock_print.assert_any_call(paragraph('Supported calculators')) - assert any('CrysFML library for crystallographic calculations' in call.args[0] for call in mock_print.call_args_list) - assert any('CrysPy library for crystallographic calculations' in call.args[0] for call in mock_print.call_args_list) - - -def test_create_calculator(mock_calculators): - crysfml_calculator = CalculatorFactory.create_calculator('crysfml') - cryspy_calculator = CalculatorFactory.create_calculator('cryspy') - pdffit_calculator = CalculatorFactory.create_calculator('pdffit') # Not supported - - # Assertions - assert isinstance(crysfml_calculator, CrysfmlCalculator) - assert isinstance(cryspy_calculator, CryspyCalculator) - assert pdffit_calculator is None - - -def test_create_calculator_unknown(mock_calculators): - unknown_calculator = CalculatorFactory.create_calculator('unknown') - - # Assertions - assert unknown_calculator is None diff --git a/tests/unit/analysis/collections/test_joint_fit_experiment.py b/tests/unit/analysis/collections/test_joint_fit_experiment.py deleted file mode 100644 index bb6d9d02..00000000 --- a/tests/unit/analysis/collections/test_joint_fit_experiment.py +++ /dev/null @@ -1,22 +0,0 @@ -from easydiffraction.analysis.collections.joint_fit_experiments import JointFitExperiment - -# filepath: src/easydiffraction/analysis/collections/test_joint_fit_experiments.py - - -def test_joint_fit_experiment_initialization(): - # Test initialization of JointFitExperiment - expt = JointFitExperiment(id='exp1', weight=1.5) - assert expt.id.value == 'exp1' - assert expt.id.name == 'id' - assert expt.id.cif_name == 'id' - assert expt.weight.value == 1.5 - assert expt.weight.name == 'weight' - assert expt.weight.cif_name == 'weight' - - -def test_joint_fit_experiment_properties(): - # Test properties of JointFitExperiment - expt = JointFitExperiment(id='exp2', weight=2.0) - assert expt.cif_category_key == 'joint_fit_experiment' - assert expt.category_key == 'joint_fit_experiment' - assert expt._entry_id == 'exp2' diff --git a/tests/unit/analysis/minimizers/test_fitting_progress_tracker.py b/tests/unit/analysis/minimizers/test_fitting_progress_tracker.py deleted file mode 100644 index c82f6e49..00000000 --- a/tests/unit/analysis/minimizers/test_fitting_progress_tracker.py +++ /dev/null @@ -1,98 +0,0 @@ -from unittest.mock import patch - -import numpy as np -import pytest - -from easydiffraction.analysis.fitting.progress_tracker import FittingProgressTracker -from easydiffraction.analysis.fitting.progress_tracker import format_cell - - -def test_format_cell(): - # Test center alignment - assert format_cell('test', width=10, align='center') == ' test ' - # Test left alignment - assert format_cell('test', width=10, align='left') == 'test ' - # Test right alignment - assert format_cell('test', width=10, align='right') == ' test' - # Test default alignment (center) - assert format_cell('test', width=10) == ' test ' - # Test invalid alignment - assert format_cell('test', width=10, align='invalid') == 'test' - - -@pytest.fixture -def tracker(): - return FittingProgressTracker() - - -@patch('builtins.print') -def test_start_tracking(mock_print, tracker): - tracker.start_tracking('MockMinimizer') - - # Assertions - mock_print.assert_any_call("🚀 Starting fit process with 'MockMinimizer'...") - mock_print.assert_any_call('📈 Goodness-of-fit (reduced χ²) change:') - assert mock_print.call_count > 2 # Ensure headers and borders are printed - - -@patch('builtins.print') -def test_add_tracking_info(mock_print, tracker): - tracker.add_tracking_info([1, '9.0', '10% ↓']) - - # Assertions - mock_print.assert_called_once() - assert '│ 1 │ 9.0 │ 10% ↓ │' in mock_print.call_args[0][0] - - -@patch('builtins.print') -def test_finish_tracking(mock_print, tracker): - tracker._last_iteration = 5 - tracker._last_chi2 = 1.23 - tracker._best_chi2 = 1.23 - tracker._best_iteration = 5 - - tracker.finish_tracking() - - # Assertions - mock_print.assert_any_call('🏆 Best goodness-of-fit (reduced χ²) is 1.23 at iteration 5') - mock_print.assert_any_call('✅ Fitting complete.') - - -def test_reset(tracker): - tracker._iteration = 5 - tracker._previous_chi2 = 1.23 - tracker.reset() - - # Assertions - assert tracker._iteration == 0 - assert tracker._previous_chi2 is None - - -@patch('easydiffraction.analysis.fitting.metrics.calculate_reduced_chi_square', return_value=1.23) -@patch('builtins.print') -def test_track(mock_print, mock_calculate_chi2, tracker): - residuals = np.array([1.1, 2.1, 3.1, 4.1, 5.1]) - parameters = [1.0, 2.0, 3.0] - - tracker.track(residuals, parameters) - - # Assertions - # mock_calculate_chi2.assert_called_once_with(residuals, len(parameters)) - assert tracker._iteration == 1 - assert tracker._previous_chi2 == 29.025 - assert tracker._best_chi2 == 29.025 - assert tracker._best_iteration == 1 - mock_print.assert_called() - - -def test_start_timer(tracker): - with patch('time.perf_counter', return_value=100.0): - tracker.start_timer() - assert tracker._start_time == 100.0 - - -def test_stop_timer(tracker): - with patch('time.perf_counter', side_effect=[100.0, 105.0]): - tracker.start_timer() - tracker.stop_timer() - assert tracker._fitting_time == 5.0 diff --git a/tests/unit/analysis/minimizers/test_minimizer_base.py b/tests/unit/analysis/minimizers/test_minimizer_base.py deleted file mode 100644 index be613c05..00000000 --- a/tests/unit/analysis/minimizers/test_minimizer_base.py +++ /dev/null @@ -1,116 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import pytest - -from easydiffraction.analysis.fitting.results import FitResults -from easydiffraction.analysis.minimizers.minimizer_base import MinimizerBase - - -# Mock subclass of MinimizerBase to test its methods -class MockMinimizer(MinimizerBase): - def _prepare_solver_args(self, parameters): - return {'mock_arg': 'mock_value'} - - def _run_solver(self, objective_function, **engine_parameters): - return {'success': True, 'raw_result': 'mock_result'} - - def _sync_result_to_parameters(self, raw_result, parameters): - for param in parameters: - param.value = 1.0 # Mock synchronization - - def _check_success(self, raw_result): - return raw_result.get('success', False) - - def _finalize_fit(self, parameters, raw_result): - return FitResults( - success=raw_result.get('success', False), - parameters=parameters, - chi_square=raw_result.get('chi_square', 0.0), - reduced_chi_square=raw_result.get('reduced_chi_square', 0.0), - message=raw_result.get('message', ''), - iterations=raw_result.get('iterations', 0), - engine_result=raw_result.get('raw_result', None), - starting_parameters=[p.start_value for p in parameters], - fitting_time=raw_result.get('fitting_time', 0.0), - ) - - -@pytest.fixture -def mock_minimizer(): - return MockMinimizer(name='MockMinimizer', method='mock_method', max_iterations=100) - - -@pytest.fixture -def mock_parameters(): - param1 = MagicMock(name='param1', value=None, start_value=0.5, uncertainty=None) - param2 = MagicMock(name='param2', value=None, start_value=1.0, uncertainty=None) - return [param1, param2] - - -@pytest.fixture -def mock_objective_function(): - return MagicMock(return_value=[1.0, 2.0, 3.0]) - - -def test_prepare_solver_args(mock_minimizer, mock_parameters): - solver_args = mock_minimizer._prepare_solver_args(mock_parameters) - assert solver_args == {'mock_arg': 'mock_value'} - - -def test_run_solver(mock_minimizer, mock_objective_function): - raw_result = mock_minimizer._run_solver(mock_objective_function, mock_arg='mock_value') - assert raw_result == {'success': True, 'raw_result': 'mock_result'} - - -def test_sync_result_to_parameters(mock_minimizer, mock_parameters): - raw_result = {'success': True} - mock_minimizer._sync_result_to_parameters(raw_result, mock_parameters) - - # Assertions - for param in mock_parameters: - assert param.value == 1.0 - - -def test_check_success(mock_minimizer): - raw_result = {'success': True} - assert mock_minimizer._check_success(raw_result) is True - - raw_result = {'success': False} - assert mock_minimizer._check_success(raw_result) is False - - -def test_finalize_fit(mock_minimizer, mock_parameters): - raw_result = {'success': True} - result = mock_minimizer._finalize_fit(mock_parameters, raw_result) - - # Assertions - assert isinstance(result, FitResults) - assert result.success is True - assert result.parameters == mock_parameters - - -@patch('easydiffraction.analysis.fitting.progress_tracker.FittingProgressTracker') -def test_fit(mock_tracker, mock_minimizer, mock_parameters, mock_objective_function): - mock_minimizer.tracker.finish_tracking = MagicMock() - result = mock_minimizer.fit(mock_parameters, mock_objective_function) - - # Assertions - assert isinstance(result, FitResults) - assert result.success is True - - -def test_create_objective_function(mock_minimizer): - parameters = [MagicMock()] - sample_models = MagicMock() - experiments = MagicMock() - calculator = MagicMock() - - objective_function = mock_minimizer._create_objective_function(parameters, sample_models, experiments, calculator) - - # Assertions - assert callable(objective_function) - with patch.object(mock_minimizer, '_objective_function', return_value=[1.0, 2.0, 3.0]) as mock_objective: - residuals = objective_function({'param1': 1.0}) - mock_objective.assert_called_once_with({'param1': 1.0}, parameters, sample_models, experiments, calculator) - assert residuals == [1.0, 2.0, 3.0] diff --git a/tests/unit/analysis/minimizers/test_minimizer_dfols.py b/tests/unit/analysis/minimizers/test_minimizer_dfols.py deleted file mode 100644 index db26f0bc..00000000 --- a/tests/unit/analysis/minimizers/test_minimizer_dfols.py +++ /dev/null @@ -1,79 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import numpy as np -import pytest - -from easydiffraction.analysis.minimizers.minimizer_dfols import DfolsMinimizer - - -@pytest.fixture -def mock_parameters(): - param1 = MagicMock(name='param1', value=1.0, min=0.0, max=2.0, uncertainty=None) - param2 = MagicMock(name='param2', value=2.0, min=1.0, max=3.0, uncertainty=None) - return [param1, param2] - - -@pytest.fixture -def mock_objective_function(): - return MagicMock(return_value=np.array([1.0, 2.0, 3.0])) - - -@pytest.fixture -def dfols_minimizer(): - return DfolsMinimizer(name='dfols', max_iterations=100) - - -def test_prepare_solver_args(dfols_minimizer, mock_parameters): - solver_args = dfols_minimizer._prepare_solver_args(mock_parameters) - - # Assertions - assert np.allclose(solver_args['x0'], [1.0, 2.0]) - assert np.allclose(solver_args['bounds'][0], [0.0, 1.0]) # Lower bounds - assert np.allclose(solver_args['bounds'][1], [2.0, 3.0]) # Upper bounds - - -@patch('easydiffraction.analysis.minimizers.minimizer_dfols.solve') -def test_run_solver(mock_solve, dfols_minimizer, mock_objective_function): - mock_solve.return_value = MagicMock(x=np.array([1.5, 2.5]), flag=0) - - solver_args = {'x0': np.array([1.0, 2.0]), 'bounds': (np.array([0.0, 1.0]), np.array([2.0, 3.0]))} - raw_result = dfols_minimizer._run_solver(mock_objective_function, **solver_args) - - # Assertions - mock_solve.assert_called_once_with( - mock_objective_function, x0=solver_args['x0'], bounds=solver_args['bounds'], maxfun=dfols_minimizer.max_iterations - ) - assert np.allclose(raw_result.x, [1.5, 2.5]) - - -def test_sync_result_to_parameters(dfols_minimizer, mock_parameters): - raw_result = MagicMock(x=np.array([1.5, 2.5])) - - dfols_minimizer._sync_result_to_parameters(mock_parameters, raw_result) - - # Assertions - assert mock_parameters[0].value == 1.5 - assert mock_parameters[1].value == 2.5 - assert mock_parameters[0].uncertainty is None - assert mock_parameters[1].uncertainty is None - - -def test_check_success(dfols_minimizer): - raw_result = MagicMock(flag=0, EXIT_SUCCESS=0) - assert dfols_minimizer._check_success(raw_result) is True - - raw_result = MagicMock(flag=1, EXIT_SUCCESS=0) - assert dfols_minimizer._check_success(raw_result) is False - - -@patch('easydiffraction.analysis.minimizers.minimizer_dfols.solve') -def test_fit(mock_solve, dfols_minimizer, mock_parameters, mock_objective_function): - mock_solve.return_value = MagicMock(x=np.array([1.5, 2.5]), flag=0) - dfols_minimizer.tracker.finish_tracking = MagicMock() - - result = dfols_minimizer.fit(mock_parameters, mock_objective_function) - - # Assertions - assert np.allclose([p.value for p in result.parameters], [1.5, 2.5]) - assert result.iterations == 0 # DFO-LS doesn't provide iteration count by default diff --git a/tests/unit/analysis/minimizers/test_minimizer_factory.py b/tests/unit/analysis/minimizers/test_minimizer_factory.py deleted file mode 100644 index a0d5caed..00000000 --- a/tests/unit/analysis/minimizers/test_minimizer_factory.py +++ /dev/null @@ -1,66 +0,0 @@ -from unittest.mock import patch - -import pytest - -from easydiffraction.analysis.minimizers.minimizer_dfols import DfolsMinimizer -from easydiffraction.analysis.minimizers.minimizer_factory import MinimizerFactory -from easydiffraction.analysis.minimizers.minimizer_lmfit import LmfitMinimizer - - -def test_list_available_minimizers(): - minimizers = MinimizerFactory.list_available_minimizers() - - # Assertions - assert isinstance(minimizers, list) - assert 'lmfit' in minimizers - assert 'dfols' in minimizers - - -@patch('builtins.print') -def test_show_available_minimizers(mock_print): - MinimizerFactory.show_available_minimizers() - - # Assertions - # mock_print.assert_any_call("Available minimizers") - assert any( - 'LMFIT library using the default Levenberg-Marquardt least squares method' in call.args[0] - for call in mock_print.call_args_list - ) - assert any( - 'DFO-LS library for derivative-free least-squares optimization' in call.args[0] for call in mock_print.call_args_list - ) - - -def test_create_minimizer(): - # Test creating an LmfitMinimizer - minimizer = MinimizerFactory.create_minimizer('lmfit') - assert isinstance(minimizer, LmfitMinimizer) - assert minimizer.method == 'leastsq' - - # Test creating a DfolsMinimizer - minimizer = MinimizerFactory.create_minimizer('dfols') - assert isinstance(minimizer, DfolsMinimizer) - assert minimizer.method is None - - # Test invalid minimizer - with pytest.raises(ValueError, match="Unknown minimizer 'invalid'.*"): - MinimizerFactory.create_minimizer('invalid') - - -def test_register_minimizer(): - class MockMinimizer: - def __init__(self, method=None): - self.method = method - - MinimizerFactory.register_minimizer( - name='mock_minimizer', minimizer_cls=MockMinimizer, method='mock_method', description='Mock minimizer for testing' - ) - - # Assertions - minimizers = MinimizerFactory.list_available_minimizers() - assert 'mock_minimizer' in minimizers - - # Test creating the registered minimizer - minimizer = MinimizerFactory.create_minimizer('mock_minimizer') - assert isinstance(minimizer, MockMinimizer) - assert minimizer.method == 'mock_method' diff --git a/tests/unit/analysis/minimizers/test_minimizer_lmfit.py b/tests/unit/analysis/minimizers/test_minimizer_lmfit.py deleted file mode 100644 index 5a0e5938..00000000 --- a/tests/unit/analysis/minimizers/test_minimizer_lmfit.py +++ /dev/null @@ -1,98 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import lmfit -import pytest - -from easydiffraction.analysis.minimizers.minimizer_lmfit import LmfitMinimizer -from easydiffraction.core.objects import Parameter - - -@pytest.fixture -def mock_parameters(): - param1 = Parameter(name='param1', cif_name='param1', value=1.0, free=True, min_value=0.0, max_value=2.0, uncertainty=None) - param2 = Parameter(name='param2', cif_name='param2', value=2.0, free=False, min_value=1.0, max_value=3.0, uncertainty=None) - return [param1, param2] - - -@pytest.fixture -def mock_objective_function(): - return MagicMock(return_value=[1.0, 2.0, 3.0]) - - -@pytest.fixture -def lmfit_minimizer(): - return LmfitMinimizer(name='lmfit', method='leastsq', max_iterations=100) - - -def test_prepare_solver_args(lmfit_minimizer, mock_parameters): - solver_args = lmfit_minimizer._prepare_solver_args(mock_parameters) - - # Assertions - assert isinstance(solver_args['engine_parameters'], lmfit.Parameters) - assert 'None__param1' in solver_args['engine_parameters'] - assert 'None__param2' in solver_args['engine_parameters'] - assert solver_args['engine_parameters']['None__param1'].value == 1.0 - assert solver_args['engine_parameters']['None__param1'].min == 0.0 - assert solver_args['engine_parameters']['None__param1'].max == 2.0 - assert solver_args['engine_parameters']['None__param1'].vary is True - assert solver_args['engine_parameters']['None__param2'].value == 2.0 - assert solver_args['engine_parameters']['None__param2'].vary is False - - -@patch('easydiffraction.analysis.minimizers.minimizer_lmfit.lmfit.minimize') -def test_run_solver(mock_minimize, lmfit_minimizer, mock_objective_function, mock_parameters): - mock_minimize.return_value = MagicMock(params={'param1': MagicMock(value=1.5), 'param2': MagicMock(value=2.5)}) - - solver_args = lmfit_minimizer._prepare_solver_args(mock_parameters) - raw_result = lmfit_minimizer._run_solver(mock_objective_function, **solver_args) - - # Assertions - mock_minimize.assert_called_once_with( - mock_objective_function, - params=solver_args['engine_parameters'], - method='leastsq', - nan_policy='propagate', - max_nfev=lmfit_minimizer.max_iterations, - ) - assert raw_result.params['param1'].value == 1.5 - assert raw_result.params['param2'].value == 2.5 - - -def test_sync_result_to_parameters(lmfit_minimizer, mock_parameters): - raw_result = MagicMock( - params={'None__param1': MagicMock(value=1.5, stderr=0.1), 'None__param2': MagicMock(value=2.5, stderr=0.2)} - ) - - lmfit_minimizer._sync_result_to_parameters(mock_parameters, raw_result) - - # Assertions - assert mock_parameters[0].value == 1.5 - assert mock_parameters[0].uncertainty == 0.1 - assert mock_parameters[1].value == 2.5 - assert mock_parameters[1].uncertainty == 0.2 - - -def test_check_success(lmfit_minimizer): - raw_result = MagicMock(success=True) - assert lmfit_minimizer._check_success(raw_result) is True - - raw_result = MagicMock(success=False) - assert lmfit_minimizer._check_success(raw_result) is False - - -@patch('easydiffraction.analysis.minimizers.minimizer_lmfit.lmfit.minimize') -def test_fit(mock_minimize, lmfit_minimizer, mock_parameters, mock_objective_function): - mock_minimize.return_value = MagicMock( - params={'None__param1': MagicMock(value=1.5, stderr=0.1), 'None__param2': MagicMock(value=2.5, stderr=0.2)}, - success=True, - ) - lmfit_minimizer.tracker.finish_tracking = MagicMock() - result = lmfit_minimizer.fit(mock_parameters, mock_objective_function) - - # Assertions - assert result.success is True - assert result.parameters[0].value == 1.5 - assert result.parameters[0].uncertainty == 0.1 - assert result.parameters[1].value == 2.5 - assert result.parameters[1].uncertainty == 0.2 diff --git a/tests/unit/analysis/test_analysis.py b/tests/unit/analysis/test_analysis.py deleted file mode 100644 index 245a57b0..00000000 --- a/tests/unit/analysis/test_analysis.py +++ /dev/null @@ -1,157 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import pytest - -from easydiffraction.analysis.analysis import Analysis - - -@pytest.fixture -def mock_project(): - project = MagicMock() - project.sample_models.get_all_params.return_value = [ - MagicMock( - datablock_id='block1', - category_key='cat1', - collection_entry_id='entry1', - name='param1', - value=1.0, - units='unit1', - free=True, - min=0.0, - max=2.0, - uncertainty=0.1, - ) - ] - project.experiments.get_all_params.return_value = [ - MagicMock( - datablock_id='block2', - category_key='cat2', - collection_entry_id='entry2', - name='param2', - value=2.0, - units='unit2', - free=False, - min=1.0, - max=3.0, - uncertainty=0.2, - ) - ] - project.sample_models.get_fittable_params.return_value = project.sample_models.get_all_params() - project.experiments.get_fittable_params.return_value = project.experiments.get_all_params() - project.sample_models.get_free_params.return_value = project.sample_models.get_all_params() - project.experiments.get_free_params.return_value = project.experiments.get_all_params() - project.experiments.ids = ['experiment1', 'experiment2'] - project._varname = 'project' - return project - - -@pytest.fixture -def analysis(mock_project): - return Analysis(project=mock_project) - - -# @patch("builtins.print") -# def test_show_all_params(mock_print, analysis): -# analysis._show_params = MagicMock() -# analysis.show_all_params() -# -# # Assertions -# assert('parameters for all experiments' in mock_print.call_args[0][0]) -# -# @patch("builtins.print") -# def test_show_fittable_params(mock_print, analysis): -# analysis._show_params = MagicMock() -# analysis.show_fittable_params() -# -# # Assertions -# assert('Fittable parameters for all experiments' in mock_print.call_args[0][0]) -# -# @patch("builtins.print") -# def test_show_free_params(mock_print, analysis): -# analysis._show_params = MagicMock() -# analysis.show_free_params() -# -# # Assertions -# assert('Free parameters for both sample models' in mock_print.call_args[0][0]) -# # mock_print.assert_any_call("Free parameters for both sample models (🧩 data blocks) and experiments (🔬 data blocks)") - - -@patch('builtins.print') -def test_show_current_calculator(mock_print, analysis): - analysis.show_current_calculator() - - # Assertions - # mock_print.assert_any_call("Current calculator") - mock_print.assert_any_call('cryspy') - - -@patch('builtins.print') -def test_show_current_minimizer(mock_print, analysis): - analysis.show_current_minimizer() - - # Assertions - # mock_print.assert_any_call("Current minimizer") - mock_print.assert_any_call('lmfit (leastsq)') - - -@patch('easydiffraction.analysis.calculators.calculator_factory.CalculatorFactory.create_calculator') -@patch('builtins.print') -def test_current_calculator_setter(mock_print, mock_create_calculator, analysis): - mock_create_calculator.return_value = MagicMock() - - analysis.current_calculator = 'pdffit2' - - # Assertions - mock_create_calculator.assert_called_once_with('pdffit2') - - -@patch('easydiffraction.analysis.minimizers.minimizer_factory.MinimizerFactory.create_minimizer') -@patch('builtins.print') -def test_current_minimizer_setter(mock_print, mock_create_minimizer, analysis): - mock_create_minimizer.return_value = MagicMock() - - analysis.current_minimizer = 'dfols' - - # Assertions - mock_print.assert_any_call('dfols') - - -@patch('builtins.print') -def test_fit_mode_setter(mock_print, analysis): - analysis.fit_mode = 'joint' - - # Assertions - assert analysis.fit_mode == 'joint' - mock_print.assert_any_call('joint') - - -@patch('easydiffraction.analysis.minimization.DiffractionMinimizer.fit') -@patch('builtins.print') -def no_test_fit_single_mode(mock_print, mock_fit, analysis, mock_project): - analysis.fit_mode = 'single' - analysis.fit() - - # Assertions - mock_fit.assert_called() - mock_print.assert_any_call('single') - - -@patch('easydiffraction.analysis.minimization.DiffractionMinimizer.fit') -@patch('builtins.print') -def test_fit_joint_mode(mock_print, mock_fit, analysis, mock_project): - analysis.fit_mode = 'joint' - analysis.fit() - - # Assertions - mock_fit.assert_called_once() - - -@patch('builtins.print') -def test_as_cif(mock_print, analysis): - cif_text = analysis.as_cif() - - # Assertions - assert '_analysis.calculator_engine cryspy' in cif_text - assert '_analysis.fitting_engine "lmfit (leastsq)"' in cif_text - assert '_analysis.fit_mode single' in cif_text diff --git a/tests/unit/analysis/test_minimization.py b/tests/unit/analysis/test_minimization.py deleted file mode 100644 index 90e39c3f..00000000 --- a/tests/unit/analysis/test_minimization.py +++ /dev/null @@ -1,163 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import numpy as np -import pytest - -from easydiffraction.analysis.minimization import DiffractionMinimizer - - -@pytest.fixture -def mock_sample_models(): - sample_models = MagicMock() - sample_models.get_free_params.return_value = [ - MagicMock(name='param1', value=1.0, start_value=None, min=0.0, max=2.0, free=True), - MagicMock(name='param2', value=2.0, start_value=None, min=1.0, max=3.0, free=True), - ] - return sample_models - - -@pytest.fixture -def mock_experiments(): - experiments = MagicMock() - experiments.get_free_params.return_value = [ - MagicMock(name='param3', value=3.0, start_value=None, min=2.0, max=4.0, free=True), - ] - experiments.ids = ['experiment1'] - experiments._items = { - 'experiment1': MagicMock( - datastore=MagicMock( - pattern=MagicMock( - meas=np.array([10.0, 20.0, 30.0]), - meas_su=np.array([1.0, 1.0, 1.0]), - excluded=np.array([False, False, False]), - ) - ) - ) - } - return experiments - - -@pytest.fixture -def mock_calculator(): - calculator = MagicMock() - - def mock_calculate_pattern(sample_models, experiment, **kwargs): - experiment.datastore.calc = np.array([9.0, 19.0, 29.0]) - - calculator.calculate_pattern.side_effect = mock_calculate_pattern - return calculator - - -@pytest.fixture -def mock_minimizer(): - minimizer = MagicMock() - minimizer.fit.return_value = MagicMock(success=True) - minimizer._sync_result_to_parameters = MagicMock() - minimizer.tracker.track = MagicMock(return_value=np.array([1.0, 2.0, 3.0])) - return minimizer - - -@pytest.fixture -def diffraction_minimizer(mock_minimizer): - with patch( - 'easydiffraction.analysis.minimizers.minimizer_factory.MinimizerFactory.create_minimizer', - return_value=mock_minimizer, - ): - return DiffractionMinimizer(selection='lmfit (leastsq)') - - -def test_fit_no_params( - diffraction_minimizer, - mock_sample_models, - mock_experiments, - mock_calculator, -): - mock_sample_models.get_free_params.return_value = [] - mock_experiments.get_free_params.return_value = [] - - result = diffraction_minimizer.fit( - mock_sample_models, - mock_experiments, - mock_calculator, - ) - - # Assertions - assert result is None - - -def test_fit_with_params( - diffraction_minimizer, - mock_sample_models, - mock_experiments, - mock_calculator, -): - diffraction_minimizer.fit( - mock_sample_models, - mock_experiments, - mock_calculator, - ) - - # Assertions - assert diffraction_minimizer.results.success is True - assert mock_calculator.calculate_pattern.called - assert mock_sample_models.get_free_params.called - assert mock_experiments.get_free_params.called - - -def test_residual_function( - diffraction_minimizer, - mock_sample_models, - mock_experiments, - mock_calculator, -): - parameters = mock_sample_models.get_free_params() + mock_experiments.get_free_params() - engine_params = MagicMock() - - residuals = diffraction_minimizer._residual_function( - engine_params=engine_params, - parameters=parameters, - sample_models=mock_sample_models, - experiments=mock_experiments, - calculator=mock_calculator, - ) - - # Assertions - assert isinstance(residuals, np.ndarray) - assert len(residuals) == 3 - assert mock_calculator.calculate_pattern.called - assert diffraction_minimizer.minimizer._sync_result_to_parameters.called - - -# @patch( -# 'easydiffraction.analysis.reliability_factors.get_reliability_inputs', -# return_value=(np.array([10.0]), np.array([9.0]), np.array([1.0])), -# ) -# def test_process_fit_results( -# mock_get_reliability_inputs, -# diffraction_minimizer, -# mock_sample_models, -# mock_experiments, -# mock_calculator, -# ): -# diffraction_minimizer.results = MagicMock() -# diffraction_minimizer._process_fit_results( -# mock_sample_models, -# mock_experiments, -# mock_calculator, -# ) -# -# # Assertions -# # mock_get_reliability_inputs.assert_called_once_with(mock_sample_models, mock_experiments, mock_calculator) -# -# # Extract the arguments passed to `display_results` -# _, kwargs = diffraction_minimizer.results.display_results.call_args -# -# # Assertions for arrays -# np.testing.assert_array_equal(kwargs['y_calc'], np.array([9.0, 19.0, 29.0])) -# np.testing.assert_array_equal(kwargs['y_err'], np.array([1.0, 1.0, 1.0])) -# np.testing.assert_array_equal(kwargs['y_obs'], np.array([10.0, 20.0, 30.0])) -# -# # Assertions for other arguments -# assert kwargs['f_obs'] is None -# assert kwargs['f_calc'] is None diff --git a/tests/unit/analysis/test_reliability_factors.py b/tests/unit/analysis/test_reliability_factors.py deleted file mode 100644 index 9f326b55..00000000 --- a/tests/unit/analysis/test_reliability_factors.py +++ /dev/null @@ -1,110 +0,0 @@ -from unittest.mock import Mock - -import numpy as np - -from easydiffraction.analysis.fitting.metrics import calculate_r_factor -from easydiffraction.analysis.fitting.metrics import calculate_r_factor_squared -from easydiffraction.analysis.fitting.metrics import calculate_rb_factor -from easydiffraction.analysis.fitting.metrics import calculate_reduced_chi_square -from easydiffraction.analysis.fitting.metrics import calculate_weighted_r_factor -from easydiffraction.analysis.fitting.metrics import get_reliability_inputs - - -def test_calculate_r_factor(): - y_obs = [10, 20, 30] - y_calc = [9, 19, 29] - result = calculate_r_factor(y_obs, y_calc) - expected = 0.05 - np.testing.assert_allclose(result, expected) - - # Test with empty arrays - assert np.isnan(calculate_r_factor([], [])) - - # Test with zero denominator - assert np.isnan(calculate_r_factor([0, 0, 0], [1, 1, 1])) - - -def test_calculate_weighted_r_factor(): - y_obs = [10, 20, 30] - y_calc = [9, 19, 29] - weights = [1, 1, 1] - result = calculate_weighted_r_factor(y_obs, y_calc, weights) - expected = 0.04629100498862757 - np.testing.assert_allclose(result, expected) - - # Test with empty arrays - assert np.isnan(calculate_weighted_r_factor([], [], [])) - - # Test with zero denominator - assert np.isnan(calculate_weighted_r_factor([0, 0, 0], [1, 1, 1], [1, 1, 1])) - - -def test_calculate_rb_factor(): - y_obs = [10, 20, 30] - y_calc = [9, 19, 29] - result = calculate_rb_factor(y_obs, y_calc) - expected = 0.05 - np.testing.assert_allclose(result, expected) - - # Test with empty arrays - assert np.isnan(calculate_rb_factor([], [])) - - # Test with zero denominator - assert np.isnan(calculate_rb_factor([0, 0, 0], [1, 1, 1])) - - -def test_calculate_r_factor_squared(): - y_obs = [10, 20, 30] - y_calc = [9, 19, 29] - result = calculate_r_factor_squared(y_obs, y_calc) - expected = 0.04629100498862757 - np.testing.assert_allclose(result, expected) - - # Test with empty arrays - assert np.isnan(calculate_r_factor_squared([], [])) - - # Test with zero denominator - assert np.isnan(calculate_r_factor_squared([0, 0, 0], [1, 1, 1])) - - -def test_calculate_reduced_chi_square(): - residuals = [1, 2, 3] - num_parameters = 1 - result = calculate_reduced_chi_square(residuals, num_parameters) - expected = 7.0 - np.testing.assert_allclose(result, expected) - - # Test with empty residuals - assert np.isnan(calculate_reduced_chi_square([], 1)) - - # Test with zero degrees of freedom - assert np.isnan(calculate_reduced_chi_square([1, 2, 3], 3)) - - -def test_get_reliability_inputs(): - # Mock inputs - sample_models = None - experiments = Mock() - calculator = Mock() - - experiments._items = { - 'experiment1': Mock( - datastore=Mock( - meas=np.array([10.0, 20.0, 30.0]), - meas_su=np.array([1.0, 1.0, 1.0]), - excluded=np.array([False, False, False]), - ) - ) - } - - def mock_calculate_pattern(sample_models, experiment, **kwargs): - experiment.datastore.calc = np.array([9.0, 19.0, 29.0]) - - calculator.calculate_pattern.side_effect = mock_calculate_pattern - - y_obs, y_calc, y_err = get_reliability_inputs(sample_models, experiments, calculator) - - # Assertions - np.testing.assert_array_equal(y_obs, [10.0, 20.0, 30.0]) - np.testing.assert_array_equal(y_calc, [9.0, 19.0, 29.0]) - np.testing.assert_array_equal(y_err, [1.0, 1.0, 1.0]) diff --git a/tests/unit/core/test_objects.py b/tests/unit/core/test_objects.py deleted file mode 100644 index 7873e85b..00000000 --- a/tests/unit/core/test_objects.py +++ /dev/null @@ -1,128 +0,0 @@ -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Datablock -from easydiffraction.core.objects import Descriptor -from easydiffraction.core.objects import Parameter - -# filepath: src/easydiffraction/core/test_objects.py - - -def test_descriptor_initialization(): - desc = Descriptor(value=10, name='test', cif_name='test_cif', editable=True) - assert desc.value == 10 - assert desc.name == 'test' - assert desc.cif_name == 'test_cif' - assert desc.editable is True - - -def test_descriptor_value_setter(): - desc = Descriptor(value=10, name='test', cif_name='test_cif', editable=True) - desc.value = 20 - assert desc.value == 20 - - desc_non_editable = Descriptor(value=10, name='test', cif_name='test_cif', editable=False) - desc_non_editable.value = 30 - assert desc_non_editable.value == 10 # Value should not change - - -def test_parameter_initialization(): - param = Parameter( - value=5.0, - name='param', - cif_name='param_cif', - uncertainty=0.1, - free=True, - constrained=False, - min_value=0.0, - max_value=10.0, - ) - assert param.value == 5.0 - assert param.uncertainty == 0.1 - assert param.free is True - assert param.constrained is False - assert param.min == 0.0 - assert param.max == 10.0 - - -def test_component_abstract_methods(): - class TestComponent(Component): - @property - def category_key(self): - return 'test_category' - - @property - def cif_category_key(self): - return 'test_cif_category' - - comp = TestComponent() - assert comp.category_key == 'test_category' - assert comp.cif_category_key == 'test_cif_category' - - -def test_component_attribute_handling(): - class TestComponent(Component): - @property - def category_key(self): - return 'test_category' - - @property - def cif_category_key(self): - return 'test_cif_category' - - comp = TestComponent() - desc = Descriptor(value=10, name='test', cif_name='test_cif') - comp.test_attr = desc - assert comp.test_attr.value == 10 # Access Descriptor value directly - - -def test_collection_add_and_retrieve(): - class TestCollection(Collection): - @property - def _child_class(self): - return str - - collection = TestCollection() - - collection._items['item1'] = 'value1' - collection._items['item2'] = 'value2' - - assert collection['item1'] == 'value1' - assert collection['item2'] == 'value2' - - -def test_collection_iteration(): - class TestCollection(Collection): - @property - def _child_class(self): - return str - - collection = TestCollection() - - collection._items['item1'] = 'value1' - collection._items['item2'] = 'value2' - - items = list(collection) - assert items == ['value1', 'value2'] - - -def test_datablock_components(): - class TestComponent(Component): - @property - def category_key(self): - return 'test_category' - - @property - def cif_category_key(self): - return 'test_cif_category' - - class TestDatablock(Datablock): - def __init__(self): - super().__init__() - self.component1 = TestComponent() - self.component2 = TestComponent() - - datablock = TestDatablock() - items = datablock.items() - assert len(items) == 2 - assert isinstance(items[0], TestComponent) - assert isinstance(items[1], TestComponent) diff --git a/tests/unit/core/test_singletons.py b/tests/unit/core/test_singletons.py deleted file mode 100644 index f7f22212..00000000 --- a/tests/unit/core/test_singletons.py +++ /dev/null @@ -1,87 +0,0 @@ -from unittest.mock import MagicMock - -import pytest - -from easydiffraction.core.objects import Parameter -from easydiffraction.core.singletons import BaseSingleton -from easydiffraction.core.singletons import ConstraintsHandler -from easydiffraction.core.singletons import UidMapHandler - - -@pytest.fixture -def params(): - param1 = Parameter(value=1.0, name='param1', cif_name='param1_cif') - param2 = Parameter(value=2.0, name='param2', cif_name='param2_cif') - return param1, param2 - - -@pytest.fixture -def mock_aliases(params): - param1, param2 = params - mock = MagicMock() - mock._items = { - 'alias1': MagicMock(label=MagicMock(value='alias1'), param_uid=MagicMock(value=param1.uid)), - 'alias2': MagicMock(label=MagicMock(value='alias2'), param_uid=MagicMock(value=param2.uid)), - } - return mock - - -@pytest.fixture -def mock_constraints(): - mock = MagicMock() - mock._items = { - 'expr1': MagicMock(lhs_alias=MagicMock(value='alias1'), rhs_expr=MagicMock(value='alias2 + 1')), - 'expr2': MagicMock(lhs_alias=MagicMock(value='alias2'), rhs_expr=MagicMock(value='alias1 * 2')), - } - return mock - - -def test_base_singleton(): - class TestSingleton(BaseSingleton): - pass - - instance1 = TestSingleton.get() - instance2 = TestSingleton.get() - - assert instance1 is instance2 # Ensure only one instance is created - - -def test_uid_map_handler(params): - param1, param2 = params - handler = UidMapHandler.get() - uid_map = handler.get_uid_map() - - assert uid_map[param1.uid] is param1 - assert uid_map[param2.uid] is param2 - assert uid_map[param1.uid].uid == 'None.param1_cif' - assert uid_map[param2.uid].uid == 'None.param2_cif' - - -def test_constraints_handler_set_aliases(mock_aliases, params): - param1, param2 = params - handler = ConstraintsHandler.get() - handler.set_aliases(mock_aliases) - - assert handler._alias_to_param['alias1'].param_uid.value is param1.uid - assert handler._alias_to_param['alias2'].param_uid.value is param2.uid - - -def test_constraints_handler_set_constraints(mock_constraints): - handler = ConstraintsHandler.get() - handler.set_constraints(mock_constraints) - - assert len(handler._parsed_constraints) == 2 - assert handler._parsed_constraints[0] == ('alias1', 'alias2 + 1') - assert handler._parsed_constraints[1] == ('alias2', 'alias1 * 2') - - -def test_constraints_handler_apply(mock_aliases, mock_constraints, params): - param1, _ = params - handler = ConstraintsHandler.get() - handler.set_aliases(mock_aliases) - handler.set_constraints(mock_constraints) - - handler.apply() - - assert param1.value == 3.0 - assert param1.constrained is True diff --git a/tests/unit/easydiffraction/analysis/calculators/test_base.py b/tests/unit/easydiffraction/analysis/calculators/test_base.py new file mode 100644 index 00000000..53a1684d --- /dev/null +++ b/tests/unit/easydiffraction/analysis/calculators/test_base.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_module_import(): + import easydiffraction.analysis.calculators.base as MUT + + assert MUT.__name__ == 'easydiffraction.analysis.calculators.base' + + +def test_calculator_base_get_valid_linked_phases_filters_missing(): + from easydiffraction.analysis.calculators.base import CalculatorBase + + class DummyCalc(CalculatorBase): + @property + def name(self): + return 'dummy' + + @property + def engine_imported(self): + return True + + def calculate_structure_factors(self, sample_model, experiment): + pass + + def _calculate_single_model_pattern(self, sample_model, experiment, called_by_minimizer): + return np.zeros_like(experiment.datastore.x) + + class DummyLinked: + def __init__(self, entry_name): + self._identity = type('I', (), {'category_entry_name': entry_name}) + self.scale = type('S', (), {'value': 1.0}) + self.id = type('ID', (), {'value': entry_name}) + + class DummyStore: + def __init__(self, n=5): + self.x = np.arange(n, dtype=float) + + class DummyExperiment: + def __init__(self, linked): + self.linked_phases = linked + self.datastore = DummyStore() + + def _public(): + return [] + + self._public_attrs = _public + + class DummySampleModels(dict): + @property + def names(self): + return list(self.keys()) + + calc = DummyCalc() + expt = DummyExperiment([DummyLinked('present'), DummyLinked('absent')]) + sm = DummySampleModels({'present': object()}) + valid = calc._get_valid_linked_phases(sm, expt) + assert len(valid) == 1 and valid[0]._identity.category_entry_name == 'present' diff --git a/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py b/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py new file mode 100644 index 00000000..d0eea743 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_module_import(): + import easydiffraction.analysis.calculators.crysfml as MUT + + assert MUT.__name__ == 'easydiffraction.analysis.calculators.crysfml' + + +def test_crysfml_engine_flag_and_structure_factors_raises(): + from easydiffraction.analysis.calculators.crysfml import CrysfmlCalculator + + calc = CrysfmlCalculator() + # engine_imported is a boolean flag; it may be False in our env + assert isinstance(calc.engine_imported, bool) + with pytest.raises(NotImplementedError): + calc.calculate_structure_factors(sample_models=None, experiments=None) + + +def test_crysfml_adjust_pattern_length_truncates(): + from easydiffraction.analysis.calculators.crysfml import CrysfmlCalculator + + calc = CrysfmlCalculator() + long = list(range(10)) + out = calc._adjust_pattern_length(long, target_length=4) + assert out == [0, 1, 2, 3] diff --git a/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py b/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py new file mode 100644 index 00000000..f1bfd927 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.analysis.calculators.cryspy as MUT + + assert MUT.__name__ == 'easydiffraction.analysis.calculators.cryspy' + + +def test_cryspy_calculator_engine_flag_and_converters(): + # These tests avoid requiring real cryspy by not invoking heavy paths + from easydiffraction.analysis.calculators.cryspy import CryspyCalculator + + calc = CryspyCalculator() + # engine_imported is boolean (may be False if cryspy not installed) + assert isinstance(calc.engine_imported, bool) + + # Converters should just delegate/format without external deps + class DummySample: + @property + def as_cif(self): + return 'data_x' + + # _convert_sample_model_to_cryspy_cif returns input as_cif + assert calc._convert_sample_model_to_cryspy_cif(DummySample()) == 'data_x' diff --git a/tests/unit/easydiffraction/analysis/calculators/test_factory.py b/tests/unit/easydiffraction/analysis/calculators/test_factory.py new file mode 100644 index 00000000..2fed1816 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/calculators/test_factory.py @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_list_and_show_supported_calculators_do_not_crash(capsys, monkeypatch): + from easydiffraction.analysis.calculators.factory import CalculatorFactory + + # Simulate no engines available by forcing engine_imported to False + class DummyCalc: + def __call__(self): + return self + + @property + def engine_imported(self): + return False + + monkeypatch = monkeypatch # keep name + monkeypatch.setitem( + CalculatorFactory._potential_calculators, + 'dummy', + { + 'description': 'Dummy calc', + 'class': DummyCalc, + }, + ) + + lst = CalculatorFactory.list_supported_calculators() + assert isinstance(lst, list) + + CalculatorFactory.show_supported_calculators() + out = capsys.readouterr().out + # Should print the paragraph title + assert 'Supported calculators' in out + + +def test_create_calculator_unknown_returns_none(capsys): + from easydiffraction.analysis.calculators.factory import CalculatorFactory + + obj = CalculatorFactory.create_calculator('this_is_unknown') + assert obj is None + out = capsys.readouterr().out + assert 'Unknown calculator' in out diff --git a/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py b/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py new file mode 100644 index 00000000..fdb7783e --- /dev/null +++ b/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py @@ -0,0 +1,98 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_module_import(): + import easydiffraction.analysis.calculators.pdffit as MUT + + assert MUT.__name__ == 'easydiffraction.analysis.calculators.pdffit' + + +def test_pdffit_engine_flag_and_hkl_message(capsys): + from easydiffraction.analysis.calculators.pdffit import PdffitCalculator + + calc = PdffitCalculator() + assert isinstance(calc.engine_imported, bool) + # calculate_structure_factors prints fixed message and returns [] by contract + out = calc.calculate_structure_factors(sample_models=None, experiments=None) + assert out == [] + # The method prints a note + printed = capsys.readouterr().out + assert 'HKLs (not applicable)' in printed + + +def test_pdffit_cif_v2_to_v1_regex_behavior(monkeypatch): + # Exercise the regex conversion path indirectly by providing minimal objects + from easydiffraction.analysis.calculators.pdffit import PdffitCalculator + + class DummyParam: + def __init__(self, v): + self.value = v + + class DummyPeak: + # provide required attributes used in calculation + def __init__(self): + self.sharp_delta_1 = DummyParam(0.0) + self.sharp_delta_2 = DummyParam(0.0) + self.damp_particle_diameter = DummyParam(0.0) + self.cutoff_q = DummyParam(1.0) + self.damp_q = DummyParam(0.0) + self.broad_q = DummyParam(0.0) + + class DummyLinkedPhases(dict): + def __getitem__(self, k): + return type('LP', (), {'scale': DummyParam(1.0)})() + + class DummyExperiment: + def __init__(self): + self.name = 'E' + self.peak = DummyPeak() + self.datastore = type('D', (), {'x': np.linspace(0.0, 1.0, 5)})() + self.type = type('T', (), {'radiation_probe': type('P', (), {'value': 'neutron'})()})() + self.linked_phases = DummyLinkedPhases() + + class DummySampleModel: + name = 'PhaseA' + + @property + def as_cif(self): + # CIF v2-like tags with dots between letters + return '_atom.site.label A1\n_cell.length_a 1.0' + + # Monkeypatch PdfFit and parser to avoid real engine usage + import easydiffraction.analysis.calculators.pdffit as mod + + class FakePdf: + def add_structure(self, s): + pass + + def setvar(self, *a, **k): + pass + + def read_data_lists(self, *a, **k): + pass + + def calc(self): + pass + + def getpdf_fit(self): + return [0.0, 0.0, 0.0, 0.0, 0.0] + + class FakeParser: + def parse(self, text): + # Ensure the dot between letters is converted to underscore + assert '_atom_site_label' in text or '_atom.site.label' not in text + return object() + + monkeypatch.setattr(mod, 'PdfFit', FakePdf) + monkeypatch.setattr(mod, 'pdffit_cif_parser', lambda: FakeParser()) + monkeypatch.setattr(mod, 'redirect_stdout', lambda *a, **k: None) + monkeypatch.setattr(mod, '_pdffit_devnull', None, raising=False) + + calc = PdffitCalculator() + pattern = calc._calculate_single_model_pattern( + DummySampleModel(), DummyExperiment(), called_by_minimizer=False + ) + assert isinstance(pattern, np.ndarray) and pattern.shape[0] == 5 diff --git a/tests/unit/easydiffraction/analysis/categories/test_aliases.py b/tests/unit/easydiffraction/analysis/categories/test_aliases.py new file mode 100644 index 00000000..1971ee73 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/categories/test_aliases.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.analysis.categories.aliases import Alias +from easydiffraction.analysis.categories.aliases import Aliases + + +def test_alias_creation_and_collection(): + a = Alias(label='x', param_uid='p1') + assert a.label.value == 'x' + coll = Aliases() + coll.add(a) + # Collections index by entry name; check via names or direct indexing + assert 'x' in coll.names + assert coll['x'].param_uid.value == 'p1' diff --git a/tests/unit/easydiffraction/analysis/categories/test_constraints.py b/tests/unit/easydiffraction/analysis/categories/test_constraints.py new file mode 100644 index 00000000..96d960ae --- /dev/null +++ b/tests/unit/easydiffraction/analysis/categories/test_constraints.py @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.analysis.categories.constraints import Constraint +from easydiffraction.analysis.categories.constraints import Constraints + + +def test_constraint_creation_and_collection(): + c = Constraint(lhs_alias='a', rhs_expr='b + c') + assert c.lhs_alias.value == 'a' + coll = Constraints() + coll.add(c) + assert 'a' in coll.names + assert coll['a'].rhs_expr.value == '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 new file mode 100644 index 00000000..cf706705 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/categories/test_joint_fit_experiments.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.analysis.categories.joint_fit_experiments import JointFitExperiment +from easydiffraction.analysis.categories.joint_fit_experiments import JointFitExperiments + + +def test_joint_fit_experiment_and_collection(): + j = JointFitExperiment(id='ex1', weight=0.5) + assert j.id.value == 'ex1' + assert j.weight.value == 0.5 + coll = JointFitExperiments() + coll.add(j) + assert 'ex1' in coll.names + assert coll['ex1'].weight.value == 0.5 diff --git a/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py b/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py new file mode 100644 index 00000000..184dad92 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_calculate_r_metrics_and_chi_square(): + from easydiffraction.analysis.fit_helpers import metrics as M + + y_obs = np.array([1.0, 2.0, 3.0]) + y_calc = np.array([1.1, 1.9, 2.8]) + weights = np.array([1.0, 2.0, 3.0]) + residuals = y_obs - y_calc + + r = M.calculate_r_factor(y_obs, y_calc) + rb = M.calculate_rb_factor(y_obs, y_calc) + rw = M.calculate_weighted_r_factor(y_obs, y_calc, weights) + r2 = M.calculate_r_factor_squared(y_obs, y_calc) + chi2 = M.calculate_reduced_chi_square(residuals, num_parameters=1) + + assert 0 <= r <= 1 and np.isfinite(r) + assert np.isclose(r, rb) + assert np.isfinite(rw) + assert np.isfinite(r2) + assert np.isfinite(chi2) + + +def test_get_reliability_inputs_collects_arrays_with_default_su(): + from easydiffraction.analysis.fit_helpers import metrics as M + + # Minimal fakes for experiments + class DS: + def __init__(self): + self.meas = np.array([1.0, 2.0]) + self.meas_su = None # triggers default ones + self.calc = np.array([1.1, 1.9]) + + class Expt: + def __init__(self): + self.datastore = DS() + + class Expts(dict): + def values(self): + return [Expt()] + + class SampleModels(dict): + pass + + class Calc: + def calculate_pattern(self, sample_models, experiment): + # no-op; calc already set in DS + return None + + y_obs, y_calc, y_err = M.get_reliability_inputs(SampleModels(), Expts(), Calc()) + assert y_obs.shape == (2,) and y_calc.shape == (2,) and y_err.shape == (2,) + assert np.allclose(y_err, 1.0) diff --git a/tests/unit/easydiffraction/analysis/fit_helpers/test_reporting.py b/tests/unit/easydiffraction/analysis/fit_helpers/test_reporting.py new file mode 100644 index 00000000..3ac4c1c9 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/fit_helpers/test_reporting.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.analysis.fit_helpers.reporting as MUT + + expected_module_name = 'easydiffraction.analysis.fit_helpers.reporting' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_fitresults_display_results_prints_and_table(capsys, monkeypatch): + # Arrange: build a minimal fake parameter object with required attributes + class Identity: + def __init__(self): + self.datablock_entry_name = 'db' + self.category_code = 'cat' + self.category_entry_name = 'entry' + + class Param: + def __init__(self, start, value, uncertainty, name='p', units='u'): + self._identity = Identity() + self._fit_start_value = start + self.value = value + self.uncertainty = uncertainty + self.name = name + self.units = units + + from easydiffraction.analysis.fit_helpers.reporting import FitResults + + params = [Param(start=1.0, value=1.2, uncertainty=0.05, name='a', units='arb')] + + # Act: create results and display with all metrics available + fr = FitResults( + success=True, + parameters=params, + reduced_chi_square=1.2345, + fitting_time=0.9876, + ) + + y_obs = [10.0, 20.0] + y_calc = [9.5, 19.5] + y_err = [1.0, 1.0] + f_obs = [5.0, 6.0] + f_calc = [5.1, 5.9] + + fr.display_results(y_obs=y_obs, y_calc=y_calc, y_err=y_err, f_obs=f_obs, f_calc=f_calc) + + # Assert: key lines printed and a table rendered + out = capsys.readouterr().out + assert 'Fit results' in out + assert 'Success: True' in out + assert 'reduced χ²' in out + assert 'R-factor (Rf)' in out + assert 'R-factor squared (Rf²)' in out + assert 'Weighted R-factor (wR)' in out + assert 'Bragg R-factor (BR)' in out + assert 'Fitted parameters:' in out + # Table border from tabulate fancy_outline + assert '╒' in out or '+' in out diff --git a/tests/unit/easydiffraction/analysis/fit_helpers/test_tracking.py b/tests/unit/easydiffraction/analysis/fit_helpers/test_tracking.py new file mode 100644 index 00000000..2c9495c1 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/fit_helpers/test_tracking.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_module_import(): + import easydiffraction.analysis.fit_helpers.tracking as MUT + + expected_module_name = 'easydiffraction.analysis.fit_helpers.tracking' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_tracker_terminal_flow_prints_and_updates_best(monkeypatch, capsys): + import easydiffraction.analysis.fit_helpers.tracking as tracking_mod + from easydiffraction.analysis.fit_helpers.tracking import FitProgressTracker + + # Force terminal branch (not notebook) + monkeypatch.setattr(tracking_mod, 'is_notebook', lambda: False) + + tracker = FitProgressTracker() + tracker.start_tracking('dummy') + tracker.start_timer() + + # First iteration sets previous and best + res1 = np.array([2.0, 1.0]) # chi2 = 5, dof depends on num params but relative change only + tracker.track(res1, parameters=[1]) + + # Second iteration small change below threshold -> no row emitted + out1 = capsys.readouterr().out + assert 'Goodness-of-fit' in out1 + + res2 = np.array([1.9, 1.0]) + tracker.track(res2, parameters=[1]) + + # Third iteration large improvement -> row emitted + res3 = np.array([0.1, 0.1]) + tracker.track(res3, parameters=[1]) + + tracker.stop_timer() + tracker.finish_tracking() + out2 = capsys.readouterr().out + assert 'Best goodness-of-fit' in out2 + assert tracker.best_iteration is not None diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_base.py b/tests/unit/easydiffraction/analysis/minimizers/test_base.py new file mode 100644 index 00000000..d608bdc2 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/minimizers/test_base.py @@ -0,0 +1,107 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_module_import(): + import easydiffraction.analysis.minimizers.base as MUT + + assert MUT.__name__ == 'easydiffraction.analysis.minimizers.base' + + +def test_minimizer_base_fit_flow_and_finalize(): + from easydiffraction.analysis.minimizers.base import MinimizerBase + + class DummyParam: + def __init__(self, v): + self.value = v + + class DummyResult: + def __init__(self, success=True): + self.success = success + + class DummyMinimizer(MinimizerBase): + def __init__(self): + super().__init__(name='dummy', method='m', max_iterations=5) + self.synced = False + + def _prepare_solver_args(self, parameters): + # Make sure parameters list is received + assert isinstance(parameters, list) + return {'engine_parameters': {'ok': True}} + + def _run_solver(self, objective_function, **kwargs): + # Exercise calling of the provided objective + residuals = objective_function(kwargs.get('engine_parameters')) + # Update tracker so finish_tracking has valid metrics + self.tracker.track(residuals=np.array(residuals), parameters=[1]) + return DummyResult(success=True) + + def _sync_result_to_parameters(self, parameters, raw_result): + # Mark that syncing happened and tweak a parameter + self.synced = True + if parameters: + parameters[0].value = 42 + + def _check_success(self, raw_result): + return getattr(raw_result, 'success', False) + + # Provide residuals implementation used by _objective_function + def _compute_residuals( + self, engine_params, parameters, sample_models, experiments, calculator + ): + # Minimal residuals; verify engine params passed through + assert engine_params == {'ok': True} + return np.array([0.0, 0.0]) + + minim = DummyMinimizer() + + params = [DummyParam(1.0), DummyParam(2.0)] + + # Wrap minimizer's objective creator to simulate higher-level usage + objective = minim._create_objective_function( + parameters=params, + sample_models=None, + experiments=None, + calculator=None, + ) + + result = minim.fit(parameters=params, objective_function=objective) + + # Assertions: finalize populated, sync occurred, tracker captured time + assert result.success is True + assert minim.synced is True + assert isinstance(result.parameters, list) and result.parameters[0].value == 42 + # Fitting time should be a positive float + assert minim.tracker.fitting_time is not None and minim.tracker.fitting_time >= 0.0 + + +def test_minimizer_base_create_objective_function_uses_compute_residuals(): + from easydiffraction.analysis.minimizers.base import MinimizerBase + + class M(MinimizerBase): + def _prepare_solver_args(self, parameters): + return {} + + def _run_solver(self, objective_function, **kwargs): + return None + + def _sync_result_to_parameters(self, parameters, raw_result): + pass + + def _check_success(self, raw_result): + return True + + def _compute_residuals( + self, engine_params, parameters, sample_models, experiments, calculator + ): + # Return a deterministic vector to assert against + return np.array([1.0, 2.0, 3.0]) + + m = M() + f = m._create_objective_function( + parameters=[], sample_models=None, experiments=None, calculator=None + ) + out = f({}) + assert np.allclose(out, np.array([1.0, 2.0, 3.0])) diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py b/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py new file mode 100644 index 00000000..b3bb4e03 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_module_import(): + import easydiffraction.analysis.minimizers.dfols as MUT + + assert MUT.__name__ == 'easydiffraction.analysis.minimizers.dfols' + + +def test_dfols_prepare_run_and_sync(monkeypatch): + from easydiffraction.analysis.minimizers.dfols import DfolsMinimizer + + class P: + def __init__(self, v, lo=-np.inf, hi=np.inf): + self.value = v + self.fit_min = lo + self.fit_max = hi + self.uncertainty = None + + class FakeRes: + EXIT_SUCCESS = 0 + + def __init__(self): + self.x = np.array([3.0, 4.0]) + self.flag = 0 + + # Patch dfols.solve to return our FakeRes + import easydiffraction.analysis.minimizers.dfols as mod + + def fake_solve(fun, x0, bounds, maxfun): + # Verify we pass reasonable arguments + assert isinstance(x0, np.ndarray) and x0.shape[0] == 2 + assert isinstance(bounds, tuple) and all(isinstance(b, np.ndarray) for b in bounds) + return FakeRes() + + monkeypatch.setattr(mod, 'solve', fake_solve) + + minim = DfolsMinimizer(max_iterations=10) + params = [P(1.0, lo=0.0, hi=5.0), P(2.0, lo=1.0, hi=6.0)] + kwargs = minim._prepare_solver_args(params) + assert set(kwargs.keys()) == {'x0', 'bounds'} + res = minim._run_solver(lambda p: np.array([0.0]), **kwargs) + # Sync back values and check success flag handling + minim._sync_result_to_parameters(params, res) + assert params[0].value == 3.0 and params[1].value == 4.0 + assert params[0].uncertainty is None and params[1].uncertainty is None + assert minim._check_success(res) is True diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_factory.py b/tests/unit/easydiffraction/analysis/minimizers/test_factory.py new file mode 100644 index 00000000..a429342d --- /dev/null +++ b/tests/unit/easydiffraction/analysis/minimizers/test_factory.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_minimizer_factory_list_and_show(capsys): + from easydiffraction.analysis.minimizers.factory import MinimizerFactory + + lst = MinimizerFactory.list_available_minimizers() + assert isinstance(lst, list) and len(lst) >= 1 + MinimizerFactory.show_available_minimizers() + out = capsys.readouterr().out + assert 'Supported minimizers' in out + + +def test_minimizer_factory_unknown_raises(): + from easydiffraction.analysis.minimizers.factory import MinimizerFactory + + try: + MinimizerFactory.create_minimizer('___unknown___') + except ValueError as e: + assert 'Unknown minimizer' in str(e) + else: + assert False, 'Expected ValueError' + + +def test_minimizer_factory_create_known_and_register(monkeypatch): + from easydiffraction.analysis.minimizers.base import MinimizerBase + from easydiffraction.analysis.minimizers.factory import MinimizerFactory + + # Create a known minimizer instance (lmfit (leastsq) exists) + m = MinimizerFactory.create_minimizer('lmfit (leastsq)') + assert isinstance(m, MinimizerBase) + + # Register a custom minimizer and create it + class Custom(MinimizerBase): + def _prepare_solver_args(self, parameters): + return {} + + def _run_solver(self, objective_function, **kwargs): + return None + + def _sync_result_to_parameters(self, raw_result, parameters): + pass + + def _check_success(self, raw_result): + return True + + MinimizerFactory.register_minimizer( + name='custom-test', minimizer_cls=Custom, method=None, description='x' + ) + created = MinimizerFactory.create_minimizer('custom-test') + assert isinstance(created, Custom) diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py b/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py new file mode 100644 index 00000000..e7322bcd --- /dev/null +++ b/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import types + +import numpy as np + + +def test_module_import(): + import easydiffraction.analysis.minimizers.lmfit as MUT + + assert MUT.__name__ == 'easydiffraction.analysis.minimizers.lmfit' + + +def test_lmfit_prepare_and_sync(monkeypatch): + from easydiffraction.analysis.minimizers.lmfit import LmfitMinimizer + + class P: + def __init__(self, name, value, free=True, lo=-np.inf, hi=np.inf): + self._minimizer_uid = name + self._value = value + self.free = free + self.fit_min = lo + self.fit_max = hi + self.uncertainty = None + + @property + def value(self): + return self._value + + @value.setter + def value(self, v): + self._value = v + + # Fake lmfit.Parameters and result structure + class FakeParam: + def __init__(self, value, stderr=None): + self.value = value + self.stderr = stderr + + class FakeParams(dict): + def add(self, name, value, vary, min, max): + self[name] = types.SimpleNamespace(value=value, vary=vary, min=min, max=max) + + class FakeResult: + def __init__(self): + self.params = {'p1': FakeParam(10.0, stderr=0.5), 'p2': FakeParam(20.0, stderr=1.0)} + self.success = True + + # Monkeypatch lmfit in module namespace + import easydiffraction.analysis.minimizers.lmfit as lm + + monkeypatch.setattr( + lm, + 'lmfit', + types.SimpleNamespace(Parameters=FakeParams, minimize=lambda *a, **k: FakeResult()), + ) + + minim = LmfitMinimizer() + params = [P('p1', 1.0), P('p2', 2.0)] + + # Prepare + kwargs = minim._prepare_solver_args(params) + assert isinstance(kwargs.get('engine_parameters'), FakeParams) + # Run solver calls our fake minimize and returns FakeResult + res = minim._run_solver(lambda *a, **k: np.array([0.0]), **kwargs) + assert isinstance(res, FakeResult) + + # Sync back updates parameter values and uncertainties + minim._sync_result_to_parameters(params, res) + assert params[0].value == 10.0 and params[0].uncertainty == 0.5 + assert params[1].value == 20.0 and params[1].uncertainty == 1.0 + assert minim._check_success(res) is True diff --git a/tests/unit/easydiffraction/analysis/test_analysis.py b/tests/unit/easydiffraction/analysis/test_analysis.py new file mode 100644 index 00000000..53a2b796 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/test_analysis.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.analysis.analysis as MUT + + expected_module_name = 'easydiffraction.analysis.analysis' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def _make_project_with_names(names): + class ExpCol: + def __init__(self, names): + self._names = names + + @property + def names(self): + return self._names + + class P: + experiments = ExpCol(names) + sample_models = object() + _varname = 'proj' + + return P() + + +def test_show_current_calculator_and_minimizer_prints(capsys): + from easydiffraction.analysis.analysis import Analysis + + a = Analysis(project=_make_project_with_names([])) + a.show_current_calculator() + a.show_current_minimizer() + out = capsys.readouterr().out + assert 'Current calculator' in out + assert 'cryspy' in out + assert 'Current minimizer' in out + assert 'lmfit (leastsq)' in out + + +def test_current_calculator_setter_success_and_unknown(monkeypatch, capsys): + from easydiffraction.analysis import calculators as calc_pkg + from easydiffraction.analysis.analysis import Analysis + + a = Analysis(project=_make_project_with_names([])) + + # Success path + monkeypatch.setattr( + calc_pkg.factory.CalculatorFactory, + 'create_calculator', + lambda name: object(), + ) + a.current_calculator = 'pdffit' + out = capsys.readouterr().out + assert 'Current calculator changed to' in out + assert a.current_calculator == 'pdffit' + + # Unknown path (create_calculator returns None): no change + monkeypatch.setattr( + calc_pkg.factory.CalculatorFactory, + 'create_calculator', + lambda name: None, + ) + a.current_calculator = 'unknown' + assert a.current_calculator == 'pdffit' + + +def test_fit_modes_show_and_switch_to_joint(monkeypatch, capsys): + from easydiffraction.analysis.analysis import Analysis + + a = Analysis(project=_make_project_with_names(['e1', 'e2'])) + + a.show_available_fit_modes() + a.show_current_fit_mode() + out1 = capsys.readouterr().out + assert 'Available fit modes' in out1 + assert 'Current fit mode' in out1 + assert 'single' in out1 + + a.fit_mode = 'joint' + out2 = capsys.readouterr().out + assert 'Current fit mode changed to' in out2 + assert a.fit_mode == 'joint' diff --git a/tests/unit/easydiffraction/analysis/test_analysis_access_params.py b/tests/unit/easydiffraction/analysis/test_analysis_access_params.py new file mode 100644 index 00000000..9fb8bc63 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/test_analysis_access_params.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_how_to_access_parameters_prints_paths_and_uids(capsys): + from easydiffraction.analysis.analysis import Analysis + from easydiffraction.core.parameters import Parameter + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + # Build two parameters with identity metadata set directly + def make_param(db, cat, entry, name, val): + p = Parameter( + name=name, + value_spec=AttributeSpec(value=val, type_=DataTypes.NUMERIC, default=0.0), + cif_handler=CifHandler(names=[f'_{cat}.{name}']), + ) + # Inject identity metadata (avoid parent chain) + p._identity.datablock_entry_name = lambda: db + p._identity.category_code = cat + if entry: + p._identity.category_entry_name = lambda: entry + else: + p._identity.category_entry_name = lambda: '' + return p + + p1 = make_param('db1', 'catA', '', 'alpha', 1.0) + p2 = make_param('db2', 'catB', 'row1', 'beta', 2.0) + + class Coll: + def __init__(self, params): + self.parameters = params + + class Project: + _varname = 'proj' + + def __init__(self): + self.sample_models = Coll([p1]) + self.experiments = Coll([p2]) + + a = Analysis(Project()) + a.how_to_access_parameters() + out = capsys.readouterr().out + assert 'How to access parameters' in out + # Expect code path strings + assert "proj.sample_models['db1'].catA.alpha" in out + assert "proj.experiments['db2'].catB['row1'].beta" in out + # Expect CIF uid (owner.unique_name) present for both + assert 'db1.catA.alpha' in out + assert 'db2.catB.row1.beta' in out diff --git a/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py b/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py new file mode 100644 index 00000000..473437b2 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_show_params_empty_branches(capsys): + from easydiffraction.analysis.analysis import Analysis + + class Empty: + @property + def parameters(self): + return [] + + @property + def fittable_parameters(self): + return [] + + @property + def free_parameters(self): + return [] + + class P: + sample_models = Empty() + experiments = Empty() + _varname = 'proj' + + a = Analysis(project=P()) + + # show_all_params -> warning path + a.show_all_params() + # show_fittable_params -> warning path + a.show_fittable_params() + # show_free_params -> warning path + a.show_free_params() + + out = capsys.readouterr().out + assert ( + 'No parameters found' in out + or 'No fittable parameters' in out + or 'No free parameters' in out + ) diff --git a/tests/unit/easydiffraction/analysis/test_calculation.py b/tests/unit/easydiffraction/analysis/test_calculation.py new file mode 100644 index 00000000..7e55f26f --- /dev/null +++ b/tests/unit/easydiffraction/analysis/test_calculation.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.analysis.calculation as MUT + + expected_module_name = 'easydiffraction.analysis.calculation' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_calculator_wrapper_set_and_calls(monkeypatch): + from easydiffraction.analysis.calculation import Calculator + + calls = {} + + class DummyCalc: + def calculate_structure_factors(self, sm, exps): + calls['sf'] = True + return ['hkl'] + + def calculate_pattern(self, sm, expt): + calls['pat'] = True + + class DummyFactory: + def create_calculator(self, engine): + calls['engine'] = engine + return DummyCalc() + + c = Calculator(engine='cryspy') + # Inject dummy factory + c.calculator_factory = DummyFactory() + c.set_calculator('pdffit') + assert calls['engine'] == 'pdffit' + + # Call delegates + assert c.calculate_structure_factors('sm', 'exps') == ['hkl'] + c.calculate_pattern('sm', 'expt') + assert calls['sf'] and calls['pat'] diff --git a/tests/unit/easydiffraction/analysis/test_fitting.py b/tests/unit/easydiffraction/analysis/test_fitting.py new file mode 100644 index 00000000..95ed171c --- /dev/null +++ b/tests/unit/easydiffraction/analysis/test_fitting.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.analysis.fitting as MUT + + expected_module_name = 'easydiffraction.analysis.fitting' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_fitter_early_exit_when_no_params(capsys, monkeypatch): + from easydiffraction.analysis.fitting import Fitter + + class DummyCollection: + free_parameters = [] + + def __init__(self): + self._names = ['e1'] + + @property + def names(self): + return self._names + + class DummyMin: + tracker = type('T', (), {'track': staticmethod(lambda a, b: a)})() + + def fit(self, params, obj): + return None + + f = Fitter() + # Avoid creating a real minimizer + f.minimizer = DummyMin() + f.fit(sample_models=DummyCollection(), experiments=DummyCollection(), calculator=object()) + out = capsys.readouterr().out + assert 'No parameters selected for fitting' in out diff --git a/tests/unit/easydiffraction/core/test_category.py b/tests/unit/easydiffraction/core/test_category.py new file mode 100644 index 00000000..6d4ae695 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_category.py @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.io.cif.handler import CifHandler + + +class SimpleItem(CategoryItem): + def __init__(self, entry_name): + super().__init__() + self._identity.category_code = 'simple' + self._identity.category_entry_name = entry_name + object.__setattr__( + self, + '_a', + StringDescriptor( + name='a', + description='', + value_spec=AttributeSpec(value='x', type_=DataTypes.STRING, default=''), + cif_handler=CifHandler(names=['_simple.a']), + ), + ) + object.__setattr__( + self, + '_b', + StringDescriptor( + name='b', + description='', + value_spec=AttributeSpec(value='y', type_=DataTypes.STRING, default=''), + cif_handler=CifHandler(names=['_simple.b']), + ), + ) + + @property + def a(self): + return self._a + + @property + def b(self): + return self._b + + +class SimpleCollection(CategoryCollection): + def __init__(self): + super().__init__(item_type=SimpleItem) + + +def test_category_item_str_and_properties(): + it = SimpleItem('name1') + s = str(it) + assert '<' in s and 'a=' in s and 'b=' in s + assert it.unique_name.endswith('.simple.name1') or it.unique_name == 'simple.name1' + assert len(it.parameters) == 2 + + +def test_category_collection_str_and_cif_calls(): + c = SimpleCollection() + c.add(SimpleItem('n1')) + c.add(SimpleItem('n2')) + s = str(c) + assert 'collection' in s and '2 items' in s + # as_cif delegates to serializer; should be a string (possibly empty) + assert isinstance(c.as_cif, str) diff --git a/tests/unit/easydiffraction/core/test_collection.py b/tests/unit/easydiffraction/core/test_collection.py new file mode 100644 index 00000000..b8388f68 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_collection.py @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction 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 + + class Item: + def __init__(self, name): + self._identity = Identity(owner=self, category_entry=lambda: name) + + class MyCollection(CollectionBase): + @property + def parameters(self): + return [] + + @property + def as_cif(self) -> str: + return '' + + c = MyCollection(item_type=Item) + a = Item('a') + b = Item('b') + c['a'] = a + c['b'] = b + assert c['a'] is a and c['b'] is b + a2 = Item('a') + c['a'] = a2 + assert c['a'] is a2 and len(list(c.keys())) == 2 + del c['b'] + assert list(c.names) == ['a'] diff --git a/tests/unit/easydiffraction/core/test_datablock.py b/tests/unit/easydiffraction/core/test_datablock.py new file mode 100644 index 00000000..7e227c22 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_datablock.py @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction 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.parameters import Parameter + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + class Cat(CategoryItem): + def __init__(self): + super().__init__() + self._identity.category_code = 'cat' + self._identity.category_entry_name = 'e1' + # real Parameters + self._p1 = Parameter( + name='p1', + description='', + value_spec=AttributeSpec(value=1.0, type_=DataTypes.NUMERIC, default=0.0), + units='', + cif_handler=CifHandler(names=['_cat.p1']), + ) + self._p2 = Parameter( + name='p2', + description='', + value_spec=AttributeSpec(value=2.0, type_=DataTypes.NUMERIC, default=0.0), + units='', + cif_handler=CifHandler(names=['_cat.p2']), + ) + # Make p2 constrained and not free + self._p2._constrained = True + self._p2._free = False + # Mark p1 free to be included in free_parameters + self._p1.free = True + + @property + def p1(self): + return self._p1 + + @property + def p2(self): + return self._p2 + + class Block(DatablockItem): + def __init__(self, name): + super().__init__() + # set datablock entry name + self._identity.datablock_entry_name = lambda: name + # include the category as attribute so DatablockItem.parameters picks them up + self._cat = Cat() + + @property + def cat(self): + return self._cat + + coll = DatablockCollection(item_type=Block) + a = Block('A') + b = Block('B') + coll.add(a) + coll.add(b) + # parameters collection aggregates from both blocks (p1 & p2 each) + params = coll.parameters + assert len(params) == 4 + # fittable excludes constrained parameters + fittable = coll.fittable_parameters + assert all(isinstance(p, Parameter) for p in fittable) + assert len(fittable) == 2 # only p1 from each block + # free is subset of fittable where free=True (true for p1) + free_params = coll.free_parameters + assert free_params == fittable diff --git a/tests/unit/easydiffraction/core/test_diagnostic.py b/tests/unit/easydiffraction/core/test_diagnostic.py new file mode 100644 index 00000000..ffb11457 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_diagnostic.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + +from easydiffraction.core.diagnostic import Diagnostics + + +class DummyLogger: + def __init__(self): + self.last = None + + def error(self, message, exc_type): + self.last = ('error', message, exc_type) + + def debug(self, message): + self.last = ('debug', message) + + +def test_diagnostics_error_and_debug_monkeypatch(monkeypatch: pytest.MonkeyPatch): + dummy = DummyLogger() + # Patch module-level log used by Diagnostics + import easydiffraction.core.diagnostic as diag_mod + + monkeypatch.setattr(diag_mod, 'log', dummy, raising=True) + + Diagnostics.no_value('x', default=1) + assert dummy.last[0] == 'debug' + + Diagnostics.type_mismatch('x', value=3, expected_type=int) + kind, msg, exc = dummy.last + assert kind == 'error' + assert issubclass(exc, TypeError) diff --git a/tests/unit/easydiffraction/core/test_factory.py b/tests/unit/easydiffraction/core/test_factory.py new file mode 100644 index 00000000..040232ca --- /dev/null +++ b/tests/unit/easydiffraction/core/test_factory.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_module_import(): + import easydiffraction.core.factory as MUT + + expected_module_name = 'easydiffraction.core.factory' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_validate_args_valid_and_invalid(): + import easydiffraction.core.factory as MUT + + specs = [ + {'required': ['a'], 'optional': ['b']}, + {'required': ['x', 'y'], 'optional': []}, + ] + # valid: only required + MUT.FactoryBase._validate_args({'a'}, specs, 'Thing') + # valid: required + optional subset + MUT.FactoryBase._validate_args({'a', 'b'}, specs, 'Thing') + MUT.FactoryBase._validate_args({'x', 'y'}, specs, 'Thing') + # invalid: unknown key + with pytest.raises(ValueError): + MUT.FactoryBase._validate_args({'a', 'c'}, specs, 'Thing') diff --git a/tests/unit/easydiffraction/core/test_guard.py b/tests/unit/easydiffraction/core/test_guard.py new file mode 100644 index 00000000..9f9e9a90 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_guard.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_guard_allows_only_declared_public_properties_and_links_parent(monkeypatch): + from easydiffraction.core.guard import GuardedBase + + class Child(GuardedBase): + @property + def parameters(self): + return [] + + @property + def as_cif(self) -> str: + return '' + + @property + def value(self): + return getattr(self, '_value', 0) + + @value.setter + def value(self, v): + self._assign_attr('_value', v) + + class Parent(GuardedBase): + def __init__(self): + super().__init__() + self._child = Child() + + @property + def child(self): + return self._child + + @property + def parameters(self): + return [] + + @property + def as_cif(self) -> str: + return '' + + p = Parent() + # Writable property on child should set and link parent + p.child.value = 3 + assert p.child.value == 3 + # Private assign links parent automatically + assert p.child._parent is p + + # Unknown attribute should raise AttributeError under current logging mode + with pytest.raises(AttributeError): + p.child.unknown_attr = 1 diff --git a/tests/unit/easydiffraction/core/test_identity.py b/tests/unit/easydiffraction/core/test_identity.py new file mode 100644 index 00000000..1093e872 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_identity.py @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_identity_direct_and_parent_resolution(): + from easydiffraction.core.identity import Identity + + class Node: + def __init__(self, name=None, parent=None): + self._identity = Identity(owner=self, category_code=name) + if parent is not None: + self._parent = parent + + parent = Node(name='cat') + child = Node(parent=parent) + assert parent._identity.category_code == 'cat' + assert child._identity.category_code == 'cat' + + +def test_identity_cycle_safe_resolution(): + from easydiffraction.core.identity import Identity + + class Node: + def __init__(self): + self._identity = Identity(owner=self) + + a = Node() + b = Node() + a._parent = b + b._parent = a + assert a._identity.category_code is None diff --git a/tests/unit/easydiffraction/core/test_parameters.py b/tests/unit/easydiffraction/core/test_parameters.py new file mode 100644 index 00000000..dc5afd15 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_parameters.py @@ -0,0 +1,100 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np +import pytest + + +def test_module_import(): + import easydiffraction.core.parameters as MUT + + assert MUT.__name__ == 'easydiffraction.core.parameters' + + +def test_string_descriptor_type_override_raises_type_error(): + # Creating a StringDescriptor with a NUMERIC spec should raise via Diagnostics + from easydiffraction.core.parameters import StringDescriptor + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + with pytest.raises(TypeError): + StringDescriptor( + name='title', + value_spec=AttributeSpec(value='abc', type_=DataTypes.NUMERIC, default='x'), + description='Title text', + cif_handler=CifHandler(names=['_proj.title']), + ) + + +def test_numeric_descriptor_str_includes_units(): + from easydiffraction.core.parameters import NumericDescriptor + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + d = NumericDescriptor( + name='w', + value_spec=AttributeSpec(value=1.23, type_=DataTypes.NUMERIC, default=0.0), + units='deg', + cif_handler=CifHandler(names=['_x.w']), + ) + s = str(d) + assert s.startswith('<') and s.endswith('>') and 'deg' in s and 'w' in s + + +def test_parameter_string_repr_and_as_cif_and_flags(): + from easydiffraction.core.parameters import Parameter + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + p = Parameter( + name='a', + value_spec=AttributeSpec(value=2.5, type_=DataTypes.NUMERIC, default=0.0), + units='A', + cif_handler=CifHandler(names=['_param.a']), + ) + # Update extra attributes + p.uncertainty = 0.1 + p.free = True + + s = str(p) + assert '± 0.1' in s and 'A' in s and '(free=True)' in s + + # CIF line is ` ` + assert p.as_cif == '_param.a 2.5' + + # CifHandler uid is owner's unique_name (parameter name here) + assert p._cif_handler.uid == p.unique_name == 'a' + + +def test_parameter_uncertainty_must_be_non_negative(): + from easydiffraction.core.parameters import Parameter + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + p = Parameter( + name='b', + value_spec=AttributeSpec(value=1.0, type_=DataTypes.NUMERIC, default=0.0), + cif_handler=CifHandler(names=['_param.b']), + ) + with pytest.raises(TypeError): + p.uncertainty = -0.5 + + +def test_parameter_fit_bounds_assign_and_read(): + from easydiffraction.core.parameters import Parameter + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + p = Parameter( + name='c', + value_spec=AttributeSpec(value=0.0, type_=DataTypes.NUMERIC, default=0.0), + cif_handler=CifHandler(names=['_param.c']), + ) + p.fit_min = -1.0 + p.fit_max = 10.0 + assert np.isclose(p.fit_min, -1.0) and np.isclose(p.fit_max, 10.0) diff --git a/tests/unit/easydiffraction/core/test_singletons.py b/tests/unit/easydiffraction/core/test_singletons.py new file mode 100644 index 00000000..b798c345 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_singletons.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_uid_map_handler_singleton_and_add_and_replace_uid(): + from easydiffraction.core.parameters import NumericDescriptor + from easydiffraction.core.singletons import UidMapHandler + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + h1 = UidMapHandler.get() + h2 = UidMapHandler.get() + assert h1 is h2 + + # Clean slate for test + h1.get_uid_map().clear() + + d = NumericDescriptor( + name='p', + value_spec=AttributeSpec(value=1.0, type_=DataTypes.NUMERIC, default=0.0), + cif_handler=CifHandler(names=['_x.p']), + ) + h1.add_to_uid_map(d) + assert d.uid in h1.get_uid_map() + + # replace_uid: bad key + with pytest.raises(KeyError): + h1.replace_uid('missing', 'new') + + # replace_uid: success path + old_uid = d.uid + new_uid = old_uid + 'x' + h1.replace_uid(old_uid, new_uid) + assert new_uid in h1.get_uid_map() and old_uid not in h1.get_uid_map() + + +def test_uid_map_handler_rejects_non_descriptor(): + from easydiffraction.core.singletons import UidMapHandler + + h = UidMapHandler.get() + with pytest.raises(TypeError): + h.add_to_uid_map(object()) diff --git a/tests/unit/easydiffraction/core/test_validation.py b/tests/unit/easydiffraction/core/test_validation.py new file mode 100644 index 00000000..4a07121a --- /dev/null +++ b/tests/unit/easydiffraction/core/test_validation.py @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.core.validation as MUT + + expected_module_name = 'easydiffraction.core.validation' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_type_validator_accepts_and_rejects(monkeypatch): + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.utils.logging import log + + # So that errors do not raise in test process + log.configure(reaction=log.Reaction.WARN) + + spec = AttributeSpec(type_=DataTypes.STRING, default='abc') + # valid + expected = 'xyz' + actual = spec.validated('xyz', name='p') + assert expected == actual + # invalid -> fallback to default + expected_fallback = 'abc' + actual_fallback = spec.validated(10, name='p') + assert expected_fallback == actual_fallback + + +def test_range_validator_bounds(monkeypatch): + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.core.validation import RangeValidator + from easydiffraction.utils.logging import log + + log.configure(reaction=log.Reaction.WARN) + spec = AttributeSpec( + type_=DataTypes.NUMERIC, default=1.0, content_validator=RangeValidator(ge=0, le=2) + ) + # inside range + expected = 1.5 + actual = spec.validated(1.5, name='p') + assert expected == actual + # outside -> fallback default + expected_fallback = 1.0 + actual_fallback = spec.validated(5.0, name='p') + assert expected_fallback == actual_fallback + + +def test_membership_and_regex_validators(monkeypatch): + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import MembershipValidator + from easydiffraction.core.validation import RegexValidator + from easydiffraction.utils.logging import log + + log.configure(reaction=log.Reaction.WARN) + mspec = AttributeSpec(default='b', content_validator=MembershipValidator(['a', 'b'])) + assert mspec.validated('a', name='m') == 'a' + # reject -> fallback default + assert mspec.validated('c', name='m') == 'b' + + rspec = AttributeSpec(default='a1', content_validator=RegexValidator(r'^[a-z]\d$')) + assert rspec.validated('b2', name='r') == 'b2' + assert rspec.validated('BAD', name='r') == 'a1' diff --git a/tests/unit/easydiffraction/crystallography/test_crystallography.py b/tests/unit/easydiffraction/crystallography/test_crystallography.py new file mode 100644 index 00000000..46bb1538 --- /dev/null +++ b/tests/unit/easydiffraction/crystallography/test_crystallography.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.crystallography.crystallography as MUT + + expected_module_name = 'easydiffraction.crystallography.crystallography' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/crystallography/test_space_groups.py b/tests/unit/easydiffraction/crystallography/test_space_groups.py new file mode 100644 index 00000000..637212e3 --- /dev/null +++ b/tests/unit/easydiffraction/crystallography/test_space_groups.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.crystallography.space_groups as MUT + + expected_module_name = 'easydiffraction.crystallography.space_groups' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/experiments/categories/background/test_base.py b/tests/unit/easydiffraction/experiments/categories/background/test_base.py new file mode 100644 index 00000000..72b656db --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/background/test_base.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_background_base_minimal_impl_and_collection_cif(): + from easydiffraction.core.category import CategoryItem + from easydiffraction.core.collection import CollectionBase + from easydiffraction.core.parameters import Parameter + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.experiments.categories.background.base import BackgroundBase + from easydiffraction.io.cif.handler import CifHandler + + class ConstantBackground(CategoryItem): + def __init__(self, name: str, value: float): + # CategoryItem doesn't define __init__; call GuardedBase via super() + super().__init__() + self._identity.category_code = 'background' + self._identity.category_entry_name = name + self._level = Parameter( + name='level', + value_spec=AttributeSpec(value=value, type_=DataTypes.NUMERIC, default=0.0), + cif_handler=CifHandler(names=['_bkg.level']), + ) + + def calculate(self, x_data): + return np.full_like(np.asarray(x_data), fill_value=self._level.value, dtype=float) + + def show(self): + # No-op for tests + return None + + class BackgroundCollection(BackgroundBase): + def __init__(self): + # Initialize underlying collection with the item type + CollectionBase.__init__(self, item_type=ConstantBackground) + + def calculate(self, x_data): + x = np.asarray(x_data) + total = np.zeros_like(x, dtype=float) + for item in self.values(): + total += item.calculate(x) + return total + + def show(self) -> None: # pragma: no cover - trivial + return None + + coll = BackgroundCollection() + a = ConstantBackground('a', 1.0) + b = ConstantBackground('b', 2.0) + coll.add(a) + coll.add(b) + + # calculate sums two backgrounds externally (out of scope), here just verify item.calculate + x = np.array([0.0, 1.0, 2.0]) + assert np.allclose(a.calculate(x), [1.0, 1.0, 1.0]) + + # CIF of collection is loop with header tag and two rows + cif = coll.as_cif + assert 'loop_' in cif and '_bkg.level' in cif and '1.0' in cif and '2.0' in cif diff --git a/tests/unit/easydiffraction/experiments/categories/background/test_chebyshev.py b/tests/unit/easydiffraction/experiments/categories/background/test_chebyshev.py new file mode 100644 index 00000000..81c608a5 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/background/test_chebyshev.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_chebyshev_background_calculate_and_cif(): + from easydiffraction.experiments.categories.background.chebyshev import ( + ChebyshevPolynomialBackground, + ) + from easydiffraction.experiments.categories.background.chebyshev import PolynomialTerm + + cb = ChebyshevPolynomialBackground() + x = np.linspace(0.0, 1.0, 5) + + # Empty background -> zeros + y0 = cb.calculate(x) + assert np.allclose(y0, 0.0) + + # Add two terms and verify CIF contains expected tags + t0 = PolynomialTerm(order=0, coef=1.0) + t1 = PolynomialTerm(order=1, coef=0.5) + cb.add(t0) + cb.add(t1) + cif = cb.as_cif + assert '_pd_background.Chebyshev_order' in cif and '_pd_background.Chebyshev_coef' in cif diff --git a/tests/unit/easydiffraction/experiments/categories/background/test_enums.py b/tests/unit/easydiffraction/experiments/categories/background/test_enums.py new file mode 100644 index 00000000..a7d3b757 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/background/test_enums.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_background_enum_default_and_descriptions(): + import easydiffraction.experiments.categories.background.enums as MUT + + assert MUT.BackgroundTypeEnum.default() == MUT.BackgroundTypeEnum.LINE_SEGMENT + assert ( + MUT.BackgroundTypeEnum.LINE_SEGMENT.description() == 'Linear interpolation between points' + ) + assert MUT.BackgroundTypeEnum.CHEBYSHEV.description() == 'Chebyshev polynomial background' diff --git a/tests/unit/easydiffraction/experiments/categories/background/test_factory.py b/tests/unit/easydiffraction/experiments/categories/background/test_factory.py new file mode 100644 index 00000000..0a282157 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/background/test_factory.py @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_background_factory_default_and_errors(): + from easydiffraction.experiments.categories.background.enums import BackgroundTypeEnum + from easydiffraction.experiments.categories.background.factory import BackgroundFactory + + # Default should produce a LineSegmentBackground + obj = BackgroundFactory.create() + assert obj.__class__.__name__.endswith('LineSegmentBackground') + + # Explicit type + obj2 = BackgroundFactory.create(BackgroundTypeEnum.CHEBYSHEV) + assert obj2.__class__.__name__.endswith('ChebyshevPolynomialBackground') + + # Unsupported enum (fake) should raise ValueError + class FakeEnum: + value = 'x' + + with pytest.raises(ValueError): + BackgroundFactory.create(FakeEnum) # type: ignore[arg-type] diff --git a/tests/unit/easydiffraction/experiments/categories/background/test_line_segment.py b/tests/unit/easydiffraction/experiments/categories/background/test_line_segment.py new file mode 100644 index 00000000..c9e9b68c --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/background/test_line_segment.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_line_segment_background_calculate_and_cif(): + from easydiffraction.experiments.categories.background.line_segment import LineSegment + from easydiffraction.experiments.categories.background.line_segment import ( + LineSegmentBackground, + ) + + bkg = LineSegmentBackground() + # No points -> zeros + x = np.array([0.0, 1.0, 2.0]) + y0 = bkg.calculate(x) + assert np.allclose(y0, [0.0, 0.0, 0.0]) + + # Add two points -> linear interpolation + p1 = LineSegment(x=0.0, y=0.0) + p2 = LineSegment(x=2.0, y=4.0) + bkg.add(p1) + bkg.add(p2) + y = bkg.calculate(x) + assert np.allclose(y, [0.0, 2.0, 4.0]) + + # CIF loop has correct header and rows + cif = bkg.as_cif + assert ( + 'loop_' in cif + and '_pd_background.line_segment_X' in cif + and '_pd_background.line_segment_intensity' in cif + ) diff --git a/tests/unit/easydiffraction/experiments/categories/instrument/test_base.py b/tests/unit/easydiffraction/experiments/categories/instrument/test_base.py new file mode 100644 index 00000000..52ec4d30 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/instrument/test_base.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_instrument_base_sets_category_code(): + from easydiffraction.experiments.categories.instrument.base import InstrumentBase + + class DummyInstr(InstrumentBase): + def __init__(self): + super().__init__() + + d = DummyInstr() + assert d._identity.category_code == 'instrument' diff --git a/tests/unit/easydiffraction/experiments/categories/instrument/test_cwl.py b/tests/unit/easydiffraction/experiments/categories/instrument/test_cwl.py new file mode 100644 index 00000000..9898df52 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/instrument/test_cwl.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.experiments.categories.instrument.cwl import CwlInstrument + + +def test_cwl_instrument_parameters_settable(): + instr = CwlInstrument() + instr.setup_wavelength = 2.0 + instr.calib_twotheta_offset = 0.1 + assert instr.setup_wavelength.value == 2.0 + assert instr.calib_twotheta_offset.value == 0.1 diff --git a/tests/unit/easydiffraction/experiments/categories/instrument/test_factory.py b/tests/unit/easydiffraction/experiments/categories/instrument/test_factory.py new file mode 100644 index 00000000..35023d8c --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/instrument/test_factory.py @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_instrument_factory_default_and_errors(): + try: + from easydiffraction.experiments.categories.instrument.factory import InstrumentFactory + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + except ImportError as e: # pragma: no cover - environment-specific circular import + pytest.skip(f'InstrumentFactory import triggers circular import in this context: {e}') + return + + inst = InstrumentFactory.create() # defaults + assert inst.__class__.__name__ in {'CwlInstrument', 'TofInstrument'} + + # Valid combinations + inst2 = InstrumentFactory.create(ScatteringTypeEnum.BRAGG, BeamModeEnum.CONSTANT_WAVELENGTH) + assert inst2.__class__.__name__ == 'CwlInstrument' + inst3 = InstrumentFactory.create(ScatteringTypeEnum.BRAGG, BeamModeEnum.TIME_OF_FLIGHT) + assert inst3.__class__.__name__ == 'TofInstrument' + + # Invalid scattering type + class FakeST: + pass + + with pytest.raises(ValueError): + InstrumentFactory.create(FakeST, BeamModeEnum.CONSTANT_WAVELENGTH) # type: ignore[arg-type] + + # Invalid beam mode + class FakeBM: + pass + + with pytest.raises(ValueError): + InstrumentFactory.create(ScatteringTypeEnum.BRAGG, FakeBM) # type: ignore[arg-type] diff --git a/tests/unit/easydiffraction/experiments/categories/instrument/test_tof.py b/tests/unit/easydiffraction/experiments/categories/instrument/test_tof.py new file mode 100644 index 00000000..73008da4 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/instrument/test_tof.py @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_tof_instrument_defaults_and_setters_and_parameters_and_cif(): + from easydiffraction.experiments.categories.instrument.tof import TofInstrument + + inst = TofInstrument() + + # Defaults + assert np.isclose(inst.setup_twotheta_bank.value, 150.0) + assert np.isclose(inst.calib_d_to_tof_offset.value, 0.0) + assert np.isclose(inst.calib_d_to_tof_linear.value, 10000.0) + assert np.isclose(inst.calib_d_to_tof_quad.value, -0.00001) + assert np.isclose(inst.calib_d_to_tof_recip.value, 0.0) + + # Setters + inst.setup_twotheta_bank = 160.0 + inst.calib_d_to_tof_offset = 1.0 + inst.calib_d_to_tof_linear = 9000.0 + inst.calib_d_to_tof_quad = -2e-5 + inst.calib_d_to_tof_recip = 0.5 + + assert np.isclose(inst.setup_twotheta_bank.value, 160.0) + assert np.isclose(inst.calib_d_to_tof_offset.value, 1.0) + assert np.isclose(inst.calib_d_to_tof_linear.value, 9000.0) + assert np.isclose(inst.calib_d_to_tof_quad.value, -2e-5) + assert np.isclose(inst.calib_d_to_tof_recip.value, 0.5) + + # Parameters exposure via CategoryItem.parameters + names = {p.name for p in inst.parameters} + assert { + 'twotheta_bank', + 'd_to_tof_offset', + 'd_to_tof_linear', + 'd_to_tof_quad', + 'd_to_tof_recip', + }.issubset(names) + + # CIF representation of the item should include tags in separate lines + cif = inst.as_cif + assert '_instr.2theta_bank' in cif and '_instr.d_to_tof_linear' in cif diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_base.py b/tests/unit/easydiffraction/experiments/categories/peak/test_base.py new file mode 100644 index 00000000..5499fc77 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_base.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.experiments.categories.peak.base import PeakBase + + +def test_peak_base_identity_code(): + class DummyPeak(PeakBase): + def __init__(self): + super().__init__() + + p = DummyPeak() + assert p._identity.category_code == 'peak' diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_cwl.py b/tests/unit/easydiffraction/experiments/categories/peak/test_cwl.py new file mode 100644 index 00000000..074e99f1 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_cwl.py @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_cwl_peak_classes_expose_expected_parameters_and_category(): + from easydiffraction.experiments.categories.peak.cwl import CwlPseudoVoigt + from easydiffraction.experiments.categories.peak.cwl import CwlSplitPseudoVoigt + from easydiffraction.experiments.categories.peak.cwl import CwlThompsonCoxHastings + + pv = CwlPseudoVoigt() + spv = CwlSplitPseudoVoigt() + tch = CwlThompsonCoxHastings() + + # Category code set by PeakBase + for obj in (pv, spv, tch): + assert obj._identity.category_code == 'peak' + + # Broadening parameters added by CwlBroadeningMixin + for obj in (pv, spv, tch): + names = {p.name for p in obj.parameters} + assert { + 'broad_gauss_u', + 'broad_gauss_v', + 'broad_gauss_w', + 'broad_lorentz_x', + 'broad_lorentz_y', + }.issubset(names) + + # EmpiricalAsymmetry added only for split PV + names_spv = {p.name for p in spv.parameters} + assert {'asym_empir_1', 'asym_empir_2', 'asym_empir_3', 'asym_empir_4'}.issubset(names_spv) + + # FCJ asymmetry for TCH + names_tch = {p.name for p in tch.parameters} + assert {'asym_fcj_1', 'asym_fcj_2'}.issubset(names_tch) diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_cwl_mixins.py b/tests/unit/easydiffraction/experiments/categories/peak/test_cwl_mixins.py new file mode 100644 index 00000000..99890e31 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_cwl_mixins.py @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.experiments.categories.peak.cwl import CwlPseudoVoigt +from easydiffraction.experiments.categories.peak.cwl import CwlSplitPseudoVoigt +from easydiffraction.experiments.categories.peak.cwl import CwlThompsonCoxHastings + + +def test_cwl_pseudo_voigt_params_exist_and_settable(): + peak = CwlPseudoVoigt() + # Created by _add_constant_wavelength_broadening + assert peak.broad_gauss_u.name == 'broad_gauss_u' + peak.broad_gauss_u = 0.123 + assert peak.broad_gauss_u.value == 0.123 + + +def test_cwl_split_pseudo_voigt_adds_empirical_asymmetry(): + peak = CwlSplitPseudoVoigt() + # Has broadening and empirical asymmetry params + assert peak.broad_gauss_w.name == 'broad_gauss_w' + assert peak.asym_empir_1.name == 'asym_empir_1' + peak.asym_empir_2 = 0.345 + assert peak.asym_empir_2.value == 0.345 + + +def test_cwl_tch_adds_fcj_asymmetry(): + peak = CwlThompsonCoxHastings() + assert peak.asym_fcj_1.name == 'asym_fcj_1' + peak.asym_fcj_2 = 0.456 + assert peak.asym_fcj_2.value == 0.456 diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_factory.py b/tests/unit/easydiffraction/experiments/categories/peak/test_factory.py new file mode 100644 index 00000000..c9f04490 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_factory.py @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_peak_factory_default_and_combinations_and_errors(): + from easydiffraction.experiments.categories.peak.factory import PeakFactory + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import PeakProfileTypeEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + + # Defaults -> valid object for default enums + p = PeakFactory.create() + assert p._identity.category_code == 'peak' + + # Explicit valid combos + p1 = PeakFactory.create( + ScatteringTypeEnum.BRAGG, + BeamModeEnum.CONSTANT_WAVELENGTH, + PeakProfileTypeEnum.PSEUDO_VOIGT, + ) + assert p1.__class__.__name__ == 'CwlPseudoVoigt' + p2 = PeakFactory.create( + ScatteringTypeEnum.BRAGG, + BeamModeEnum.TIME_OF_FLIGHT, + PeakProfileTypeEnum.PSEUDO_VOIGT_IKEDA_CARPENTER, + ) + assert p2.__class__.__name__ == 'TofPseudoVoigtIkedaCarpenter' + p3 = PeakFactory.create( + ScatteringTypeEnum.TOTAL, + BeamModeEnum.CONSTANT_WAVELENGTH, + PeakProfileTypeEnum.GAUSSIAN_DAMPED_SINC, + ) + assert p3.__class__.__name__ == 'TotalGaussianDampedSinc' + + # Invalid scattering type + class FakeST: + pass + + with pytest.raises(ValueError): + PeakFactory.create( + FakeST, BeamModeEnum.CONSTANT_WAVELENGTH, PeakProfileTypeEnum.PSEUDO_VOIGT + ) # type: ignore[arg-type] + + # Invalid beam mode + class FakeBM: + pass + + with pytest.raises(ValueError): + PeakFactory.create(ScatteringTypeEnum.BRAGG, FakeBM, PeakProfileTypeEnum.PSEUDO_VOIGT) # type: ignore[arg-type] + + # Invalid profile type + class FakePPT: + pass + + with pytest.raises(ValueError): + PeakFactory.create(ScatteringTypeEnum.BRAGG, BeamModeEnum.CONSTANT_WAVELENGTH, FakePPT) # type: ignore[arg-type] diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_tof.py b/tests/unit/easydiffraction/experiments/categories/peak/test_tof.py new file mode 100644 index 00000000..138c824c --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_tof.py @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.experiments.categories.peak.tof import TofPseudoVoigt +from easydiffraction.experiments.categories.peak.tof import TofPseudoVoigtBackToBack +from easydiffraction.experiments.categories.peak.tof import TofPseudoVoigtIkedaCarpenter + + +def test_tof_pseudo_voigt_has_broadening_params(): + peak = TofPseudoVoigt() + assert peak.broad_gauss_sigma_0.name == 'gauss_sigma_0' + peak.broad_gauss_sigma_2 = 1.23 + assert peak.broad_gauss_sigma_2.value == 1.23 + + +def test_tof_back_to_back_adds_ikeda_carpenter(): + peak = TofPseudoVoigtBackToBack() + assert peak.asym_alpha_0.name == 'asym_alpha_0' + peak.asym_alpha_1 = 0.77 + assert peak.asym_alpha_1.value == 0.77 + + +def test_tof_ikeda_carpenter_has_mix_beta(): + peak = TofPseudoVoigtIkedaCarpenter() + assert peak.broad_mix_beta_0.name == 'mix_beta_0' diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_tof_mixins.py b/tests/unit/easydiffraction/experiments/categories/peak/test_tof_mixins.py new file mode 100644 index 00000000..583b8ffa --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_tof_mixins.py @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_tof_broadening_and_asymmetry_mixins(): + from easydiffraction.experiments.categories.peak.base import PeakBase + from easydiffraction.experiments.categories.peak.tof_mixins import IkedaCarpenterAsymmetryMixin + from easydiffraction.experiments.categories.peak.tof_mixins import TofBroadeningMixin + + class TofPeak(PeakBase, TofBroadeningMixin, IkedaCarpenterAsymmetryMixin): + def __init__(self): + super().__init__() + self._add_time_of_flight_broadening() + self._add_ikeda_carpenter_asymmetry() + + p = TofPeak() + names = {param.name for param in p.parameters} + # Broadening + assert { + 'gauss_sigma_0', + 'gauss_sigma_1', + 'gauss_sigma_2', + 'lorentz_gamma_0', + 'lorentz_gamma_1', + 'lorentz_gamma_2', + 'mix_beta_0', + 'mix_beta_1', + }.issubset(names) + # Asymmetry + assert {'asym_alpha_0', 'asym_alpha_1'}.issubset(names) + + # Verify setters update values + p.broad_gauss_sigma_0 = 1.0 + p.asym_alpha_1 = 0.5 + assert np.isclose(p.broad_gauss_sigma_0.value, 1.0) + assert np.isclose(p.asym_alpha_1.value, 0.5) diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_total.py b/tests/unit/easydiffraction/experiments/categories/peak/test_total.py new file mode 100644 index 00000000..741018be --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_total.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_total_gaussian_damped_sinc_parameters_and_setters(): + from easydiffraction.experiments.categories.peak.total import TotalGaussianDampedSinc + + p = TotalGaussianDampedSinc() + assert p._identity.category_code == 'peak' + names = {param.name for param in p.parameters} + assert { + 'damp_q', + 'broad_q', + 'cutoff_q', + 'sharp_delta_1', + 'sharp_delta_2', + 'damp_particle_diameter', + }.issubset(names) + + # Setters update values + p.damp_q = 0.1 + p.broad_q = 0.2 + p.cutoff_q = 30.0 + p.sharp_delta_1 = 1.0 + p.sharp_delta_2 = 2.0 + p.damp_particle_diameter = 50.0 + + vals = {param.name: param.value for param in p.parameters} + assert np.isclose(vals['damp_q'], 0.1) + assert np.isclose(vals['broad_q'], 0.2) + assert np.isclose(vals['cutoff_q'], 30.0) + assert np.isclose(vals['sharp_delta_1'], 1.0) + assert np.isclose(vals['sharp_delta_2'], 2.0) + assert np.isclose(vals['damp_particle_diameter'], 50.0) diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_total_mixins.py b/tests/unit/easydiffraction/experiments/categories/peak/test_total_mixins.py new file mode 100644 index 00000000..c7cd6d01 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_total_mixins.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.experiments.categories.peak.total import TotalGaussianDampedSinc + + +def test_total_gaussian_damped_sinc_params(): + peak = TotalGaussianDampedSinc() + assert peak.damp_q.name == 'damp_q' + peak.damp_q = 0.12 + assert peak.damp_q.value == 0.12 + assert peak.broad_q.name == 'broad_q' diff --git a/tests/unit/easydiffraction/experiments/categories/test_excluded_regions.py b/tests/unit/easydiffraction/experiments/categories/test_excluded_regions.py new file mode 100644 index 00000000..536bf2d8 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/test_excluded_regions.py @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_excluded_regions_add_updates_datastore_and_cif(): + from types import SimpleNamespace + + from easydiffraction.experiments.categories.excluded_regions import ExcludedRegion + from easydiffraction.experiments.categories.excluded_regions import ExcludedRegions + + # Minimal fake datastore + full_x = np.array([0.0, 1.0, 2.0, 3.0]) + full_meas = np.array([10.0, 11.0, 12.0, 13.0]) + full_meas_su = np.array([1.0, 1.0, 1.0, 1.0]) + ds = SimpleNamespace( + full_x=full_x, + full_meas=full_meas, + full_meas_su=full_meas_su, + excluded=np.zeros_like(full_x, dtype=bool), + x=full_x.copy(), + meas=full_meas.copy(), + meas_su=full_meas_su.copy(), + ) + + coll = ExcludedRegions() + # stitch in a parent with datastore + object.__setattr__(coll, '_parent', SimpleNamespace(datastore=ds)) + + r = ExcludedRegion(start=1.0, end=2.0) + coll.add(r) + + # Second and third points excluded + assert np.all(ds.excluded == np.array([False, True, True, False])) + assert np.all(ds.x == np.array([0.0, 3.0])) + assert np.all(ds.meas == np.array([10.0, 13.0])) + + # CIF loop includes header tags + cif = coll.as_cif + assert 'loop_' in cif and '_excluded_region.start' in cif and '_excluded_region.end' in cif diff --git a/tests/unit/easydiffraction/experiments/categories/test_experiment_type.py b/tests/unit/easydiffraction/experiments/categories/test_experiment_type.py new file mode 100644 index 00000000..8a78444c --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/test_experiment_type.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.experiments.categories.experiment_type as MUT + + expected_module_name = 'easydiffraction.experiments.categories.experiment_type' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_experiment_type_properties_and_validation(monkeypatch): + from easydiffraction.experiments.categories.experiment_type import ExperimentType + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import RadiationProbeEnum + from easydiffraction.experiments.experiment.enums import SampleFormEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + from easydiffraction.utils.logging import log + + log.configure(reaction=log.Reaction.WARN) + + et = ExperimentType( + sample_form=SampleFormEnum.POWDER.value, + beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH.value, + radiation_probe=RadiationProbeEnum.NEUTRON.value, + scattering_type=ScatteringTypeEnum.BRAGG.value, + ) + # getters nominal + assert et.sample_form.value == SampleFormEnum.POWDER.value + assert et.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH.value + assert et.radiation_probe.value == RadiationProbeEnum.NEUTRON.value + assert et.scattering_type.value == ScatteringTypeEnum.BRAGG.value + + # try invalid value should fall back to previous (membership validator) + et.sample_form = 'invalid' + assert et.sample_form.value == SampleFormEnum.POWDER.value diff --git a/tests/unit/easydiffraction/experiments/categories/test_linked_phases.py b/tests/unit/easydiffraction/experiments/categories/test_linked_phases.py new file mode 100644 index 00000000..298d3b91 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/test_linked_phases.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_linked_phases_add_and_cif_headers(): + from easydiffraction.experiments.categories.linked_phases import LinkedPhase + from easydiffraction.experiments.categories.linked_phases import LinkedPhases + + lp = LinkedPhase(id='Si', scale=2.0) + assert lp.id.value == 'Si' and lp.scale.value == 2.0 + + coll = LinkedPhases() + coll.add(lp) + + # CIF loop header presence + cif = coll.as_cif + assert 'loop_' in cif and '_pd_phase_block.id' in cif and '_pd_phase_block.scale' in cif diff --git a/tests/unit/easydiffraction/experiments/datastore/test_base.py b/tests/unit/easydiffraction/experiments/datastore/test_base.py new file mode 100644 index 00000000..04e22171 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/datastore/test_base.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.experiments.datastore.base as MUT + + expected_module_name = 'easydiffraction.experiments.datastore.base' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/experiments/datastore/test_factory.py b/tests/unit/easydiffraction/experiments/datastore/test_factory.py new file mode 100644 index 00000000..0bb9325f --- /dev/null +++ b/tests/unit/easydiffraction/experiments/datastore/test_factory.py @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + +# Deterministic import order to avoid a circular import in isolation: +# Pre-import the experiment enums so that when DatastoreFactory pulls in +# PdDatastore -> enums, the module is already initialized and doesn't re-enter +# the experiment package initialization. +import easydiffraction.experiments.experiment.enums as _exp_enums # noqa: F401 +from easydiffraction.experiments.datastore.factory import DatastoreFactory + + +def test_create_powder_and_sc_datastores(): + ds_pd = DatastoreFactory.create(sample_form='powder', beam_mode='constant wavelength') + assert hasattr(ds_pd, 'beam_mode') + + ds_sc = DatastoreFactory.create(sample_form='single crystal', beam_mode='constant wavelength') + assert not hasattr(ds_sc, 'beam_mode') + + +def test_create_invalid_sample_form_raises(): + with pytest.raises(ValueError): + DatastoreFactory.create(sample_form='unknown', beam_mode='constant wavelength') diff --git a/tests/unit/easydiffraction/experiments/datastore/test_pd.py b/tests/unit/easydiffraction/experiments/datastore/test_pd.py new file mode 100644 index 00000000..3ecaa288 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/datastore/test_pd.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.experiments.datastore.pd as MUT + + expected_module_name = 'easydiffraction.experiments.datastore.pd' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/experiments/datastore/test_sc.py b/tests/unit/easydiffraction/experiments/datastore/test_sc.py new file mode 100644 index 00000000..35bae2c3 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/datastore/test_sc.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.experiments.datastore.sc as MUT + + expected_module_name = 'easydiffraction.experiments.datastore.sc' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/experiments/experiment/test_base.py b/tests/unit/easydiffraction/experiments/experiment/test_base.py new file mode 100644 index 00000000..bd0f93c9 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/experiment/test_base.py @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + + + +def test_module_import(): + import easydiffraction.experiments.experiment.base as MUT + + expected_module_name = 'easydiffraction.experiments.experiment.base' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_pd_experiment_peak_profile_type_switch(capsys): + from easydiffraction.experiments.categories.experiment_type import ExperimentType + from easydiffraction.experiments.experiment.base import PdExperimentBase + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import PeakProfileTypeEnum + from easydiffraction.experiments.experiment.enums import RadiationProbeEnum + from easydiffraction.experiments.experiment.enums import SampleFormEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + + class ConcretePd(PdExperimentBase): + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + pass + + et = ExperimentType( + sample_form=SampleFormEnum.POWDER.value, + beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH.value, + radiation_probe=RadiationProbeEnum.NEUTRON.value, + scattering_type=ScatteringTypeEnum.BRAGG.value, + ) + ex = ConcretePd(name='ex1', type=et) + # valid switch using enum + ex.peak_profile_type = PeakProfileTypeEnum.PSEUDO_VOIGT + assert ex.peak_profile_type == PeakProfileTypeEnum.PSEUDO_VOIGT + # invalid string should warn and keep previous + ex.peak_profile_type = 'non-existent' + captured = capsys.readouterr().out + assert 'Unsupported' in captured or 'Unknown' in captured diff --git a/tests/unit/easydiffraction/experiments/experiment/test_bragg_pd.py b/tests/unit/easydiffraction/experiments/experiment/test_bragg_pd.py new file mode 100644 index 00000000..cdc13f97 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/experiment/test_bragg_pd.py @@ -0,0 +1,72 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np +import pytest + +from easydiffraction.experiments.categories.background.enums import BackgroundTypeEnum +from easydiffraction.experiments.categories.experiment_type import ExperimentType +from easydiffraction.experiments.experiment.bragg_pd import BraggPdExperiment +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import RadiationProbeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + + +def _mk_type_powder_cwl_bragg(): + return ExperimentType( + sample_form=SampleFormEnum.POWDER.value, + beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH.value, + radiation_probe=RadiationProbeEnum.NEUTRON.value, + scattering_type=ScatteringTypeEnum.BRAGG.value, + ) + + +def test_background_defaults_and_change(): + expt = BraggPdExperiment(name='e1', type=_mk_type_powder_cwl_bragg()) + # default background type + assert expt.background_type == BackgroundTypeEnum.default() + + # change to a supported type + expt.background_type = BackgroundTypeEnum.CHEBYSHEV + assert expt.background_type == BackgroundTypeEnum.CHEBYSHEV + + # unknown type keeps previous type and prints warnings (no raise) + expt.background_type = 'not-a-type' # invalid string + assert expt.background_type == BackgroundTypeEnum.CHEBYSHEV + + +def test_load_ascii_data_rounds_and_defaults_sy(tmp_path: pytest.TempPathFactory): + expt = BraggPdExperiment(name='e1', type=_mk_type_powder_cwl_bragg()) + + # Case 1: provide only two columns -> sy defaults to sqrt(y) and min clipped to 1.0 + p = tmp_path / 'data2col.dat' + x = np.array([1.123456, 2.987654, 3.5]) + y = np.array([0.0, 4.0, 9.0]) + data = np.column_stack([x, y]) + np.savetxt(p, data) + + expt._load_ascii_data_to_experiment(str(p)) + + # x rounded to 4 decimals + assert np.allclose(expt.datastore.x, np.round(x, 4)) + # sy = sqrt(y) with values < 1e-4 replaced by 1.0 + expected_sy = np.sqrt(y) + expected_sy = np.where(expected_sy < 1e-4, 1.0, expected_sy) + assert np.allclose(expt.datastore.meas_su, expected_sy) + assert expt.datastore.excluded.shape == expt.datastore.x.shape + + # Case 2: three columns provided -> sy taken from file and clipped + p3 = tmp_path / 'data3col.dat' + sy = np.array([0.0, 1e-5, 0.2]) # first two should clip to 1.0 + data3 = np.column_stack([x, y, sy]) + np.savetxt(p3, data3) + expt._load_ascii_data_to_experiment(str(p3)) + expected_sy3 = np.where(sy < 1e-4, 1.0, sy) + assert np.allclose(expt.datastore.meas_su, expected_sy3) + + # Case 3: invalid shape -> currently triggers an exception (IndexError on shape[1]) + pinv = tmp_path / 'invalid.dat' + np.savetxt(pinv, np.ones((5, 1))) + with pytest.raises(Exception): + expt._load_ascii_data_to_experiment(str(pinv)) diff --git a/tests/unit/easydiffraction/experiments/experiment/test_bragg_sc.py b/tests/unit/easydiffraction/experiments/experiment/test_bragg_sc.py new file mode 100644 index 00000000..b4567ead --- /dev/null +++ b/tests/unit/easydiffraction/experiments/experiment/test_bragg_sc.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + +from easydiffraction.experiments.categories.experiment_type import ExperimentType +from easydiffraction.experiments.experiment.bragg_sc import BraggScExperiment +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import RadiationProbeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum +from easydiffraction.utils.logging import Logger + + +def _mk_type_sc_bragg(): + return ExperimentType( + sample_form=SampleFormEnum.SINGLE_CRYSTAL.value, + beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH.value, + radiation_probe=RadiationProbeEnum.NEUTRON.value, + scattering_type=ScatteringTypeEnum.BRAGG.value, + ) + + +class _ConcreteBraggSc(BraggScExperiment): + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + # Not used in this test + pass + + +def test_init_and_placeholder_no_crash(monkeypatch: pytest.MonkeyPatch): + # Prevent logger from raising on attribute errors inside __init__ + monkeypatch.setattr(Logger, '_reaction', Logger.Reaction.WARN, raising=True) + expt = _ConcreteBraggSc(name='sc1', type=_mk_type_sc_bragg()) + # show_meas_chart just prints placeholder text; ensure no exception + expt.show_meas_chart() diff --git a/tests/unit/easydiffraction/experiments/experiment/test_enums.py b/tests/unit/easydiffraction/experiments/experiment/test_enums.py new file mode 100644 index 00000000..e8514b51 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/experiment/test_enums.py @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + + + +def test_module_import(): + import easydiffraction.experiments.experiment.enums as MUT + + expected_module_name = 'easydiffraction.experiments.experiment.enums' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_default_enums_consistency(): + import easydiffraction.experiments.experiment.enums as MUT + + assert MUT.SampleFormEnum.default() in list(MUT.SampleFormEnum) + assert MUT.ScatteringTypeEnum.default() in list(MUT.ScatteringTypeEnum) + assert MUT.RadiationProbeEnum.default() in list(MUT.RadiationProbeEnum) + assert MUT.BeamModeEnum.default() in list(MUT.BeamModeEnum) diff --git a/tests/unit/easydiffraction/experiments/experiment/test_factory.py b/tests/unit/easydiffraction/experiments/experiment/test_factory.py new file mode 100644 index 00000000..61492265 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/experiment/test_factory.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + + +def test_module_import(): + import easydiffraction.experiments.experiment.factory as MUT + + expected_module_name = 'easydiffraction.experiments.experiment.factory' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_experiment_factory_create_without_data_and_invalid_combo(): + import easydiffraction.experiments.experiment.factory as EF + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import RadiationProbeEnum + from easydiffraction.experiments.experiment.enums import SampleFormEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + + ex = EF.ExperimentFactory.create( + name='ex1', + sample_form=SampleFormEnum.POWDER.value, + beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH.value, + radiation_probe=RadiationProbeEnum.NEUTRON.value, + scattering_type=ScatteringTypeEnum.BRAGG.value, + ) + # Instance should be created (BraggPdExperiment) + assert hasattr(ex, 'type') and ex.type.sample_form.value == SampleFormEnum.POWDER.value + + # invalid combination: unexpected key + with pytest.raises(ValueError): + EF.ExperimentFactory.create(name='ex2', unexpected=True) diff --git a/tests/unit/easydiffraction/experiments/experiment/test_instrument_mixin.py b/tests/unit/easydiffraction/experiments/experiment/test_instrument_mixin.py new file mode 100644 index 00000000..b0ecdc49 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/experiment/test_instrument_mixin.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + + + +def test_module_import(): + import easydiffraction.experiments.experiment.instrument_mixin as MUT + + expected_module_name = 'easydiffraction.experiments.experiment.instrument_mixin' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/experiments/experiment/test_total_pd.py b/tests/unit/easydiffraction/experiments/experiment/test_total_pd.py new file mode 100644 index 00000000..c008e579 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/experiment/test_total_pd.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np +import pytest + +from easydiffraction.experiments.categories.experiment_type import ExperimentType +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import RadiationProbeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum +from easydiffraction.experiments.experiment.total_pd import TotalPdExperiment + + +def _mk_type_powder_total(): + return ExperimentType( + sample_form=SampleFormEnum.POWDER.value, + beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH.value, + radiation_probe=RadiationProbeEnum.NEUTRON.value, + scattering_type=ScatteringTypeEnum.TOTAL.value, + ) + + +def test_load_ascii_data_pdf(tmp_path: pytest.TempPathFactory): + expt = TotalPdExperiment(name='pdf1', type=_mk_type_powder_total()) + + # Mock diffpy.utils.parsers.loaddata.loadData by creating a small parser module on sys.path + data = np.column_stack([ + np.array([0.0, 1.0, 2.0]), + np.array([10.0, 11.0, 12.0]), + np.array([0.01, 0.02, 0.03]), + ]) + f = tmp_path / 'g.dat' + np.savetxt(f, data) + + # Try to import loadData; if diffpy isn't installed, expect ImportError + try: + has_diffpy = True + except Exception: + has_diffpy = False + + if not has_diffpy: + with pytest.raises(ImportError): + expt._load_ascii_data_to_experiment(str(f)) + return + + # With diffpy available, load should succeed + expt._load_ascii_data_to_experiment(str(f)) + assert np.allclose(expt.datastore.x, data[:, 0]) + assert np.allclose(expt.datastore.meas, data[:, 1]) + assert np.allclose(expt.datastore.meas_su, data[:, 2]) diff --git a/tests/unit/easydiffraction/experiments/test_experiments.py b/tests/unit/easydiffraction/experiments/test_experiments.py new file mode 100644 index 00000000..3ffa982b --- /dev/null +++ b/tests/unit/easydiffraction/experiments/test_experiments.py @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.experiments.experiments as MUT + + expected_module_name = 'easydiffraction.experiments.experiments' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_experiments_show_and_remove(monkeypatch, capsys): + from easydiffraction.experiments.experiment.base import ExperimentBase + from easydiffraction.experiments.experiments import Experiments + + class DummyType: + def __init__(self): + self.sample_form = type('E', (), {'value': 'powder'}) + self.beam_mode = type('E', (), {'value': 'constant wavelength'}) + + class DummyExp(ExperimentBase): + def __init__(self, name='e1'): + super().__init__(name=name, type=DummyType()) + + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + pass + + exps = Experiments() + exps.add(DummyExp('a')) + exps.add(DummyExp('b')) + exps.show_names() + out = capsys.readouterr().out + assert 'Defined experiments' in out + + # Remove by name should not raise + exps.remove('a') + # Still can show names + exps.show_names() + out2 = capsys.readouterr().out + assert 'Defined experiments' in out2 diff --git a/tests/unit/easydiffraction/io/cif/test_handler.py b/tests/unit/easydiffraction/io/cif/test_handler.py new file mode 100644 index 00000000..a5bf7de2 --- /dev/null +++ b/tests/unit/easydiffraction/io/cif/test_handler.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_cif_handler_names_and_uid(): + import easydiffraction.io.cif.handler as H + + names = ['_cell.length_a', '_cell.length_b'] + h = H.CifHandler(names=names) + assert h.names == names + assert h.uid is None + + class Owner: + unique_name = 'db.cat.entry.param' + + h.attach(Owner()) + assert h.uid == 'db.cat.entry.param' diff --git a/tests/unit/easydiffraction/io/cif/test_serialize.py b/tests/unit/easydiffraction/io/cif/test_serialize.py new file mode 100644 index 00000000..83d09978 --- /dev/null +++ b/tests/unit/easydiffraction/io/cif/test_serialize.py @@ -0,0 +1,83 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.io.cif.serialize as MUT + + expected_module_name = 'easydiffraction.io.cif.serialize' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_format_value_quotes_whitespace_strings(): + import easydiffraction.io.cif.serialize as MUT + + assert MUT.format_value('a b') == '"a b"' + assert MUT.format_value('ab') == 'ab' + + +def test_param_to_cif_minimal(): + import easydiffraction.io.cif.serialize as MUT + from easydiffraction.io.cif.handler import CifHandler + + class P: + def __init__(self): + self._cif_handler = CifHandler(names=['_x.y']) # noqa: SLF001 for tests + self.value = 3 + + p = P() + assert MUT.param_to_cif(p) == '_x.y 3' + + +def test_category_collection_to_cif_empty_and_one_row(): + import easydiffraction.io.cif.serialize as MUT + from easydiffraction.core.category import CategoryCollection + from easydiffraction.core.category import CategoryItem + from easydiffraction.io.cif.handler import CifHandler + + class Item(CategoryItem): + def __init__(self, name, value): + super().__init__() + self._identity.category_entry_name = name + self._p = type('P', (), {})() + self._p._cif_handler = CifHandler(names=['_x']) # noqa: SLF001 + self._p.value = value + + @property + def parameters(self): + return [self._p] + + @property + def as_cif(self) -> str: + return MUT.category_item_to_cif(self) + + coll = CategoryCollection(item_type=Item) + assert MUT.category_collection_to_cif(coll) == '' + i = Item('n1', 5) + coll['n1'] = i + out = MUT.category_collection_to_cif(coll) + assert 'loop_' in out and '_x' in out and '5' in out + + +def test_project_to_cif_assembles_present_sections(): + import easydiffraction.io.cif.serialize as MUT + + class Obj: + def __init__(self, text): + self._text = text + + @property + def as_cif(self): + return self._text + + class Project: + def __init__(self): + self.info = Obj('I') + self.sample_models = None + self.experiments = Obj('E') + self.analysis = None + self.summary = None + + p = Project() + out = MUT.project_to_cif(p) + assert out == 'I\n\nE' diff --git a/tests/unit/easydiffraction/io/cif/test_serialize_more.py b/tests/unit/easydiffraction/io/cif/test_serialize_more.py new file mode 100644 index 00000000..004bd84e --- /dev/null +++ b/tests/unit/easydiffraction/io/cif/test_serialize_more.py @@ -0,0 +1,181 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_datastore_to_cif_empty_returns_empty_string(): + import easydiffraction.io.cif.serialize as MUT + + class DS: + def _cif_mapping(self): + return {'x': '_x', 'y': '_y'} + + # x, y absent or empty should yield empty CIF + x = np.array([]) + y = np.array([]) + + out = MUT.datastore_to_cif(DS()) + assert out == '' + + +def test_datastore_to_cif_writes_rows_and_respects_max_points(): + import easydiffraction.io.cif.serialize as MUT + + class DS: + def __init__(self, n): + self.x = np.arange(n) + self.y = np.arange(n) + 100 + + def _cif_mapping(self): + return {'x': '_x', 'y': '_y'} + + # Small dataset: no ellipsis + ds_small = DS(3) + out_small = MUT.datastore_to_cif(ds_small) + assert out_small.splitlines()[0] == 'loop_' + assert '_x' in out_small and '_y' in out_small + assert '0 100' in out_small and '2 102' in out_small + + # Larger dataset with max_points enforced + ds_large = DS(10) + out_large = MUT.datastore_to_cif(ds_large, max_points=2) + lines = out_large.splitlines() + assert '...' in lines + # First two rows and last two rows present + assert '0 100' in out_large and '1 101' in out_large + assert '8 108' in out_large and '9 109' in out_large + + +def test_datablock_item_to_cif_includes_item_and_collection(): + import easydiffraction.io.cif.serialize as MUT + from easydiffraction.core.category import CategoryCollection + from easydiffraction.core.category import CategoryItem + from easydiffraction.io.cif.handler import CifHandler + + class Item(CategoryItem): + def __init__(self, val): + super().__init__() + self._p = type('P', (), {})() + self._p._cif_handler = CifHandler(names=['_aa']) # noqa: SLF001 + self._p.value = val + + @property + def parameters(self): + return [self._p] + + @property + def as_cif(self) -> str: + return MUT.category_item_to_cif(self) + + class DB: + def __init__(self): + self._identity = type('I', (), {'datablock_entry_name': 'block1'})() + # one CategoryItem-like + self.item = Item(42) + # one CategoryCollection-like + self.coll = CategoryCollection(item_type=Item) + self.coll['row1'] = Item(7) + + out = MUT.datablock_item_to_cif(DB()) + assert out.startswith('data_block1') + assert '_aa 42' in out + assert 'loop_' in out and '_aa' in out and '7' in out + + +def test_datablock_collection_to_cif_concatenates_blocks(): + import easydiffraction.io.cif.serialize as MUT + + class B: + def __init__(self, t): + self._t = t + + @property + def as_cif(self): + return self._t + + coll = {'a': B('A'), 'b': B('B')} + out = MUT.datablock_collection_to_cif(coll) + assert out == 'A\n\nB' + + +def test_project_info_to_cif_contains_core_fields(): + import easydiffraction.io.cif.serialize as MUT + from easydiffraction.project.project_info import ProjectInfo + + info = ProjectInfo(name='p1', title='My Title', description='Some description text') + out = MUT.project_info_to_cif(info) + assert '_project.id p1' in out + assert '_project.title' in out and 'My Title' in out + assert '_project.description' in out + assert '_project.created' in out and '_project.last_modified' in out + + +def test_experiment_to_cif_with_and_without_data(): + import easydiffraction.io.cif.serialize as MUT + + class DS: + def __init__(self, text): + self._text = text + + @property + def as_cif(self): + return self._text + + class Exp: + def __init__(self, data_text): + self._identity = type('I', (), {'datablock_entry_name': 'expA'})() + self.datastore = DS(data_text) + # Minimal CategoryItem to be picked up by datablock_item_to_cif + from easydiffraction.core.category import CategoryItem + from easydiffraction.io.cif.handler import CifHandler + + class Item(CategoryItem): + def __init__(self): + super().__init__() + self._p = type('P', (), {})() + self._p._cif_handler = CifHandler(names=['_k']) # noqa: SLF001 + self._p.value = 1 + + @property + def parameters(self): + return [self._p] + + @property + def as_cif(self): + return MUT.category_item_to_cif(self) + + self.item = Item() + + out_with = MUT.experiment_to_cif(Exp('loop_\n_x\n1')) + assert out_with.startswith('data_expA') and 'loop_' in out_with + + out_without = MUT.experiment_to_cif(Exp('')) + assert out_without.startswith('data_expA') and out_without.endswith('_k 1') + + +def test_analysis_to_cif_renders_all_sections(): + import easydiffraction.io.cif.serialize as MUT + + class Obj: + def __init__(self, t): + self._t = t + + @property + def as_cif(self): + return self._t + + class A: + current_calculator = 'cryspy engine' + current_minimizer = 'lmfit (leastsq)' + fit_mode = 'single' + aliases = Obj('ALIASES') + constraints = Obj('CONSTRAINTS') + + out = MUT.analysis_to_cif(A()) + lines = out.splitlines() + assert lines[0].startswith('_analysis.calculator_engine') + assert '"cryspy engine"' in lines[0] + assert lines[1].startswith('_analysis.fitting_engine') and '"lmfit (leastsq)"' in lines[1] + assert lines[2].startswith('_analysis.fit_mode') and 'single' in lines[2] + assert 'ALIASES' in out and 'CONSTRAINTS' in out diff --git a/tests/unit/easydiffraction/plotting/plotters/test_plotter_ascii.py b/tests/unit/easydiffraction/plotting/plotters/test_plotter_ascii.py new file mode 100644 index 00000000..ab68b4d8 --- /dev/null +++ b/tests/unit/easydiffraction/plotting/plotters/test_plotter_ascii.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_module_import(): + import easydiffraction.plotting.plotters.plotter_ascii as MUT + + expected_module_name = 'easydiffraction.plotting.plotters.plotter_ascii' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_ascii_plotter_plot_minimal(capsys): + from easydiffraction.plotting.plotters.plotter_ascii import AsciiPlotter + + x = np.array([0.0, 1.0, 2.0]) + y = np.array([1.0, 2.0, 3.0]) + p = AsciiPlotter() + p.plot(x=x, y_series=[y], labels=['meas'], axes_labels=['x', 'y'], title='T', height=5) + out = capsys.readouterr().out + assert 'Displaying data for selected x-range' in out diff --git a/tests/unit/easydiffraction/plotting/plotters/test_plotter_base.py b/tests/unit/easydiffraction/plotting/plotters/test_plotter_base.py new file mode 100644 index 00000000..71e52753 --- /dev/null +++ b/tests/unit/easydiffraction/plotting/plotters/test_plotter_base.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import importlib + + +def test_module_import(): + import easydiffraction.plotting.plotters.plotter_base as MUT + + expected_module_name = 'easydiffraction.plotting.plotters.plotter_base' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_default_engine_switches_with_notebook(monkeypatch): + # Force is_notebook() to True, then reload module + import easydiffraction.plotting.plotters.plotter_base as pb + import easydiffraction.utils.utils as utils + + monkeypatch.setattr(utils, 'is_notebook', lambda: True) + pb2 = importlib.reload(pb) + assert pb2.DEFAULT_ENGINE == 'plotly' + + # Now force False + monkeypatch.setattr(utils, 'is_notebook', lambda: False) + pb3 = importlib.reload(pb) + assert pb3.DEFAULT_ENGINE == 'asciichartpy' + + +def test_default_axes_labels_keys_present(): + import easydiffraction.plotting.plotters.plotter_base as pb + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + + assert (ScatteringTypeEnum.BRAGG, BeamModeEnum.CONSTANT_WAVELENGTH) in pb.DEFAULT_AXES_LABELS + assert (ScatteringTypeEnum.BRAGG, BeamModeEnum.TIME_OF_FLIGHT) in pb.DEFAULT_AXES_LABELS diff --git a/tests/unit/easydiffraction/plotting/plotters/test_plotter_plotly.py b/tests/unit/easydiffraction/plotting/plotters/test_plotter_plotly.py new file mode 100644 index 00000000..bc9270f1 --- /dev/null +++ b/tests/unit/easydiffraction/plotting/plotters/test_plotter_plotly.py @@ -0,0 +1,87 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.plotting.plotters.plotter_plotly as MUT + + expected_module_name = 'easydiffraction.plotting.plotters.plotter_plotly' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_get_trace_and_plot(monkeypatch): + import easydiffraction.plotting.plotters.plotter_plotly as pp + + # Arrange: force non-PyCharm branch and stub fig.show/HTML/display so nothing opens + monkeypatch.setattr(pp, 'is_pycharm', lambda: False) + + shown = {'count': 0} + + class DummyFig: + def update_xaxes(self, **kwargs): + pass + + def update_yaxes(self, **kwargs): + pass + + def show(self, **kwargs): + shown['count'] += 1 + + # Patch go.Scatter and go.Figure to minimal dummies + class DummyScatter: + def __init__(self, **kwargs): + self.kwargs = kwargs + + class DummyGO: + class Scatter(DummyScatter): + pass + + class Figure(DummyFig): + def __init__(self, data=None, layout=None): + self.data = data + self.layout = layout + + class Layout: + def __init__(self, **kwargs): + self.kwargs = kwargs + + class DummyPIO: + @staticmethod + def to_html(fig, include_plotlyjs=None, full_html=None, config=None): + return '
plot
' + + dummy_display_calls = {'count': 0} + + def dummy_display(obj): + dummy_display_calls['count'] += 1 + + class DummyHTML: + def __init__(self, html): + self.html = html + + monkeypatch.setattr(pp, 'go', DummyGO) + monkeypatch.setattr(pp, 'pio', DummyPIO) + monkeypatch.setattr(pp, 'display', dummy_display) + monkeypatch.setattr(pp, 'HTML', DummyHTML) + + plotter = pp.PlotlyPlotter() + + # Exercise _get_trace + x = [0, 1, 2] + y = [1, 2, 3] + trace = plotter._get_trace(x, y, label='calc') + assert hasattr(trace, 'kwargs') + assert trace.kwargs['x'] == x and trace.kwargs['y'] == y + + # Exercise plot (non-PyCharm, display path) + plotter.plot( + x, + y_series=[y], + labels=['calc'], + axes_labels=['x', 'y'], + title='t', + height=None, + ) + + # One HTML display call expected + assert dummy_display_calls['count'] == 1 or shown['count'] == 1 diff --git a/tests/unit/easydiffraction/plotting/test_plotting.py b/tests/unit/easydiffraction/plotting/test_plotting.py new file mode 100644 index 00000000..64987638 --- /dev/null +++ b/tests/unit/easydiffraction/plotting/test_plotting.py @@ -0,0 +1,138 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.plotting.plotting as MUT + + expected_module_name = 'easydiffraction.plotting.plotting' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_plotter_configuration_and_engine_switch(capsys): + from easydiffraction.plotting.plotting import Plotter + + p = Plotter() + # show config prints a table + p.show_config() + out1 = capsys.readouterr().out + assert 'Current plotter configuration' in out1 + + # show supported engines prints a table + p.show_supported_engines() + out2 = capsys.readouterr().out + assert 'Supported plotter engines' in out2 + + # Switch engine to its current value (no-op, but exercise setter) + cur = p.engine + p.engine = cur + # And to an unsupported engine (prints error and leaves engine unchanged) + p.engine = '___not_supported___' + assert p.engine == cur + + # Supported engines include both known backends + p.show_supported_engines() + out3 = capsys.readouterr().out + assert 'asciichartpy' in out3 or 'plotly' in out3 + + +def test_plotter_factory_unsupported(capsys): + from easydiffraction.plotting.plotting import PlotterFactory + + obj = PlotterFactory.create_plotter('nope') + assert obj is None + out = capsys.readouterr().out + assert 'Unsupported plotting engine' in out + + +def test_plotter_error_paths_and_filtering(capsys): + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + from easydiffraction.plotting.plotting import Plotter + + class Ptn: + def __init__(self, x=None, meas=None, calc=None, d=None): + self.x = x + self.meas = meas + self.calc = calc + self.d = d if d is not None else x + + class ExptType: + def __init__(self): + self.scattering_type = type('S', (), {'value': ScatteringTypeEnum.BRAGG}) + self.beam_mode = type('B', (), {'value': BeamModeEnum.CONSTANT_WAVELENGTH}) + + p = Plotter() + + # Error paths + p.plot_meas(Ptn(x=None, meas=None), 'E', ExptType()) + out = capsys.readouterr().out + # plot_meas uses formatting.error(...) without printing -> no stdout + assert out == '' + + p.plot_meas(Ptn(x=[1], meas=None), 'E', ExptType()) + out = capsys.readouterr().out + # Same here: no print, so no stdout + assert out == '' + + p.plot_calc(Ptn(x=None, calc=None), 'E', ExptType()) + out = capsys.readouterr().out + # plot_calc also uses formatting.error(...) without printing for x is None + assert out == '' + + p.plot_calc(Ptn(x=[1], calc=None), 'E', ExptType()) + out = capsys.readouterr().out + assert 'No calculated data available' in out or 'No calculated data' in out + + p.plot_meas_vs_calc(Ptn(x=None), 'E', ExptType()) + out = capsys.readouterr().out + assert 'No data available' in out + p.plot_meas_vs_calc(Ptn(x=[1], meas=None, calc=[1]), 'E', ExptType()) + out = capsys.readouterr().out + assert 'No measured data available' in out + p.plot_meas_vs_calc(Ptn(x=[1], meas=[1], calc=None), 'E', ExptType()) + out = capsys.readouterr().out + assert 'No calculated data available' in out + + # Filtering + import numpy as np + + p.x_min, p.x_max = 0.5, 1.5 + arr = np.array([0.0, 1.0, 2.0]) + filt = p._filtered_y_array(arr, arr, None, None) + assert np.allclose(filt, np.array([1.0])) + + +def test_plotter_routes_to_ascii_plotter(monkeypatch): + import numpy as np + + import easydiffraction.plotting.plotters.plotter_ascii as ascii_mod + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + from easydiffraction.plotting.plotting import Plotter + + called = {} + + def fake_plot(self, x, y_series, labels, axes_labels, title, height=None): + called['labels'] = tuple(labels) + called['axes'] = tuple(axes_labels) + called['title'] = title + + monkeypatch.setattr(ascii_mod.AsciiPlotter, 'plot', fake_plot) + + class Ptn: + def __init__(self): + self.x = np.array([0.0, 1.0]) + self.meas = np.array([1.0, 2.0]) + self.d = self.x + + class ExptType: + def __init__(self): + self.scattering_type = type('S', (), {'value': ScatteringTypeEnum.BRAGG}) + self.beam_mode = type('B', (), {'value': BeamModeEnum.CONSTANT_WAVELENGTH}) + + p = Plotter() + p.engine = 'asciichartpy' # ensure AsciiPlotter + p.plot_meas(Ptn(), 'E', ExptType()) + assert called['labels'] == ('meas',) + assert 'Measured data' in called['title'] diff --git a/tests/unit/easydiffraction/project/test_project.py b/tests/unit/easydiffraction/project/test_project.py new file mode 100644 index 00000000..7fb6e8d6 --- /dev/null +++ b/tests/unit/easydiffraction/project/test_project.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.project.project as MUT + + expected_module_name = 'easydiffraction.project.project' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/project/test_project_d_spacing.py b/tests/unit/easydiffraction/project/test_project_d_spacing.py new file mode 100644 index 00000000..121bc905 --- /dev/null +++ b/tests/unit/easydiffraction/project/test_project_d_spacing.py @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_update_pattern_d_spacing_branches(monkeypatch, capsys): + # Arrange minimal experiment/collection using real Experiments + from easydiffraction.experiments.experiment.base import PdExperimentBase + from easydiffraction.experiments.experiment.instrument_mixin import InstrumentMixin + from easydiffraction.experiments.experiments import Experiments + + class DS: + def __init__(self, x): + self.x = x + self.d = None + + class Instr: + def __init__(self): + self.calib_d_to_tof_offset = type('P', (), {'value': 1.0}) + self.calib_d_to_tof_linear = type('P', (), {'value': 2.0}) + self.calib_d_to_tof_quad = type('P', (), {'value': 0.0}) + self.setup_wavelength = type('P', (), {'value': 1.54}) + + class TypeObj: + def __init__(self, beam_mode_value): + self.beam_mode = type('E', (), {'value': beam_mode_value}) + self.sample_form = type('E', (), {'value': 'powder'}) + self.scattering_type = type('E', (), {'value': 'bragg'}) + + class DummyExp(InstrumentMixin, PdExperimentBase): + def __init__(self, name, beam_mode_value): + super().__init__(name=name, type=TypeObj(beam_mode_value)) + # Replace with controlled datastore/instrument for test + self._datastore = DS(x=np.array([1.0, 2.0, 3.0])) + + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + pass + + exps = Experiments() + tof_exp = DummyExp('e_tof', 'time-of-flight') + cwl_exp = DummyExp('e_cwl', 'constant wavelength') + exps.add(tof_exp) + exps.add(cwl_exp) + + from easydiffraction.project.project import Project + + proj = Project() + proj.experiments = exps + + # Act TOF + proj.update_pattern_d_spacing('e_tof') + # Act CWL + proj.update_pattern_d_spacing('e_cwl') + + # Assert: d arrays were computed + assert isinstance(tof_exp.datastore.d, np.ndarray) + assert isinstance(cwl_exp.datastore.d, np.ndarray) + + +def test_update_pattern_d_spacing_unsupported_prints(monkeypatch, capsys): + # Use real Experiments and flip the mode to unsupported post-init + from easydiffraction.experiments.experiment.base import PdExperimentBase + from easydiffraction.experiments.experiment.instrument_mixin import InstrumentMixin + from easydiffraction.experiments.experiments import Experiments + + class DS: + def __init__(self): + self.x = np.array([1.0]) + self.d = None + + class TypeObj: + def __init__(self, beam_mode_value): + self.beam_mode = type('E', (), {'value': beam_mode_value}) + self.sample_form = type('E', (), {'value': 'powder'}) + self.scattering_type = type('E', (), {'value': 'bragg'}) + + class DummyExp(InstrumentMixin, PdExperimentBase): + def __init__(self, name, beam_mode_value): + super().__init__(name=name, type=TypeObj(beam_mode_value)) + self._datastore = DS() + + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + pass + + exps = Experiments() + exp = DummyExp('e1', 'constant wavelength') + # Flip to unsupported value after init to avoid factory issues + exp.type.beam_mode = type('E', (), {'value': 'unsupported'}) + exps.add(exp) + + from easydiffraction.project.project import Project + + p = Project() + p.experiments = exps + + # Act + p.update_pattern_d_spacing('e1') + + # Assert warning printed + out = capsys.readouterr().out + assert 'Unsupported beam mode' in out diff --git a/tests/unit/easydiffraction/project/test_project_info.py b/tests/unit/easydiffraction/project/test_project_info.py new file mode 100644 index 00000000..88116d81 --- /dev/null +++ b/tests/unit/easydiffraction/project/test_project_info.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.project.project_info as MUT + + expected_module_name = 'easydiffraction.project.project_info' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name 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 new file mode 100644 index 00000000..8753b775 --- /dev/null +++ b/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_project_load_prints_and_sets_path(tmp_path, capsys): + from easydiffraction.project.project import Project + + p = Project() + dir_path = tmp_path / 'pdir' + p.load(str(dir_path)) + out = capsys.readouterr().out + assert 'Loading project' in out and str(dir_path) in out + # Path should be set on ProjectInfo + assert p.info.path == dir_path + + +def test_summary_show_project_info_wraps_description(capsys): + from easydiffraction.summary.summary import Summary + + long_desc = ' '.join(['desc'] * 50) # long text to trigger wrapping + + class Info: + title = 'T' + description = long_desc + + class Project: + def __init__(self): + self.info = Info() + + s = Summary(Project()) + s.show_project_info() + out = capsys.readouterr().out + # Title and Description paragraph headers present + assert 'PROJECT INFO' in out + assert 'Title' in out + assert 'Description' in out + # Ensure multiple lines of description were printed (wrapped) + # Keep the exact word count and verify the presence of line breaks in the description block + assert out.count('desc') == 50 # all words are present exactly once + assert '\ndesc ' in out or ' desc\n' in out # wrapped across lines diff --git a/tests/unit/easydiffraction/project/test_project_save.py b/tests/unit/easydiffraction/project/test_project_save.py new file mode 100644 index 00000000..47941f27 --- /dev/null +++ b/tests/unit/easydiffraction/project/test_project_save.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction 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 + + monkeypatch.chdir(tmp_path) + p = Project() + p.save() + out = capsys.readouterr().out + # It should announce saving and create the three core files in cwd + assert 'Saving project' in out + assert (tmp_path / 'project.cif').exists() + assert (tmp_path / 'analysis.cif').exists() + assert (tmp_path / 'summary.cif').exists() + + +def test_project_save_as_writes_core_files(tmp_path, monkeypatch): + from easydiffraction.analysis.analysis import Analysis + from easydiffraction.project.project import Project + from easydiffraction.project.project_info import ProjectInfo + from easydiffraction.summary.summary import Summary + + # Monkeypatch as_cif producers to avoid heavy internals + monkeypatch.setattr(ProjectInfo, 'as_cif', lambda self: 'info') + monkeypatch.setattr(Analysis, 'as_cif', lambda self: 'analysis') + monkeypatch.setattr(Summary, 'as_cif', lambda self: 'summary') + + p = Project(name='p1') + target = tmp_path / 'proj_dir' + p.save_as(str(target)) + + # Assert expected files/dirs exist + assert (target / 'project.cif').is_file() + assert (target / 'analysis.cif').is_file() + assert (target / 'summary.cif').is_file() + assert (target / 'sample_models').is_dir() + assert (target / 'experiments').is_dir() diff --git a/tests/unit/easydiffraction/sample_models/categories/test_atom_sites.py b/tests/unit/easydiffraction/sample_models/categories/test_atom_sites.py new file mode 100644 index 00000000..b182fe38 --- /dev/null +++ b/tests/unit/easydiffraction/sample_models/categories/test_atom_sites.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.sample_models.categories.atom_sites import AtomSite +from easydiffraction.sample_models.categories.atom_sites import AtomSites + + +def test_atom_site_defaults_and_setters(): + a = AtomSite(label='Si1', type_symbol='Si') + a.fract_x = 0.1 + a.fract_y = 0.2 + a.fract_z = 0.3 + a.occupancy = 0.9 + a.b_iso = 1.5 + a.adp_type = 'Biso' + assert a.label.value == 'Si1' + assert a.type_symbol.value == 'Si' + assert (a.fract_x.value, a.fract_y.value, a.fract_z.value) == (0.1, 0.2, 0.3) + assert a.occupancy.value == 0.9 + assert a.b_iso.value == 1.5 + assert a.adp_type.value == 'Biso' + + +def test_atom_sites_collection_adds_by_label(): + sites = AtomSites() + a = AtomSite(label='O1', type_symbol='O') + sites.add(a) + assert 'O1' in sites.names + assert sites['O1'].type_symbol.value == 'O' diff --git a/tests/unit/easydiffraction/sample_models/categories/test_cell.py b/tests/unit/easydiffraction/sample_models/categories/test_cell.py new file mode 100644 index 00000000..aade56db --- /dev/null +++ b/tests/unit/easydiffraction/sample_models/categories/test_cell.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_cell_defaults_and_overrides(): + from easydiffraction.sample_models.categories.cell import Cell + + c = Cell() + # Defaults from AttributeSpec in implementation + assert pytest.approx(c.length_a.value) == 10.0 + assert pytest.approx(c.length_b.value) == 10.0 + assert pytest.approx(c.length_c.value) == 10.0 + assert pytest.approx(c.angle_alpha.value) == 90.0 + assert pytest.approx(c.angle_beta.value) == 90.0 + assert pytest.approx(c.angle_gamma.value) == 90.0 + + # Override through constructor + c2 = Cell(length_a=12.3, angle_beta=100.0) + assert pytest.approx(c2.length_a.value) == 12.3 + assert pytest.approx(c2.angle_beta.value) == 100.0 + + +def test_cell_setters_apply_validation_and_units(): + from easydiffraction.sample_models.categories.cell import Cell + + c = Cell() + # Set valid values within range + c.length_a = 5.5 + c.angle_gamma = 120.0 + assert pytest.approx(c.length_a.value) == 5.5 + assert pytest.approx(c.angle_gamma.value) == 120.0 + # Check units are preserved on parameter objects + assert c.length_a.units == 'Å' + assert c.angle_gamma.units == 'deg' + + +def test_cell_identity_category_code(): + from easydiffraction.sample_models.categories.cell import Cell + + c = Cell() + assert c._identity.category_code == 'cell' diff --git a/tests/unit/easydiffraction/sample_models/categories/test_space_group.py b/tests/unit/easydiffraction/sample_models/categories/test_space_group.py new file mode 100644 index 00000000..02136c14 --- /dev/null +++ b/tests/unit/easydiffraction/sample_models/categories/test_space_group.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.sample_models.categories.space_group import SpaceGroup + + +def test_space_group_name_updates_it_code(): + sg = SpaceGroup() + # default name 'P 1' should set code to the first available + default_code = sg.it_coordinate_system_code.value + sg.name_h_m = 'P 1' + assert sg.it_coordinate_system_code.value == sg._it_coordinate_system_code_allowed_values[0] + # changing name resets the code again + sg.name_h_m = 'P -1' + assert sg.it_coordinate_system_code.value == sg._it_coordinate_system_code_allowed_values[0] diff --git a/tests/unit/easydiffraction/sample_models/sample_model/test_base.py b/tests/unit/easydiffraction/sample_models/sample_model/test_base.py new file mode 100644 index 00000000..941e0af9 --- /dev/null +++ b/tests/unit/easydiffraction/sample_models/sample_model/test_base.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.sample_models.sample_model.base import SampleModelBase + + +def test_sample_model_base_str_and_properties(): + m = SampleModelBase(name='m1') + m.name = 'm2' + assert m.name == 'm2' + s = str(m) + assert 'SampleModelBase' in s or '<' in s diff --git a/tests/unit/easydiffraction/sample_models/sample_model/test_factory.py b/tests/unit/easydiffraction/sample_models/sample_model/test_factory.py new file mode 100644 index 00000000..f6d7e694 --- /dev/null +++ b/tests/unit/easydiffraction/sample_models/sample_model/test_factory.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + +from easydiffraction.sample_models.sample_model.factory import SampleModelFactory + + +def test_create_minimal_by_name(): + m = SampleModelFactory.create(name='abc') + assert m.name == 'abc' + + +def test_invalid_arg_combo_raises(): + with pytest.raises(ValueError): + SampleModelFactory.create(name=None, cif_path=None) diff --git a/tests/unit/easydiffraction/sample_models/test_sample_models.py b/tests/unit/easydiffraction/sample_models/test_sample_models.py new file mode 100644 index 00000000..21870518 --- /dev/null +++ b/tests/unit/easydiffraction/sample_models/test_sample_models.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.sample_models.sample_models import SampleModels + + +def test_add_minimal_and_remove(): + models = SampleModels() + models.add_minimal('m1') + assert 'm1' in models.names + models.remove('m1') + assert 'm1' not in models diff --git a/tests/unit/easydiffraction/summary/test_summary.py b/tests/unit/easydiffraction/summary/test_summary.py new file mode 100644 index 00000000..6a25c3ff --- /dev/null +++ b/tests/unit/easydiffraction/summary/test_summary.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_summary_as_cif_returns_placeholder_string(): + from easydiffraction.summary.summary import Summary + + class P: + pass + + s = Summary(P()) + out = s.as_cif() + assert isinstance(out, str) + assert 'To be added' in out + + +def test_summary_show_report_prints_sections(capsys): + from easydiffraction.summary.summary import Summary + + class Info: + title = 'T' + description = '' + + class Project: + def __init__(self): + self.info = Info() + self.sample_models = {} # empty mapping to exercise loops safely + self.experiments = {} # empty mapping to exercise loops safely + + class A: + current_calculator = 'cryspy' + current_minimizer = 'lmfit' + + class R: + reduced_chi_square = 0.0 + + fit_results = R() + + self.analysis = A() + + s = Summary(Project()) + s.show_report() + out = capsys.readouterr().out + # Verify that all top-level sections appear (titles are uppercased by formatter) + assert 'PROJECT INFO' in out + assert 'CRYSTALLOGRAPHIC DATA' in out + assert 'EXPERIMENTS' in out + assert 'FITTING' in out + + + + + + + +def test_module_import(): + import easydiffraction.summary.summary as MUT + + expected_module_name = 'easydiffraction.summary.summary' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/summary/test_summary_details.py b/tests/unit/easydiffraction/summary/test_summary_details.py new file mode 100644 index 00000000..fb32eafc --- /dev/null +++ b/tests/unit/easydiffraction/summary/test_summary_details.py @@ -0,0 +1,135 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_summary_crystallographic_and_experimental_sections(capsys): + from easydiffraction.summary.summary import Summary + + # Build a minimal sample model stub that exposes required attributes + class Val: + def __init__(self, v): + self.value = v + + class CellParam: + def __init__(self, name, value): + self.name = name + self.value = value + + class Model: + def __init__(self): + self.name = 'phaseA' + self.space_group = type('SG', (), {'name_h_m': Val('P 1')})() + + class Cell: + @property + def parameters(self_inner): + return [ + CellParam('length_a', 5.4321), + CellParam('angle_alpha', 90.0), + ] + + self.cell = Cell() + + class Site: + def __init__(self, label, typ, x, y, z, occ, biso): + self.label = Val(label) + self.type_symbol = Val(typ) + self.fract_x = Val(x) + self.fract_y = Val(y) + self.fract_z = Val(z) + self.occupancy = Val(occ) + self.b_iso = Val(biso) + + self.atom_sites = [Site('Na1', 'Na', 0.1, 0.2, 0.3, 1.0, 0.5)] + + # Minimal experiment stub with instrument and peak info + class Expt: + def __init__(self): + self.name = 'exp1' + typ = type( + 'T', + (), + { + 'sample_form': Val('powder'), + 'radiation_probe': Val('neutron'), + 'beam_mode': Val('constant wavelength'), + }, + ) + self.type = typ() + + class Instr: + def __init__(self): + self.setup_wavelength = Val(1.23456) + self.calib_twotheta_offset = Val(0.12345) + + def _public_attrs(self): + return ['setup_wavelength', 'calib_twotheta_offset'] + + self.instrument = Instr() + self.peak_profile_type = 'pseudo-Voigt' + + class Peak: + def __init__(self): + self.broad_gauss_u = Val(0.1) + self.broad_gauss_v = Val(0.2) + self.broad_gauss_w = Val(0.3) + self.broad_lorentz_x = Val(0.4) + self.broad_lorentz_y = Val(0.5) + + def _public_attrs(self): + return [ + 'broad_gauss_u', + 'broad_gauss_v', + 'broad_gauss_w', + 'broad_lorentz_x', + 'broad_lorentz_y', + ] + + self.peak = Peak() + + def _public_attrs(self): + return ['instrument', 'peak_profile_type', 'peak'] + + class Info: + title = 'T' + description = '' + + class Project: + def __init__(self): + self.info = Info() + self.sample_models = {'phaseA': Model()} + self.experiments = {'exp1': Expt()} + + class A: + current_calculator = 'cryspy' + current_minimizer = 'lmfit' + + class R: + reduced_chi_square = 1.23 + + fit_results = R() + + self.analysis = A() + + s = Summary(Project()) + # Run both sections separately for targeted assertions + s.show_crystallographic_data() + s.show_experimental_data() + out = capsys.readouterr().out + + # Crystallographic section + assert 'CRYSTALLOGRAPHIC DATA' in out + assert '🧩 phaseA' in out + assert 'Space group' in out and 'P 1' in out + # Cell parameter names are shortened by the implementation (e.g., 'length_a' -> 'a') + assert 'Cell parameters' in out and ' a ' in out and ' alpha ' in out + assert 'Atom sites' in out and 'Na1' in out and 'Na' in out + + # Experimental section + assert 'EXPERIMENTS' in out + assert '🔬 exp1' in out + assert 'powder' in out and 'neutron' in out and 'constant wavelength' in out + assert 'Wavelength' in out and '1.23456'[:6] in out + assert '2θ offset' in out and '0.12345'[:6] in out + assert 'Profile type' in out and 'pseudo-Voigt' in out + assert 'Peak broadening (Gaussian)' in out + assert 'Peak broadening (Lorentzian)' in out diff --git a/tests/unit/easydiffraction/test___init__.py b/tests/unit/easydiffraction/test___init__.py new file mode 100644 index 00000000..5a0b82d4 --- /dev/null +++ b/tests/unit/easydiffraction/test___init__.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +# Focused tests for package __init__: lazy attributes and error path +import importlib + +import pytest + + +def test_lazy_attributes_resolve_and_are_accessible(): + import easydiffraction as ed + + # Access a few lazy attributes; just ensure they exist and are callable/class-like + assert hasattr(ed, 'Project') + assert hasattr(ed, 'ExperimentFactory') + assert hasattr(ed, 'SampleModelFactory') + + # Access utility functions from utils via lazy getattr + assert callable(ed.show_version) + assert callable(ed.get_value_from_xye_header) + + # Import once to exercise __getattr__; subsequent access should be cached by Python + _ = ed.Project + _ = ed.ExperimentFactory + + +def test___getattr__unknown_raises_attribute_error(): + ed = importlib.import_module('easydiffraction') + with pytest.raises(AttributeError): + ed.DefinitelyUnknownAttribute + + +def test_lazy_functions_execute_with_monkeypatch(monkeypatch, capsys, tmp_path): + import easydiffraction as ed + import easydiffraction.utils.utils as utils + + # 1) list_tutorials uses utils.fetch_tutorial_list → monkeypatch there + monkeypatch.setattr(utils, 'fetch_tutorial_list', lambda: ['a.ipynb', 'b.ipynb']) + ed.list_tutorials() # calls into utils.list_tutorials + out = capsys.readouterr().out + assert 'Tutorials available for easydiffraction' in out + + # 2) download_from_repository should call pooch.retrieve; avoid network + import easydiffraction.utils.utils as utils + + calls = {} + monkeypatch.setattr(utils.pooch, 'retrieve', lambda **kw: calls.setdefault('ok', True)) + utils.download_from_repository( + 'dummy.txt', branch='main', destination=str(tmp_path), overwrite=True + ) + assert calls.get('ok') is True diff --git a/tests/unit/easydiffraction/test___main__.py b/tests/unit/easydiffraction/test___main__.py new file mode 100644 index 00000000..6db1dc35 --- /dev/null +++ b/tests/unit/easydiffraction/test___main__.py @@ -0,0 +1,54 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from typer.testing import CliRunner + +runner = CliRunner() + + +def test_module_import(): + import easydiffraction.__main__ as MUT + + expected_module_name = 'easydiffraction.__main__' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_cli_version_invokes_show_version(monkeypatch, capsys): + import easydiffraction as ed + import easydiffraction.__main__ as main_mod + + called = {'ok': False} + + def fake_show_version(): + print('VERSION_OK') + called['ok'] = True + + monkeypatch.setattr(ed, 'show_version', fake_show_version) + result = runner.invoke(main_mod.app, ['--version']) + assert result.exit_code == 0 + assert called['ok'] + assert 'VERSION_OK' in result.stdout + + +def test_cli_help_shows_and_exits_zero(): + import easydiffraction.__main__ as main_mod + + result = runner.invoke(main_mod.app, ['--help']) + assert result.exit_code == 0 + assert 'EasyDiffraction command-line interface' in result.stdout + + +def test_cli_subcommands_call_utils(monkeypatch): + import easydiffraction as ed + import easydiffraction.__main__ as main_mod + + logs = [] + monkeypatch.setattr(ed, 'list_tutorials', lambda: logs.append('LIST')) + monkeypatch.setattr(ed, 'fetch_tutorials', lambda: logs.append('FETCH')) + + res1 = runner.invoke(main_mod.app, ['list-tutorials']) + res2 = runner.invoke(main_mod.app, ['fetch-tutorials']) + + assert res1.exit_code == 0 and res2.exit_code == 0 + assert logs == ['LIST', 'FETCH'] diff --git a/tests/unit/easydiffraction/utils/test_formatting.py b/tests/unit/easydiffraction/utils/test_formatting.py new file mode 100644 index 00000000..3aebb21b --- /dev/null +++ b/tests/unit/easydiffraction/utils/test_formatting.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import re + + +def _strip_ansi(s: str) -> str: + return re.sub(r'\x1b\[[0-9;]*m', '', s) + + +def test_chapter_uppercase_and_length(): + import easydiffraction.utils.formatting as F + + title = 'Intro' + s = _strip_ansi(F.chapter(title)) + # chapter uses box drawing SYMBOL = '═' + assert '═' in s and title.upper() in s + + +def test_section_formatting_contains_markers(): + import easydiffraction.utils.formatting as F + + s = _strip_ansi(F.section('part')) + assert '*** PART ***' in s.upper() + + +def test_paragraph_preserves_quotes(): + import easydiffraction.utils.formatting as F + + s = _strip_ansi(F.paragraph("Hello 'World'")) + assert "'World'" in s + + +def test_error_warning_info_prefixes(): + import easydiffraction.utils.formatting as F + + assert 'Error' in _strip_ansi(F.error('x')) + assert 'Warning' in _strip_ansi(F.warning('x')) + assert 'Info' in _strip_ansi(F.info('x')) diff --git a/tests/unit/easydiffraction/utils/test_logging.py b/tests/unit/easydiffraction/utils/test_logging.py new file mode 100644 index 00000000..a77d9297 --- /dev/null +++ b/tests/unit/easydiffraction/utils/test_logging.py @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + + + +def test_module_import(): + import easydiffraction.utils.logging as MUT + + expected_module_name = 'easydiffraction.utils.logging' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_logger_configure_and_warn_reaction(): + import easydiffraction.utils.logging as MUT + + # configure to WARN so .error produces warnings and not exceptions + MUT.log.configure(reaction=MUT.log.Reaction.WARN) + MUT.log.debug('d') + MUT.log.info('i') + MUT.log.warning('w') + MUT.log.error('e') + # switch mode/level + MUT.log.set_level(MUT.log.Level.INFO) + MUT.log.set_mode(MUT.log.Mode.VERBOSE) + # nothing to assert; absence of exception is success + assert True diff --git a/tests/unit/easydiffraction/utils/test_utils.py b/tests/unit/easydiffraction/utils/test_utils.py new file mode 100644 index 00000000..9b209f64 --- /dev/null +++ b/tests/unit/easydiffraction/utils/test_utils.py @@ -0,0 +1,198 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np +import pytest + + + +def test_module_import(): + import easydiffraction.utils.utils as MUT + + expected_module_name = 'easydiffraction.utils.utils' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_twotheta_to_d_scalar_and_array(): + import easydiffraction.utils.utils as MUT + + wavelength = 1.54 + # scalar + expected_scalar = wavelength / (2 * np.sin(np.radians(30 / 2))) + actual_scalar = MUT.twotheta_to_d(30.0, wavelength) + assert np.isclose(expected_scalar, actual_scalar) + # array + twotheta = np.array([10.0, 20.0, 40.0]) + expected_array = wavelength / (2 * np.sin(np.radians(twotheta / 2))) + actual_array = MUT.twotheta_to_d(twotheta, wavelength) + assert np.allclose(expected_array, actual_array) + + +def test_tof_to_d_linear_case(): + import easydiffraction.utils.utils as MUT + + tof = np.array([10.0, 20.0, 30.0]) + offset, linear, quad = 2.0, 4.0, 0.0 + expected = (tof - offset) / linear + actual = MUT.tof_to_d(tof, offset, linear, quad) + assert np.allclose(expected, actual) + + +def test_tof_to_d_quadratic_case_smallest_positive_root(): + import easydiffraction.utils.utils as MUT + + # Model: TOF = quad * d^2, with offset=linear=0 + quad = 2.0 + tof = np.array([2.0, 8.0, 18.0]) # roots: sqrt(tof/quad) + expected = np.sqrt(tof / quad) + actual = MUT.tof_to_d(tof, offset=0.0, linear=0.0, quad=quad) + assert np.allclose(expected, actual, equal_nan=False) + + +def test_str_to_ufloat_parsing_nominal_and_esd(): + import easydiffraction.utils.utils as MUT + + u = MUT.str_to_ufloat('3.566(2)') + expected = np.array([3.566, 0.002]) + actual = np.array([u.nominal_value, u.std_dev]) + assert np.allclose(expected, actual) + + +def test_str_to_ufloat_no_esd_defaults_nan(): + import easydiffraction.utils.utils as MUT + + u = MUT.str_to_ufloat('1.23') + expected_value = 1.23 + actual_value = u.nominal_value + # uncertainty is NaN when not specified + assert np.isclose(expected_value, actual_value) and np.isnan(u.std_dev) + + +def test_get_value_from_xye_header(tmp_path): + import easydiffraction.utils.utils as MUT + + text = 'DIFC = 123.45 two_theta = 67.89\nrest of file\n' + p = tmp_path / 'file.xye' + p.write_text(text) + expected_difc = 123.45 + expected_two_theta = 67.89 + actual = np.array([ + MUT.get_value_from_xye_header(p, 'DIFC'), + MUT.get_value_from_xye_header(p, 'two_theta'), + ]) + expected = np.array([expected_difc, expected_two_theta]) + assert np.allclose(expected, actual) + + +def test_validate_url_rejects_non_http_https(): + import easydiffraction.utils.utils as MUT + + with pytest.raises(ValueError): + MUT._validate_url('ftp://example.com/file') + + +def test_is_github_ci_env_true(monkeypatch): + import easydiffraction.utils.utils as MUT + + monkeypatch.setenv('GITHUB_ACTIONS', 'true') + expected = True + actual = MUT.is_github_ci() + assert expected == actual + + +def test_package_version_missing_package_returns_none(): + import easydiffraction.utils.utils as MUT + + expected = None + actual = MUT.package_version('__definitely_not_installed__') + assert expected == actual + + +def test_is_notebook_false_in_plain_env(monkeypatch): + import easydiffraction.utils.utils as MUT + + # Ensure no IPython and not PyCharm + monkeypatch.setattr(MUT, 'IPython', None) + monkeypatch.setenv('PYCHARM_HOSTED', '', prepend=False) + assert MUT.is_notebook() is False + + +def test_is_pycharm_and_is_colab(monkeypatch): + import easydiffraction.utils.utils as MUT + + # PyCharm + monkeypatch.setenv('PYCHARM_HOSTED', '1') + assert MUT.is_pycharm() is True + # Colab detection when module is absent -> False + assert MUT.is_colab() is False + + +def test_render_table_terminal_branch(capsys, monkeypatch): + import easydiffraction.utils.utils as MUT + + monkeypatch.setattr(MUT, 'is_notebook', lambda: False) + MUT.render_table(columns_data=[[1, 2], [3, 4]], columns_alignment=['left', 'left']) + out = capsys.readouterr().out + # fancy_outline uses box-drawing characters; accept a couple of expected ones + assert ('╒' in out and '╕' in out) or ('┌' in out and '┐' in out) + + +def test_fetch_tutorial_list_handles_missing_release(monkeypatch): + import easydiffraction.utils.utils as MUT + + # Force _get_release_info to return None twice (tag & latest) → empty list + monkeypatch.setattr(MUT, '_get_release_info', lambda tag: None) + notebooks = MUT.fetch_tutorial_list() + assert notebooks == [] + + +def test_fetch_tutorial_list_no_asset(monkeypatch): + import easydiffraction.utils.utils as MUT + + release_info = {'assets': []} + monkeypatch.setattr(MUT, '_get_release_info', lambda tag: release_info) + notebooks = MUT.fetch_tutorial_list() + assert notebooks == [] + + +def test_show_version_prints(capsys, monkeypatch): + import easydiffraction.utils.utils as MUT + + monkeypatch.setattr(MUT, 'package_version', lambda name: '1.2.3+abc') + MUT.show_version() + out = capsys.readouterr().out + assert '1.2.3+abc' in out + + +def test_extract_notebooks_from_asset_with_inmemory_zip(monkeypatch): + import io + import zipfile + + import easydiffraction.utils.utils as MUT + + # Build an in-memory zip with .ipynb files + mem = io.BytesIO() + with zipfile.ZipFile(mem, 'w') as zf: + zf.writestr('dir/a.ipynb', '{}') + zf.writestr('b.ipynb', '{}') + zf.writestr('c.txt', 'x') + data = mem.getvalue() + + class DummyResp: + def __init__(self, b): + self._b = b + + def read(self): + return self._b + + def __enter__(self): + return self + + def __exit__(self, *args): + return False + + monkeypatch.setattr(MUT, '_safe_urlopen', lambda url: DummyResp(data)) + out = MUT._extract_notebooks_from_asset('https://example.com/tut.zip') + # returns sorted basenames of .ipynb + assert out == ['a.ipynb', 'b.ipynb'] diff --git a/tests/unit/experiments/collections/test_background.py b/tests/unit/experiments/collections/test_background.py deleted file mode 100644 index 0e6fc527..00000000 --- a/tests/unit/experiments/collections/test_background.py +++ /dev/null @@ -1,106 +0,0 @@ -from unittest.mock import patch - -import numpy as np -import pytest - -from easydiffraction.experiments.collections.background import BackgroundFactory -from easydiffraction.experiments.collections.background import ChebyshevPolynomialBackground -from easydiffraction.experiments.collections.background import LineSegmentBackground -from easydiffraction.experiments.collections.background import Point -from easydiffraction.experiments.collections.background import PolynomialTerm - - -def test_point_initialization(): - point = Point(x=1.0, y=2.0) - assert point.x.value == 1.0 - assert point.y.value == 2.0 - assert point.cif_category_key == 'pd_background' - assert point.category_key == 'background' - assert point._entry_id == '1.0' - - -def test_polynomial_term_initialization(): - term = PolynomialTerm(order=2, coef=3.0) - assert term.order.value == 2 - assert term.coef.value == 3.0 - assert term.cif_category_key == 'pd_background' - assert term.category_key == 'background' - assert term._entry_id == '2' - - -def test_line_segment_background_add_and_calculate(): - background = LineSegmentBackground() - background.add(1.0, 2.0) - background.add(3.0, 4.0) - - x_data = np.array([1.0, 2.0, 3.0]) - y_data = background.calculate(x_data) - - assert np.array_equal(y_data, np.array([2.0, 3.0, 4.0])) - - -def test_line_segment_background_calculate_no_points(): - background = LineSegmentBackground() - x_data = np.array([1.0, 2.0, 3.0]) - - with patch('builtins.print') as mock_print: - y_data = background.calculate(x_data) - assert np.array_equal(y_data, np.zeros_like(x_data)) - assert 'No background points found. Setting background to zero.' in str(mock_print.call_args.args[0]) - - -def test_line_segment_background_show(capsys): - background = LineSegmentBackground() - background.add(1.0, 2.0) - background.add(3.0, 4.0) - - background.show() - captured = capsys.readouterr() - assert 'Line-segment background points' in captured.out - - -def test_chebyshev_polynomial_background_add_and_calculate(): - background = ChebyshevPolynomialBackground() - background.add(order=0, coef=1.0) - background.add(order=1, coef=2.0) - - x_data = np.array([0.0, 0.5, 1.0]) - y_data = background.calculate(x_data) - - # Expected values are calculated using the Chebyshev polynomial formula - u = (x_data - x_data.min()) / (x_data.max() - x_data.min()) * 2 - 1 - expected_y = 1.0 + 2.0 * u - assert np.allclose(y_data, expected_y) - - -def test_chebyshev_polynomial_background_calculate_no_terms(): - background = ChebyshevPolynomialBackground() - x_data = np.array([0.0, 0.5, 1.0]) - - with patch('builtins.print') as mock_print: - y_data = background.calculate(x_data) - assert np.array_equal(y_data, np.zeros_like(x_data)) - assert 'No background points found. Setting background to zero.' in str(mock_print.call_args.args[0]) - - -def test_chebyshev_polynomial_background_show(capsys): - background = ChebyshevPolynomialBackground() - background.add(order=0, coef=1.0) - background.add(order=1, coef=2.0) - - background.show() - captured = capsys.readouterr() - assert 'Chebyshev polynomial background terms' in captured.out - - -def test_background_factory_create_supported_types(): - line_segment_background = BackgroundFactory.create('line-segment') - assert isinstance(line_segment_background, LineSegmentBackground) - - chebyshev_background = BackgroundFactory.create('chebyshev polynomial') - assert isinstance(chebyshev_background, ChebyshevPolynomialBackground) - - -def test_background_factory_create_unsupported_type(): - with pytest.raises(ValueError, match="Unsupported background type: 'unsupported'.*"): - BackgroundFactory.create('unsupported') diff --git a/tests/unit/experiments/collections/test_datastore.py b/tests/unit/experiments/collections/test_datastore.py deleted file mode 100644 index ab1b870a..00000000 --- a/tests/unit/experiments/collections/test_datastore.py +++ /dev/null @@ -1,181 +0,0 @@ -import numpy as np -import pytest - -from easydiffraction.experiments.datastore import DatastoreFactory -from easydiffraction.experiments.datastore import PowderDatastore -from easydiffraction.experiments.datastore import SingleCrystalDatastore - - -def test_powder_datastore_init(): - ds = PowderDatastore(beam_mode='constant wavelength') - assert ds.meas is None - assert ds.meas_su is None - assert ds.calc is None - - assert ds.beam_mode == 'constant wavelength' - assert ds.x is None - assert ds.d is None - assert ds.bkg is None - - -def test_powder_datastore_calc(): - ds = PowderDatastore() - with pytest.raises(TypeError): - ds.calc = [1, 2, 3] # Should raise TypeError because list is not allowed - arr = np.array([1, 2, 3]) - ds.calc = arr - assert np.array_equal(ds.calc, arr) - - -def test_single_crystal_datastore_init(): - ds = SingleCrystalDatastore() - assert ds.meas is None - assert ds.meas_su is None - assert ds.calc is None - - assert ds.sin_theta_over_lambda is None - assert ds.index_h is None - assert ds.index_k is None - assert ds.index_l is None - - -def test_datastore_factory_create_powder(): - ds = DatastoreFactory.create(sample_form='powder') - assert isinstance(ds, PowderDatastore) - - -def test_datastore_factory_create_single_crystal(): - ds = DatastoreFactory.create(sample_form='single crystal') - assert isinstance(ds, SingleCrystalDatastore) - - -def test_datastore_factory_create_powder_time_of_flight(): - ds = DatastoreFactory.create(sample_form='powder', beam_mode='time-of-flight') - assert isinstance(ds, PowderDatastore) - assert ds.beam_mode == 'time-of-flight' - - -def test_datastore_factory_create_powder_constant_wavelength(): - ds = DatastoreFactory.create(sample_form='powder', beam_mode='constant wavelength') - assert isinstance(ds, PowderDatastore) - assert ds.beam_mode == 'constant wavelength' - - -def test_datastore_factory_create_invalid_sample_form(): - with pytest.raises(ValueError, match="Unsupported sample form: 'invalid'"): - DatastoreFactory.create(sample_form='invalid') - - -def test_datastore_factory_create_invalid_beam_mode(): - with pytest.raises(ValueError, match="Unsupported beam mode: 'invalid'"): - DatastoreFactory.create(beam_mode='invalid') - - -def test_datastore_factory_cif_mapping_powder_time_of_flight(): - ds = DatastoreFactory.create( - sample_form='powder', - beam_mode='time-of-flight', - ) - desired = { - 'x': '_pd_meas.time_of_flight', - 'meas': '_pd_meas.intensity_total', - 'meas_su': '_pd_meas.intensity_total_su', - } - actual = ds._cif_mapping() - assert actual == desired - - -def test_datastore_factory_cif_mapping_powder_constant_wavelength(): - ds = DatastoreFactory.create( - sample_form='powder', - beam_mode='constant wavelength', - ) - desired = { - 'x': '_pd_meas.2theta_scan', - 'meas': '_pd_meas.intensity_total', - 'meas_su': '_pd_meas.intensity_total_su', - } - actual = ds._cif_mapping() - assert actual == desired - - -def test_datastore_factory_cif_mapping_single_crystal(): - ds = DatastoreFactory.create( - sample_form='single crystal', - beam_mode='constant wavelength', - ) - desired = { - 'index_h': '_refln.index_h', - 'index_k': '_refln.index_k', - 'index_l': '_refln.index_l', - 'meas': '_refln.intensity_meas', - 'meas_su': '_refln.intensity_meas_su', - } - actual = ds._cif_mapping() - assert actual == desired - - -def test_powder_as_cif_constant_wavelength(): - ds = PowderDatastore(beam_mode='constant wavelength') - ds.x = np.array([1.0, 2.0, 3.0]) - ds.meas = np.array([10.0, 20.0, 30.0]) - ds.meas_su = np.array([0.1, 0.2, 0.3]) - ds.bkg = np.array([0.5, 0.5, 0.5]) - cif = ds.as_cif() - assert '_pd_meas.2theta_scan' in cif - assert '_pd_meas.intensity_total' in cif - assert '_pd_meas.intensity_total_su' in cif - - -def test_powder_as_cif_time_of_flight(): - ds = PowderDatastore(beam_mode='time-of-flight') - ds.x = np.array([0.5, 1.0, 1.5]) - ds.meas = np.array([15.0, 25.0, 35.0]) - ds.meas_su = np.array([0.15, 0.25, 0.35]) - ds.bkg = np.array([0.4, 0.4, 0.4]) - cif = ds.as_cif() - assert '_pd_meas.time_of_flight' in cif - assert '_pd_meas.intensity_total' in cif - assert '_pd_meas.intensity_total_su' in cif - - -def test_single_crystal_as_cif(): - ds = SingleCrystalDatastore() - ds.sin_theta_over_lambda = np.array([0.1, 0.2]) - ds.index_h = np.array([1, 0]) - ds.index_k = np.array([0, 1]) - ds.index_l = np.array([0, 0]) - ds.meas = np.array([100, 200]) - ds.meas_su = np.array([10, 20]) - cif = ds.as_cif() - assert '_refln.index_h' in cif - assert '_refln.index_k' in cif - assert '_refln.index_l' in cif - assert '_refln.intensity_meas' in cif - assert '_refln.intensity_meas_su' in cif - - -def test_as_cif_truncation(): - ds = PowderDatastore() - ds.x = np.arange(10) - ds.meas = np.arange(10) * 10 - ds.meas_su = np.arange(10) * 0.1 - ds.bkg = np.arange(10) * 0.5 - - cif = ds.as_cif(max_points=2) - - # It should contain CIF headers - assert '_pd_meas.2theta_scan' in cif - assert '_pd_meas.intensity_total' in cif - assert '_pd_meas.intensity_total_su' in cif - - # It should contain first 2 and last 2 rows, but not the middle - assert '0 0 0.0' in cif - assert '1 10 0.1' in cif - assert '3 20 0.2' not in cif - assert '7 70 0.7' not in cif - assert '8 80 0.8' in cif - assert '9 90 0.9' in cif - - # Ellipsis should indicate truncation - assert '...' in cif diff --git a/tests/unit/experiments/collections/test_linked_phases.py b/tests/unit/experiments/collections/test_linked_phases.py deleted file mode 100644 index 367e083f..00000000 --- a/tests/unit/experiments/collections/test_linked_phases.py +++ /dev/null @@ -1,46 +0,0 @@ -from easydiffraction.experiments.collections.linked_phases import LinkedPhase -from easydiffraction.experiments.collections.linked_phases import LinkedPhases - - -def test_linked_phase_category_key(): - lp = LinkedPhase(id='phase1', scale=1.0) - assert lp.category_key == 'linked_phases' - - -def test_linked_phase_cif_category_key(): - lp = LinkedPhase(id='phase1', scale=1.0) - assert lp.cif_category_key == 'pd_phase_block' - - -def test_linked_phase_init(): - lp = LinkedPhase(id='phase1', scale=1.23) - assert lp.id.value == 'phase1' - assert lp.id.name == 'id' - assert lp.id.cif_name == 'id' - assert lp.scale.value == 1.23 - assert lp.scale.name == 'scale' - assert lp.scale.cif_name == 'scale' - - -def test_linked_phases_type(): - lps = LinkedPhases() - assert lps._type == 'category' - - -def test_linked_phases_child_class(): - lps = LinkedPhases() - assert lps._child_class is LinkedPhase - - -def test_linked_phases_init_empty(): - lps = LinkedPhases() - assert len(lps._items) == 0 - - -def test_linked_phases_add(): - lps = LinkedPhases() - lps.add(id='phase1', scale=1.0) - lps.add(id='phase2', scale=2.0) - assert len(lps._items) == 2 - assert lps['phase1'].scale.value == 1.0 - assert lps['phase2'].scale.value == 2.0 diff --git a/tests/unit/experiments/components/test_experiment_type.py b/tests/unit/experiments/components/test_experiment_type.py deleted file mode 100644 index 68fac38f..00000000 --- a/tests/unit/experiments/components/test_experiment_type.py +++ /dev/null @@ -1,54 +0,0 @@ -from easydiffraction.core.objects import Descriptor -from easydiffraction.experiments.components.experiment_type import ExperimentType - - -def test_experiment_type_initialization(): - experiment_type = ExperimentType( - sample_form='powder', - beam_mode='constant wavelength', - radiation_probe='neutron', - scattering_type='bragg', - ) - - assert isinstance(experiment_type.sample_form, Descriptor) - assert experiment_type.sample_form.value == 'powder' - assert experiment_type.sample_form.name == 'sample_form' - assert experiment_type.sample_form.cif_name == 'sample_form' - - assert isinstance(experiment_type.beam_mode, Descriptor) - assert experiment_type.beam_mode.value == 'constant wavelength' - assert experiment_type.beam_mode.name == 'beam_mode' - assert experiment_type.beam_mode.cif_name == 'beam_mode' - - assert isinstance(experiment_type.radiation_probe, Descriptor) - assert experiment_type.radiation_probe.value == 'neutron' - assert experiment_type.radiation_probe.name == 'radiation_probe' - assert experiment_type.radiation_probe.cif_name == 'radiation_probe' - - -def test_experiment_type_properties(): - experiment_type = ExperimentType( - sample_form='single crystal', - beam_mode='time-of-flight', - radiation_probe='xray', - scattering_type='bragg', - ) - - assert experiment_type.category_key == 'expt_type' - assert experiment_type.cif_category_key == 'expt_type' - assert experiment_type.datablock_id is None - assert experiment_type.entry_id is None - assert experiment_type._locked is True - - -def no_test_experiment_type_locking_attributes(): - # TODO: hmm this doesn't work as expected. - experiment_type = ExperimentType( - sample_form='powder', - beam_mode='constant wavelength', - radiation_probe='neutron', - scattering_type='bragg', - ) - experiment_type._locked = True # Disallow adding new attributes - experiment_type.new_attribute = 'value' - assert not hasattr(experiment_type, 'new_attribute') diff --git a/tests/unit/experiments/components/test_instrument.py b/tests/unit/experiments/components/test_instrument.py deleted file mode 100644 index 084dc41e..00000000 --- a/tests/unit/experiments/components/test_instrument.py +++ /dev/null @@ -1,85 +0,0 @@ -import pytest - -from easydiffraction.core.objects import Parameter -from easydiffraction.experiments.components.instrument import ConstantWavelengthInstrument -from easydiffraction.experiments.components.instrument import InstrumentBase -from easydiffraction.experiments.components.instrument import InstrumentFactory -from easydiffraction.experiments.components.instrument import TimeOfFlightInstrument - - -def test_instrument_base_properties(): - instrument = InstrumentBase() - assert instrument.category_key == 'instrument' - assert instrument.cif_category_key == 'instr' - assert instrument._entry_id is None - - -def test_constant_wavelength_instrument_initialization(): - instrument = ConstantWavelengthInstrument(setup_wavelength=1.5406, calib_twotheta_offset=0.1) - - assert isinstance(instrument.setup_wavelength, Parameter) - assert instrument.setup_wavelength.value == 1.5406 - assert instrument.setup_wavelength.name == 'wavelength' - assert instrument.setup_wavelength.cif_name == 'wavelength' - assert instrument.setup_wavelength.units == 'Å' - - assert isinstance(instrument.calib_twotheta_offset, Parameter) - assert instrument.calib_twotheta_offset.value == 0.1 - assert instrument.calib_twotheta_offset.name == 'twotheta_offset' - assert instrument.calib_twotheta_offset.cif_name == '2theta_offset' - assert instrument.calib_twotheta_offset.units == 'deg' - - -def test_time_of_flight_instrument_initialization(): - instrument = TimeOfFlightInstrument( - setup_twotheta_bank=150.0, - calib_d_to_tof_offset=0.5, - calib_d_to_tof_linear=10000.0, - calib_d_to_tof_quad=-1.0, - calib_d_to_tof_recip=0.1, - ) - - assert isinstance(instrument.setup_twotheta_bank, Parameter) - assert instrument.setup_twotheta_bank.value == 150.0 - assert instrument.setup_twotheta_bank.name == 'twotheta_bank' - assert instrument.setup_twotheta_bank.cif_name == '2theta_bank' - assert instrument.setup_twotheta_bank.units == 'deg' - - assert isinstance(instrument.calib_d_to_tof_offset, Parameter) - assert instrument.calib_d_to_tof_offset.value == 0.5 - assert instrument.calib_d_to_tof_offset.name == 'd_to_tof_offset' - assert instrument.calib_d_to_tof_offset.cif_name == 'd_to_tof_offset' - assert instrument.calib_d_to_tof_offset.units == 'µs' - - assert isinstance(instrument.calib_d_to_tof_linear, Parameter) - assert instrument.calib_d_to_tof_linear.value == 10000.0 - assert instrument.calib_d_to_tof_linear.name == 'd_to_tof_linear' - assert instrument.calib_d_to_tof_linear.cif_name == 'd_to_tof_linear' - assert instrument.calib_d_to_tof_linear.units == 'µs/Å' - - assert isinstance(instrument.calib_d_to_tof_quad, Parameter) - assert instrument.calib_d_to_tof_quad.value == -1.0 - assert instrument.calib_d_to_tof_quad.name == 'd_to_tof_quad' - assert instrument.calib_d_to_tof_quad.cif_name == 'd_to_tof_quad' - assert instrument.calib_d_to_tof_quad.units == 'µs/Ų' - - assert isinstance(instrument.calib_d_to_tof_recip, Parameter) - assert instrument.calib_d_to_tof_recip.value == 0.1 - assert instrument.calib_d_to_tof_recip.name == 'd_to_tof_recip' - assert instrument.calib_d_to_tof_recip.cif_name == 'd_to_tof_recip' - assert instrument.calib_d_to_tof_recip.units == 'µs·Å' - - -def test_instrument_factory_create_constant_wavelength(): - instrument = InstrumentFactory.create(beam_mode='constant wavelength') - assert isinstance(instrument, ConstantWavelengthInstrument) - - -def test_instrument_factory_create_time_of_flight(): - instrument = InstrumentFactory.create(beam_mode='time-of-flight') - assert isinstance(instrument, TimeOfFlightInstrument) - - -def test_instrument_factory_create_invalid_beam_mode(): - with pytest.raises(ValueError, match="Unsupported beam mode: 'invalid'.*"): - InstrumentFactory.create(beam_mode='invalid') diff --git a/tests/unit/experiments/components/test_peak.py b/tests/unit/experiments/components/test_peak.py deleted file mode 100644 index 1de1c15d..00000000 --- a/tests/unit/experiments/components/test_peak.py +++ /dev/null @@ -1,145 +0,0 @@ -import pytest - -from easydiffraction.core.objects import Parameter -from easydiffraction.experiments.components.peak import ConstantWavelengthBroadeningMixin -from easydiffraction.experiments.components.peak import ConstantWavelengthPseudoVoigt -from easydiffraction.experiments.components.peak import ConstantWavelengthSplitPseudoVoigt -from easydiffraction.experiments.components.peak import ConstantWavelengthThompsonCoxHastings -from easydiffraction.experiments.components.peak import EmpiricalAsymmetryMixin -from easydiffraction.experiments.components.peak import FcjAsymmetryMixin -from easydiffraction.experiments.components.peak import IkedaCarpenterAsymmetryMixin -from easydiffraction.experiments.components.peak import PeakBase -from easydiffraction.experiments.components.peak import PeakFactory -from easydiffraction.experiments.components.peak import TimeOfFlightBroadeningMixin -from easydiffraction.experiments.components.peak import TimeOfFlightPseudoVoigt -from easydiffraction.experiments.components.peak import TimeOfFlightPseudoVoigtBackToBack -from easydiffraction.experiments.components.peak import TimeOfFlightPseudoVoigtIkedaCarpenter - - -# --- Tests for Mixins --- -def test_constant_wavelength_broadening_mixin(): - class TestClass(ConstantWavelengthBroadeningMixin): - def __init__(self): - self._add_constant_wavelength_broadening() - - obj = TestClass() - assert isinstance(obj.broad_gauss_u, Parameter) - assert obj.broad_gauss_u.value == 0.01 - assert obj.broad_gauss_v.value == -0.01 - assert obj.broad_gauss_w.value == 0.02 - assert obj.broad_lorentz_x.value == 0.0 - assert obj.broad_lorentz_y.value == 0.0 - - -def test_time_of_flight_broadening_mixin(): - class TestClass(TimeOfFlightBroadeningMixin): - def __init__(self): - self._add_time_of_flight_broadening() - - obj = TestClass() - assert isinstance(obj.broad_gauss_sigma_0, Parameter) - assert obj.broad_gauss_sigma_0.value == 0.0 - assert obj.broad_gauss_sigma_1.value == 0.0 - assert obj.broad_gauss_sigma_2.value == 0.0 - assert obj.broad_lorentz_gamma_0.value == 0.0 - assert obj.broad_lorentz_gamma_1.value == 0.0 - assert obj.broad_lorentz_gamma_2.value == 0.0 - assert obj.broad_mix_beta_0.value == 0.0 - assert obj.broad_mix_beta_1.value == 0.0 - - -def test_empirical_asymmetry_mixin(): - class TestClass(EmpiricalAsymmetryMixin): - def __init__(self): - self._add_empirical_asymmetry() - - obj = TestClass() - assert isinstance(obj.asym_empir_1, Parameter) - assert obj.asym_empir_1.value == 0.1 - assert obj.asym_empir_2.value == 0.2 - assert obj.asym_empir_3.value == 0.3 - assert obj.asym_empir_4.value == 0.4 - - -def test_fcj_asymmetry_mixin(): - class TestClass(FcjAsymmetryMixin): - def __init__(self): - self._add_fcj_asymmetry() - - obj = TestClass() - assert isinstance(obj.asym_fcj_1, Parameter) - assert obj.asym_fcj_1.value == 0.01 - assert obj.asym_fcj_2.value == 0.02 - - -def test_ikeda_carpenter_asymmetry_mixin(): - class TestClass(IkedaCarpenterAsymmetryMixin): - def __init__(self): - self._add_ikeda_carpenter_asymmetry() - - obj = TestClass() - assert isinstance(obj.asym_alpha_0, Parameter) - assert obj.asym_alpha_0.value == 0.01 - assert obj.asym_alpha_1.value == 0.02 - - -# --- Tests for Base and Derived Peak Classes --- -def test_peak_base_properties(): - peak = PeakBase() - assert peak.cif_category_key == 'peak' - assert peak.category_key == 'peak' - assert peak._entry_id is None - - -def test_constant_wavelength_pseudo_voigt_initialization(): - peak = ConstantWavelengthPseudoVoigt() - assert isinstance(peak.broad_gauss_u, Parameter) - assert peak.broad_gauss_u.value == 0.01 - - -def test_constant_wavelength_split_pseudo_voigt_initialization(): - peak = ConstantWavelengthSplitPseudoVoigt() - assert isinstance(peak.broad_gauss_u, Parameter) - assert isinstance(peak.asym_empir_1, Parameter) - assert peak.asym_empir_1.value == 0.1 - - -def test_constant_wavelength_thompson_cox_hastings_initialization(): - peak = ConstantWavelengthThompsonCoxHastings() - assert isinstance(peak.broad_gauss_u, Parameter) - assert isinstance(peak.asym_fcj_1, Parameter) - assert peak.asym_fcj_1.value == 0.01 - - -def test_time_of_flight_pseudo_voigt_initialization(): - peak = TimeOfFlightPseudoVoigt() - assert isinstance(peak.broad_gauss_sigma_0, Parameter) - assert peak.broad_gauss_sigma_0.value == 0.0 - - -def test_time_of_flight_pseudo_voigt_ikeda_carpenter_initialization(): - peak = TimeOfFlightPseudoVoigtIkedaCarpenter() - assert isinstance(peak.broad_gauss_sigma_0, Parameter) - assert isinstance(peak.asym_alpha_0, Parameter) - - -def test_time_of_flight_pseudo_voigt_back_to_back_exponential_initialization(): - peak = TimeOfFlightPseudoVoigtBackToBack() - assert isinstance(peak.broad_gauss_sigma_0, Parameter) - assert isinstance(peak.asym_alpha_0, Parameter) - - -# --- Tests for PeakFactory --- -def test_peak_factory_create_constant_wavelength_pseudo_voigt(): - peak = PeakFactory.create(beam_mode='constant wavelength', profile_type='pseudo-voigt') - assert isinstance(peak, ConstantWavelengthPseudoVoigt) - - -def test_peak_factory_create_invalid_beam_mode(): - with pytest.raises(ValueError, match="Unsupported beam mode: 'invalid'.*"): - PeakFactory.create(beam_mode='invalid', profile_type='pseudo-voigt') - - -def test_peak_factory_create_invalid_profile_type(): - with pytest.raises(ValueError, match="Unsupported profile type 'invalid' for beam mode 'constant wavelength'.*"): - PeakFactory.create(beam_mode='constant wavelength', profile_type='invalid') diff --git a/tests/unit/experiments/test_experiment.py b/tests/unit/experiments/test_experiment.py deleted file mode 100644 index 8345a92d..00000000 --- a/tests/unit/experiments/test_experiment.py +++ /dev/null @@ -1,154 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import numpy as np -import pytest - -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.components.experiment_type import ExperimentType -from easydiffraction.experiments.components.experiment_type import RadiationProbeEnum -from easydiffraction.experiments.components.experiment_type import SampleFormEnum -from easydiffraction.experiments.components.experiment_type import ScatteringTypeEnum -from easydiffraction.experiments.experiment import BaseExperiment -from easydiffraction.experiments.experiment import Experiment -from easydiffraction.experiments.experiment import ExperimentFactory -from easydiffraction.experiments.experiment import PowderExperiment -from easydiffraction.experiments.experiment import SingleCrystalExperiment - - -@pytest.fixture -def expt_type(): - return ExperimentType( - sample_form=SampleFormEnum.default(), - beam_mode=BeamModeEnum.default(), - radiation_probe='xray', - scattering_type='bragg', - ) - - -class ConcreteBaseExperiment(BaseExperiment): - """Concrete implementation of BaseExperiment for testing.""" - - def _load_ascii_data_to_experiment(self, data_path): - pass - - def show_meas_chart(self, x_min=None, x_max=None): - pass - - -class ConcreteSingleCrystalExperiment(SingleCrystalExperiment): - """Concrete implementation of SingleCrystalExperiment for - testing.""" - - def _load_ascii_data_to_experiment(self, data_path): - pass - - -def test_base_experiment_initialization(expt_type): - experiment = ConcreteBaseExperiment(name='TestExperiment', type=expt_type) - assert experiment.name == 'TestExperiment' - assert experiment.type == expt_type - - -def test_powder_experiment_initialization(expt_type): - experiment = PowderExperiment(name='PowderTest', type=expt_type) - assert experiment.name == 'PowderTest' - assert experiment.type == expt_type - assert experiment.background is not None - assert experiment.peak is not None - assert experiment.linked_phases is not None - - -def test_powder_experiment_load_ascii_data(expt_type): - experiment = PowderExperiment(name='PowderTest', type=expt_type) - experiment.datastore = MagicMock() - mock_data = np.array([[1.0, 2.0, 0.1], [2.0, 3.0, 0.2]]) - with patch('numpy.loadtxt', return_value=mock_data): - experiment._load_ascii_data_to_experiment('mock_path') - assert np.array_equal(experiment.datastore.x, mock_data[:, 0]) - assert np.array_equal(experiment.datastore.meas, mock_data[:, 1]) - assert np.array_equal(experiment.datastore.meas_su, mock_data[:, 2]) - - -def test_single_crystal_experiment_initialization(expt_type): - experiment = ConcreteSingleCrystalExperiment(name='SingleCrystalTest', type=expt_type) - assert experiment.name == 'SingleCrystalTest' - assert experiment.type == expt_type - assert experiment.linked_crystal is None - - -def test_single_crystal_experiment_show_meas_chart(expt_type): - experiment = ConcreteSingleCrystalExperiment(name='SingleCrystalTest', type=expt_type) - with patch('builtins.print') as mock_print: - experiment.show_meas_chart() - mock_print.assert_called_once_with('Showing measured data chart is not implemented yet.') - - -def test_experiment_factory_create_powder(): - experiment = ExperimentFactory.create( - name='PowderTest', - sample_form=SampleFormEnum.POWDER.value, - beam_mode=BeamModeEnum.default().value, - radiation_probe=RadiationProbeEnum.default().value, - scattering_type=ScatteringTypeEnum.default().value, - ) - assert isinstance(experiment, PowderExperiment) - assert experiment.name == 'PowderTest' - - -# to be added once single crystal works -def no_test_experiment_factory_create_single_crystal(): - experiment = ExperimentFactory.create( - name='SingleCrystalTest', - sample_form=SampleFormEnum.SINGLE_CRYSTAL.value, - beam_mode=BeamModeEnum.default().value, - radiation_probe=RadiationProbeEnum.default().value, - ) - assert isinstance(experiment, SingleCrystalExperiment) - assert experiment.name == 'SingleCrystalTest' - - -def test_experiment_method(): - mock_data = np.array([[1.0, 2.0, 0.1], [2.0, 3.0, 0.2]]) - with patch('numpy.loadtxt', return_value=mock_data): - experiment = Experiment( - name='ExperimentTest', - sample_form='powder', - beam_mode=BeamModeEnum.default().value, - radiation_probe=RadiationProbeEnum.default().value, - data_path='mock_path', - ) - assert isinstance(experiment, PowderExperiment) - assert experiment.name == 'ExperimentTest' - assert np.array_equal(experiment.datastore.x, mock_data[:, 0]) - assert np.array_equal(experiment.datastore.meas, mock_data[:, 1]) - assert np.array_equal(experiment.datastore.meas_su, mock_data[:, 2]) - - -def test_experiment_factory_invalid_args_missing_required(): - # Missing required 'name' - with pytest.raises(ValueError, match='Invalid argument combination'): - ExperimentFactory.create( - sample_form=SampleFormEnum.POWDER.value, - beam_mode=BeamModeEnum.default().value, - radiation_probe=RadiationProbeEnum.default().value, - scattering_type=ScatteringTypeEnum.default().value, - ) - - -def test_experiment_factory_conflicting_args_cif_and_name(): - # Conflicting: 'cif_path' with 'name' - with pytest.raises(ValueError, match='Invalid argument combination'): - ExperimentFactory.create(name='ConflictTest', cif_path='path/to/file.cif') - - -def test_experiment_factory_conflicting_args_data_and_cif(): - # Conflicting: multiple conflicting input sources - with pytest.raises(ValueError, match='Invalid argument combination'): - ExperimentFactory.create(name='ConflictTest', data_path='mock_path', cif_str='cif content') - - -def test_experiment_factory_invalid_args_unsupported_key(): - # Unsupported keyword - with pytest.raises(ValueError, match='Invalid argument combination'): - ExperimentFactory.create(foo='bar') diff --git a/tests/unit/experiments/test_experiments.py b/tests/unit/experiments/test_experiments.py deleted file mode 100644 index 7c346d0a..00000000 --- a/tests/unit/experiments/test_experiments.py +++ /dev/null @@ -1,86 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -from easydiffraction.experiments.experiment import BaseExperiment -from easydiffraction.experiments.experiments import Experiments - - -class ConcreteBaseExperiment(BaseExperiment): - """Concrete implementation of BaseExperiment for testing.""" - - def _load_ascii_data_to_experiment(self, data_path): - pass - - def show_meas_chart(self, x_min=None, x_max=None): - pass - - -def test_experiments_initialization(): - experiments = Experiments() - assert isinstance(experiments, Experiments) - assert len(experiments.ids) == 0 - - -def test_experiments_add_prebuilt_experiment(): - experiments = Experiments() - mock_experiment = MagicMock(spec=BaseExperiment) - mock_experiment.name = 'TestExperiment' - - experiments.add(experiment=mock_experiment) - assert 'TestExperiment' in experiments.ids - assert experiments._experiments['TestExperiment'] == mock_experiment - - -def test_experiments_add_from_data_path(): - experiments = Experiments() - mock_experiment = MagicMock(spec=ConcreteBaseExperiment) - mock_experiment.name = 'TestExperiment' - - with patch('easydiffraction.experiments.experiment.ExperimentFactory.create', return_value=mock_experiment): - experiments.add_from_data_path( - name='TestExperiment', - sample_form='powder', - beam_mode='constant wavelength', - radiation_probe='xray', - data_path='mock_path', - ) - - assert 'TestExperiment' in experiments.ids - assert experiments['TestExperiment'] == mock_experiment - - -def test_experiments_remove(): - experiments = Experiments() - mock_experiment = MagicMock(spec=BaseExperiment) - mock_experiment.name = 'TestExperiment' - - experiments.add(experiment=mock_experiment) - assert 'TestExperiment' in experiments.ids - - experiments.remove('TestExperiment') - assert 'TestExperiment' not in experiments.ids - - -def test_experiments_show_names(capsys): - experiments = Experiments() - mock_experiment = MagicMock(spec=BaseExperiment) - mock_experiment.name = 'TestExperiment' - - experiments.add(experiment=mock_experiment) - experiments.show_names() - - captured = capsys.readouterr() - assert 'Defined experiments 🔬' in captured.out - assert 'TestExperiment' in captured.out - - -def test_experiments_as_cif(): - experiments = Experiments() - mock_experiment = MagicMock(spec=BaseExperiment) - mock_experiment.name = 'TestExperiment' - mock_experiment.as_cif.return_value = 'mock_cif_content' - - experiments.add(experiment=mock_experiment) - cif_output = experiments.as_cif() - - assert 'mock_cif_content' in cif_output diff --git a/tests/unit/sample_models/collections/test_atom_sites.py b/tests/unit/sample_models/collections/test_atom_sites.py deleted file mode 100644 index 5514e5b6..00000000 --- a/tests/unit/sample_models/collections/test_atom_sites.py +++ /dev/null @@ -1,86 +0,0 @@ -import pytest - -from easydiffraction.sample_models.collections.atom_sites import AtomSite -from easydiffraction.sample_models.collections.atom_sites import AtomSites - - -def test_atom_site_initialization(): - atom_site = AtomSite( - label='O1', - type_symbol='O', - fract_x=0.1, - fract_y=0.2, - fract_z=0.3, - wyckoff_letter='a', - occupancy=0.8, - b_iso=1.2, - adp_type='Biso', - ) - - # Assertions - assert atom_site.label.value == 'O1' - assert atom_site.type_symbol.value == 'O' - assert atom_site.fract_x.value == 0.1 - assert atom_site.fract_y.value == 0.2 - assert atom_site.fract_z.value == 0.3 - assert atom_site.wyckoff_letter.value == 'a' - assert atom_site.occupancy.value == 0.8 - assert atom_site.b_iso.value == 1.2 - assert atom_site.adp_type.value == 'Biso' - - -def test_atom_site_properties(): - atom_site = AtomSite(label='O1', type_symbol='O', fract_x=0.1, fract_y=0.2, fract_z=0.3) - - # Assertions - assert atom_site.cif_category_key == 'atom_site' - assert atom_site.category_key == 'atom_sites' - assert atom_site._entry_id == 'O1' - - -@pytest.fixture -def atom_sites_collection(): - return AtomSites() - - -def test_atom_sites_add(atom_sites_collection): - atom_sites_collection.add( - label='O1', - type_symbol='O', - fract_x=0.1, - fract_y=0.2, - fract_z=0.3, - wyckoff_letter='a', - occupancy=0.8, - b_iso=1.2, - adp_type='Biso', - ) - - # Assertions - assert 'O1' in atom_sites_collection._items - atom_site = atom_sites_collection._items['O1'] - assert isinstance(atom_site, AtomSite) - assert atom_site.label.value == 'O1' - assert atom_site.type_symbol.value == 'O' - assert atom_site.fract_x.value == 0.1 - assert atom_site.fract_y.value == 0.2 - assert atom_site.fract_z.value == 0.3 - assert atom_site.wyckoff_letter.value == 'a' - assert atom_site.occupancy.value == 0.8 - assert atom_site.b_iso.value == 1.2 - assert atom_site.adp_type.value == 'Biso' - - -def test_atom_sites_add_multiple(atom_sites_collection): - atom_sites_collection.add(label='O1', type_symbol='O', fract_x=0.1, fract_y=0.2, fract_z=0.3) - atom_sites_collection.add(label='C1', type_symbol='C', fract_x=0.4, fract_y=0.5, fract_z=0.6) - - # Assertions - assert 'O1' in atom_sites_collection._items - assert 'C1' in atom_sites_collection._items - assert len(atom_sites_collection._items) == 2 - - -def test_atom_sites_type(atom_sites_collection): - # Assertions - assert atom_sites_collection._type == 'category' diff --git a/tests/unit/sample_models/components/test_cell.py b/tests/unit/sample_models/components/test_cell.py deleted file mode 100644 index a7cdf2f8..00000000 --- a/tests/unit/sample_models/components/test_cell.py +++ /dev/null @@ -1,37 +0,0 @@ -from easydiffraction.sample_models.components.cell import Cell - - -def test_cell_initialization(): - cell = Cell(length_a=5.0, length_b=6.0, length_c=7.0, angle_alpha=80.0, angle_beta=85.0, angle_gamma=95.0) - - # Assertions - assert cell.length_a.value == 5.0 - assert cell.length_b.value == 6.0 - assert cell.length_c.value == 7.0 - assert cell.angle_alpha.value == 80.0 - assert cell.angle_beta.value == 85.0 - assert cell.angle_gamma.value == 95.0 - - assert cell.length_a.units == 'Å' - assert cell.angle_alpha.units == 'deg' - - -def test_cell_default_initialization(): - cell = Cell() - - # Assertions - assert cell.length_a.value == 10.0 - assert cell.length_b.value == 10.0 - assert cell.length_c.value == 10.0 - assert cell.angle_alpha.value == 90.0 - assert cell.angle_beta.value == 90.0 - assert cell.angle_gamma.value == 90.0 - - -def test_cell_properties(): - cell = Cell() - - # Assertions - assert cell.cif_category_key == 'cell' - assert cell.category_key == 'cell' - assert cell._entry_id is None diff --git a/tests/unit/sample_models/components/test_space_group.py b/tests/unit/sample_models/components/test_space_group.py deleted file mode 100644 index 5e0d6a1b..00000000 --- a/tests/unit/sample_models/components/test_space_group.py +++ /dev/null @@ -1,31 +0,0 @@ -from easydiffraction.sample_models.components.space_group import SpaceGroup - - -def test_space_group_initialization(): - space_group = SpaceGroup(name_h_m='P 2/m', it_coordinate_system_code=1) - - # Assertions - assert space_group.name_h_m.value == 'P 2/m' - assert space_group.name_h_m.name == 'name_h_m' - assert space_group.name_h_m.cif_name == 'name_H-M_alt' - - assert space_group.it_coordinate_system_code.value == 1 - assert space_group.it_coordinate_system_code.name == 'it_coordinate_system_code' - assert space_group.it_coordinate_system_code.cif_name == 'IT_coordinate_system_code' - - -def test_space_group_default_initialization(): - space_group = SpaceGroup() - - # Assertions - assert space_group.name_h_m.value == 'P 1' - assert space_group.it_coordinate_system_code.value is None - - -def test_space_group_properties(): - space_group = SpaceGroup() - - # Assertions - assert space_group.cif_category_key == 'space_group' - assert space_group.category_key == 'space_group' - assert space_group._entry_id is None diff --git a/tests/unit/sample_models/test_sample_models.py b/tests/unit/sample_models/test_sample_models.py deleted file mode 100644 index ce1f363b..00000000 --- a/tests/unit/sample_models/test_sample_models.py +++ /dev/null @@ -1,80 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import pytest - -from easydiffraction.sample_models.sample_models import SampleModel -from easydiffraction.sample_models.sample_models import SampleModels - - -@pytest.fixture -def mock_sample_model(): - with ( - patch('easydiffraction.sample_models.components.space_group.SpaceGroup') as mock_space_group, - patch('easydiffraction.sample_models.components.cell.Cell') as mock_cell, - patch('easydiffraction.sample_models.collections.atom_sites.AtomSites') as mock_atom_sites, - ): - space_group = mock_space_group.return_value - cell = mock_cell.return_value - atom_sites = mock_atom_sites.return_value - - # Mock attributes - space_group.name_h_m.value = 'P 1' - space_group.it_coordinate_system_code.value = 1 - cell.length_a.value = 1.0 - cell.length_b.value = 2.0 - cell.length_c.value = 3.0 - cell.angle_alpha.value = 90.0 - cell.angle_beta.value = 90.0 - cell.angle_gamma.value = 90.0 - atom_sites.__iter__.return_value = [] - - return SampleModel(name='test_model') - - -@pytest.fixture -def mock_sample_models(): - return SampleModels() - - -def test_sample_models_add(mock_sample_models, mock_sample_model): - mock_sample_models.add(model=mock_sample_model) - - # Assertions - assert 'test_model' in mock_sample_models.get_ids() - - -def test_sample_models_remove(mock_sample_models, mock_sample_model): - mock_sample_models.add(model=mock_sample_model) - mock_sample_models.remove('test_model') - - # Assertions - assert 'test_model' not in mock_sample_models.get_ids() - - -def test_sample_models_as_cif(mock_sample_models, mock_sample_model): - mock_sample_model.as_cif = MagicMock(return_value='data_test_model') - mock_sample_models.add(model=mock_sample_model) - - cif = mock_sample_models.as_cif() - - # Assertions - assert 'data_test_model' in cif - - -@patch('builtins.print') -def test_sample_models_show_names(mock_print, mock_sample_models, mock_sample_model): - mock_sample_models.add(model=mock_sample_model) - mock_sample_models.show_names() - - # Assertions - mock_print.assert_called_with(['test_model']) - - -@patch.object(SampleModel, 'show_params', autospec=True) -def test_sample_models_show_params(mock_show_params, mock_sample_models, mock_sample_model): - mock_sample_models.add(model=mock_sample_model) - mock_sample_models.show_params() - - # Assertions - mock_show_params.assert_called_once_with(mock_sample_model) diff --git a/tests/unit/test_project.py b/tests/unit/test_project.py deleted file mode 100644 index ba2f15b9..00000000 --- a/tests/unit/test_project.py +++ /dev/null @@ -1,212 +0,0 @@ -import datetime -import os -import pathlib -import time -from unittest.mock import MagicMock, ANY -from unittest.mock import patch - -from easydiffraction.analysis.analysis import Analysis -from easydiffraction.experiments.experiments import Experiments -from easydiffraction.project import Project -from easydiffraction.project import ProjectInfo -from easydiffraction.sample_models.sample_models import SampleModels -from easydiffraction.summary import Summary - - -def _normalize_posix(p: pathlib.Path) -> str: - """Return a normalized POSIX-style path string with a leading '/'. - - This makes tests robust across Windows and POSIX when using paths like - '/test/path' that, on Windows, become '\\test\\path'. We avoid relying on - drive letters or platform-specific semantics for these synthetic test paths. - """ - s = str(p).replace('\\', '/') - if not s.startswith('/'): - s = '/' + s.lstrip('/') - return s - -# ------------------------------------------ -# Tests for ProjectInfo -# ------------------------------------------ - - -def test_project_info_initialization(): - project_info = ProjectInfo() - - # Assertions - assert project_info.name == 'untitled_project' - assert project_info.title == 'Untitled Project' - assert project_info.description == '' - assert project_info.path == pathlib.Path.cwd() - assert isinstance(project_info.created, datetime.datetime) - assert isinstance(project_info.last_modified, datetime.datetime) - - -def test_project_info_setters(): - project_info = ProjectInfo() - - # Set values - project_info.name = 'test_project' - project_info.title = 'Test Project' - project_info.description = 'This is a test project.' - project_info.path = '/test/path' - - # Assertions - assert project_info.name == 'test_project' - assert project_info.title == 'Test Project' - assert project_info.description == 'This is a test project.' - # Use POSIX form for cross-platform consistency (Windows vs POSIX separators) - assert _normalize_posix(project_info.path) == '/test/path' - - -def test_project_info_update_last_modified(): - project_info = ProjectInfo() - initial_last_modified = project_info.last_modified - - # Add a small delay to ensure the timestamps differ - time.sleep(0.001) - project_info.update_last_modified() - - # Assertions - assert project_info.last_modified > initial_last_modified - - -def test_project_info_as_cif(): - project_info = ProjectInfo() - project_info.name = 'test_project' - project_info.title = 'Test Project' - project_info.description = 'This is a test project.' - - cif = project_info.as_cif() - - # Assertions - assert '_project.id test_project' in cif - assert "_project.title 'Test Project'" in cif - assert "_project.description 'This is a test project.'" in cif - - -@patch('builtins.print') -def test_project_info_show_as_cif(mock_print): - project_info = ProjectInfo() - project_info.name = 'test_project' - project_info.title = 'Test Project' - project_info.description = 'This is a test project.' - - project_info.show_as_cif() - - # Assertions - mock_print.assert_called() - - -# ------------------------------------------ -# Tests for Project -# ------------------------------------------ - - -def test_project_initialization(): - with ( - patch('easydiffraction.sample_models.sample_models.SampleModels'), - patch('easydiffraction.experiments.experiments.Experiments'), - patch('easydiffraction.analysis.analysis.Analysis'), - patch('easydiffraction.summary.Summary'), - ): - project = Project() # Directly assign the instance to a variable - - # Assertions - assert project.name == 'untitled_project' - assert isinstance(project.sample_models, SampleModels) - assert isinstance(project.experiments, Experiments) - assert isinstance(project.analysis, Analysis) - assert isinstance(project.summary, Summary) - - -@patch('builtins.print') -def test_project_load(mock_print): - with ( - patch('easydiffraction.sample_models.sample_models.SampleModels'), - patch('easydiffraction.experiments.experiments.Experiments'), - patch('easydiffraction.analysis.analysis.Analysis'), - patch('easydiffraction.summary.Summary'), - ): - project = Project() # Directly assign the instance to a variable - - project.load('/test/path') - - # Assertions - # path is normalised/stored as a pathlib.Path - assert _normalize_posix(project.info.path) == '/test/path' - assert 'Loading project 📦 from /test/path' in mock_print.call_args_list[0][0][0] - - -@patch('builtins.print') -@patch('pathlib.Path.open', new_callable=MagicMock) -@patch('pathlib.Path.mkdir') -def test_project_save(mock_mkdir, mock_open, mock_print): - with ( - patch('easydiffraction.sample_models.sample_models.SampleModels'), - patch('easydiffraction.experiments.experiments.Experiments'), - patch('easydiffraction.analysis.analysis.Analysis'), - patch('easydiffraction.summary.Summary'), - ): - project = Project() # Directly assign the instance to a variable - - project.info.path = '/test/path' - project.save() - - # Assertions - # Bound Path.mkdir call does not pass the path object itself as first arg - mock_mkdir.assert_any_call(parents=True, exist_ok=True) - # Bound Path.open receives only the mode (path is bound); ensure at least one write call - mock_open.assert_any_call('w') - - -@patch('builtins.print') -@patch('pathlib.Path.open', new_callable=MagicMock) -@patch('pathlib.Path.mkdir') -def test_project_save_as(mock_mkdir, mock_open, mock_print): - with ( - patch('easydiffraction.sample_models.sample_models.SampleModels'), - patch('easydiffraction.experiments.experiments.Experiments'), - patch('easydiffraction.analysis.analysis.Analysis'), - patch('easydiffraction.summary.Summary'), - ): - project = Project() # Directly assign the instance to a variable - - project.save_as('new_project_path') - - # Assertions - assert project.info.path.name == 'new_project_path' - mock_mkdir.assert_any_call(parents=True, exist_ok=True) - mock_open.assert_any_call('w') - - -def test_project_set_sample_models(): - with ( - patch('easydiffraction.sample_models.sample_models.SampleModels'), - patch('easydiffraction.experiments.experiments.Experiments'), - patch('easydiffraction.analysis.analysis.Analysis'), - patch('easydiffraction.summary.Summary'), - ): - project = Project() # Directly assign the instance to a variable - - sample_models = MagicMock() - project.set_sample_models(sample_models) - - # Assertions - assert project.sample_models == sample_models - - -def test_project_set_experiments(): - with ( - patch('easydiffraction.sample_models.sample_models.SampleModels'), - patch('easydiffraction.experiments.experiments.Experiments'), - patch('easydiffraction.analysis.analysis.Analysis'), - patch('easydiffraction.summary.Summary'), - ): - project = Project() # Directly assign the instance to a variable - - experiments = MagicMock() - project.set_experiments(experiments) - - # Assertions - assert project.experiments == experiments diff --git a/tests/unit/test_symmetry_lookup_table.py b/tests/unit/test_symmetry_lookup_table.py deleted file mode 100644 index 8286eb73..00000000 --- a/tests/unit/test_symmetry_lookup_table.py +++ /dev/null @@ -1,129 +0,0 @@ -import numpy as np -import pytest - -from easydiffraction.crystallography.space_groups import SPACE_GROUPS - - -def test_lookup_table_consistency(): - # Ensure that the space group numbers and system codes in the - # key are the same as in the actual entries. - for (it_number, it_code), entry in SPACE_GROUPS.items(): - assert entry['IT_number'] == it_number - assert entry['IT_coordinate_system_code'] == it_code - - -@pytest.mark.parametrize( - 'key, expected', - [ - ( - (62, 'cab'), - { - 'IT_number': 62, - 'setting': 2, - 'IT_coordinate_system_code': 'cab', - 'name_H-M_alt': 'P b n m', - 'crystal_system': 'orthorhombic', - 'Wyckoff_positions': { - 'd': { - 'multiplicity': 8, - 'site_symmetry': '1', - 'coords_xyz': [ - '(x,y,z)', - '(x+1/2,-y+1/2,-z)', - '(-x,-y,z+1/2)', - '(-x+1/2,y+1/2,-z+1/2)', - '(-x,-y,-z)', - '(-x+1/2,y+1/2,z)', - '(x,y,-z+1/2)', - '(x+1/2,-y+1/2,z+1/2)', - ], - }, - 'c': { - 'multiplicity': 4, - 'site_symmetry': '.m.', - 'coords_xyz': ['(x,y,1/4)', '(x+1/2,-y+1/2,3/4)', '(-x,-y,3/4)', '(-x+1/2,y+1/2,1/4)'], - }, - 'b': { - 'multiplicity': 4, - 'site_symmetry': '-1', - 'coords_xyz': ['(1/2,0,0)', '(0,1/2,0)', '(1/2,0,1/2)', '(0,1/2,1/2)'], - }, - 'a': { - 'multiplicity': 4, - 'site_symmetry': '-1', - 'coords_xyz': ['(0,0,0)', '(1/2,1/2,0)', '(0,0,1/2)', '(1/2,1/2,1/2)'], - }, - }, - }, - ), - ( - (199, '1'), - { - 'IT_number': 199, - 'setting': 0, - 'IT_coordinate_system_code': '1', - 'name_H-M_alt': 'I 21 3', - 'crystal_system': 'cubic', - 'Wyckoff_positions': { - 'c': { - 'multiplicity': 24, - 'site_symmetry': '1', - 'coords_xyz': [ - '(x,y,z)', - '(-x+1/2,-y,z+1/2)', - '(-x,y+1/2,-z+1/2)', - '(x+1/2,-y+1/2,-z)', - '(z,x,y)', - '(z+1/2,-x+1/2,-y)', - '(-z+1/2,-x,y+1/2)', - '(-z,x+1/2,-y+1/2)', - '(y,z,x)', - '(-y,z+1/2,-x+1/2)', - '(y+1/2,-z+1/2,-x)', - '(-y+1/2,-z,x+1/2)', - ], - }, - 'b': { - 'multiplicity': 12, - 'site_symmetry': '2..', - 'coords_xyz': [ - '(x,0,1/4)', - '(-x+1/2,0,3/4)', - '(1/4,x,0)', - '(3/4,-x+1/2,0)', - '(0,1/4,x)', - '(0,3/4,-x+1/2)', - ], - }, - 'a': { - 'multiplicity': 8, - 'site_symmetry': '.3.', - 'coords_xyz': ['(x,x,x)', '(-x+1/2,-x,x+1/2)', '(-x,x+1/2,-x+1/2)', '(x+1/2,-x+1/2,-x)'], - }, - }, - }, - ), - ], -) -def test_space_group_lookup_table_yields_expected(key, expected): - """Check the lookup table for a few keys and check that the output - matches the expected.""" - entry = SPACE_GROUPS[key] - - # Check that the keys are the same - assert set(entry.keys()) == set(expected.keys()) - - # Check the non-nested fields first - for sub_key in expected.keys(): - if sub_key == 'Wyckoff_positions': - continue - assert expected[sub_key] == entry[sub_key] - - # Then check Wyckoff - wyckoff_entry = entry['Wyckoff_positions'] - wyckoff_expected = expected['Wyckoff_positions'] - for site in wyckoff_expected.keys(): - assert site in wyckoff_expected.keys() - assert wyckoff_entry[site]['multiplicity'] == wyckoff_expected[site]['multiplicity'] - assert wyckoff_entry[site]['site_symmetry'] == wyckoff_expected[site]['site_symmetry'] - assert np.all(wyckoff_entry[site]['coords_xyz'] == wyckoff_expected[site]['coords_xyz']) diff --git a/tmp/Untitled.ipynb b/tmp/Untitled.ipynb new file mode 100644 index 00000000..b8f6e58b --- /dev/null +++ b/tmp/Untitled.ipynb @@ -0,0 +1,492 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "2b4ff90d-5a58-4202-ac2a-874168a2c6a2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction.sample_models.categories.atom_sites import AtomSite\n", + "from easydiffraction.sample_models.categories.cell import Cell\n", + "from easydiffraction.sample_models.categories.space_group import SpaceGroup" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "1100c5b2-e00c-4513-bd2e-e30742d47e67", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction.utils.logging import Logger\n", + "\n", + "Logger.configure(\n", + " level=Logger.Level.WARNING,\n", + " mode=Logger.Mode.VERBOSE,\n", + " reaction=Logger.Reaction.WARN,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "a1a30af4-91c9-4015-9c96-a571fd1a711a", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "s1 = AtomSite(label='La', type_symbol='La')" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "5f8217f7-e8cf-4202-8369-ced7438657f2", + "metadata": {}, + "outputs": [], + "source": [ + "s1.fract_x.value = 1.234" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "4c9cf2fe-7f72-4b9d-a574-5eb8c40223c4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95matom_site.La.fract_x\u001b[0m\u001b[1m>\u001b[0m. Expected `numeric`, got `str` \u001b[1m(\u001b[0m\u001b[32m'xyz'\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[1;36m1.234\u001b[0m. \n" + ] + } + ], + "source": [ + "s1.fract_x.value = 'xyz'" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "cba23ca5-a865-428b-b1c8-90e38787e593", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95matom_site.La.fract_x\u001b[0m\u001b[1m>\u001b[0m. Expected `numeric`, got `str` \u001b[1m(\u001b[0m\u001b[32m'qwe'\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[1;36m1.234\u001b[0m. \n" + ] + } + ], + "source": [ + "s1.fract_x = 'qwe'" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "3ba30971-177b-40e4-b477-e79a00341f87", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95mfract_x\u001b[0m\u001b[1m>\u001b[0m. Expected `numeric`, got `str` \u001b[1m(\u001b[0m\u001b[32m'uuuu'\u001b[0m\u001b[1m)\u001b[0m. Using default \u001b[1;36m0.0\u001b[0m. \n" + ] + } + ], + "source": [ + "s1 = AtomSite(label='Si', type_symbol='Si', fract_x='uuuu')" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "992966e7-6bb7-4bc7-bbff-80acfea6fd2c", + "metadata": {}, + "outputs": [], + "source": [ + "s1.fract_x.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "50ef6ebd-097d-4df2-93dc-39243bdba6fd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95matom_site.Si.fract_x.free\u001b[0m\u001b[1m>\u001b[0m. Expected `bool`, got `str` \u001b[1m(\u001b[0m\u001b[32m'abc'\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[3;92mTrue\u001b[0m. \n" + ] + } + ], + "source": [ + "s1.fract_x.free = 'abc'" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "2c46e9ca-f68d-4b71-b783-6660f357322c", + "metadata": {}, + "outputs": [], + "source": [ + "c = Cell()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "8e3fee6f-dc71-49a0-bf67-6f85f4ba83cd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mlength_b\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[1;36m-8.8\u001b[0m outside \u001b[1m[\u001b[0m\u001b[1;36m0\u001b[0m, \u001b[1;36m1000\u001b[0m\u001b[1m]\u001b[0m. Using default \u001b[1;36m10.0\u001b[0m. \n" + ] + } + ], + "source": [ + "c = Cell(length_b=-8.8)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "749e3f57-1097-4939-b853-4c67148fb831", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95mlength_b\u001b[0m\u001b[1m>\u001b[0m. Expected `numeric`, got `str` \u001b[1m(\u001b[0m\u001b[32m'7.7'\u001b[0m\u001b[1m)\u001b[0m. Using default \u001b[1;36m10.0\u001b[0m. \n" + ] + } + ], + "source": [ + "c = Cell(length_b='7.7')" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "f06c5fa7-372c-4d76-b749-634d92fe6d11", + "metadata": {}, + "outputs": [], + "source": [ + "c = Cell(length_b=6.6)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "70fd1bf8-e7a3-4576-bed2-b60edf8a8097", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[1;36m-5.5\u001b[0m outside \u001b[1m[\u001b[0m\u001b[1;36m0\u001b[0m, \u001b[1;36m1000\u001b[0m\u001b[1m]\u001b[0m. Keeping current \u001b[1;36m6.6\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b.value = -5.5" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "b1277593-dea1-44c9-ac7e-d113f4ee3fda", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[1;36m-4.4\u001b[0m outside \u001b[1m[\u001b[0m\u001b[1;36m0\u001b[0m, \u001b[1;36m1000\u001b[0m\u001b[1m]\u001b[0m. Keeping current \u001b[1;36m6.6\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b = -4.4" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "e174a5cf-2653-431b-a665-f88a8e4423f0", + "metadata": {}, + "outputs": [], + "source": [ + "c.length_b = 3.3" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "9b429e1d-eabd-4e35-a3b7-1a9b752bb4f1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[1;36m2222.2\u001b[0m outside \u001b[1m[\u001b[0m\u001b[1;36m0\u001b[0m, \u001b[1;36m1000\u001b[0m\u001b[1m]\u001b[0m. Keeping current \u001b[1;36m3.3\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b = 2222.2" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "65d2c0fb-8e35-493c-a3e3-24421e9326bb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b.free\u001b[0m\u001b[1m>\u001b[0m. Expected `bool`, got `str` \u001b[1m(\u001b[0m\u001b[32m'qwe'\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[3;91mFalse\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b.free = 'qwe'" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "4475ed9b-74d1-4080-8e57-0099b35e94f5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Unknown attribute \u001b[32m'fre'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Did you mean \u001b[32m'free'\u001b[0m? \n" + ] + } + ], + "source": [ + "c.length_b.fre = 'fre'" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "e3d37a6d-9ec8-4d96-8f95-ce7b67e65f36", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Unknown attribute \u001b[32m'qwe'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Allowed writable: \u001b[32m'fit_max'\u001b[0m, \u001b[32m'fit_min'\u001b[0m, \u001b[32m'free'\u001b[0m, \u001b[32m'uncertainty'\u001b[0m, \n", + " \u001b[32m'value'\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b.qwe = 'qwe'" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "f108a56d-775c-4d4e-aedf-ecc4ead28178", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Cannot modify read-only attribute \u001b[32m'description'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b.description = 'desc'" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "382d6221-0074-4ec8-84a8-ec51e117420a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Unknown attribute \u001b[32m'qwe'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell\u001b[0m\u001b[1m>\u001b[0m. Allowed writable: \u001b[32m'angle_alpha'\u001b[0m, \u001b[32m'angle_beta'\u001b[0m, \u001b[32m'angle_gamma'\u001b[0m, \u001b[32m'length_a'\u001b[0m, \n", + " \u001b[32m'length_b'\u001b[0m, \u001b[32m'length_c'\u001b[0m. \n" + ] + } + ], + "source": [ + "c.qwe = 'qwe'" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "7bf7bac1-79ce-45df-a2a8-c4b090359292", + "metadata": {}, + "outputs": [], + "source": [ + "sg = SpaceGroup()" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "5c30d269-5167-4598-ac57-66715673d9b0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mname_h_m\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'qwe'\u001b[0m is unknown. \u001b[1m(\u001b[0m\u001b[1;36m230\u001b[0m allowed values not listed here\u001b[1m)\u001b[0m. Using default \n", + " \u001b[32m'P 1'\u001b[0m. \n" + ] + } + ], + "source": [ + "sg = SpaceGroup(name_h_m='qwe')" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "c99fcf63-9536-4ea6-a30a-96367bb4aacb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mname_h_m\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'P n m'\u001b[0m is unknown. \u001b[1m(\u001b[0m\u001b[1;36m230\u001b[0m allowed values not listed here\u001b[1m)\u001b[0m. Using default\n", + " \u001b[32m'P 1'\u001b[0m. \n", + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mit_coordinate_system_code\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'cab'\u001b[0m is unknown. Allowed values: \u001b[32m''\u001b[0m. Using default \n", + " \u001b[32m''\u001b[0m. \n" + ] + } + ], + "source": [ + "sg = SpaceGroup(name_h_m='P n m', it_coordinate_system_code='cab')" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "c2d7509e-a49b-4c2b-aa45-76a97de0761e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mit_coordinate_system_code\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'cabd'\u001b[0m is unknown. Allowed values: \u001b[32m'-cba'\u001b[0m, \u001b[32m'a-cb'\u001b[0m, \n", + " \u001b[32m'abc'\u001b[0m, \u001b[32m'ba-c'\u001b[0m, \u001b[32m'bca'\u001b[0m, \u001b[32m'cab'\u001b[0m. Using default \u001b[32m'abc'\u001b[0m. \n" + ] + } + ], + "source": [ + "sg = SpaceGroup(name_h_m='P n m a', it_coordinate_system_code='cabd')" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "07db2ef2-58f3-4b46-8223-a2067254db51", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95mspace_group.name_h_m\u001b[0m\u001b[1m>\u001b[0m. Expected `string`, got `float` \u001b[1m(\u001b[0m\u001b[1;36m34.9\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[32m'P n m a'\u001b[0m. \n" + ] + } + ], + "source": [ + "sg.name_h_m = 34.9" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6934d72d-01a7-41e3-8684-5a1c6106e933", + "metadata": {}, + "outputs": [], + "source": [ + "sg.name_h_m = 'P 1'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a8a152ec-5a11-4c38-bca4-7a4230218f34", + "metadata": {}, + "outputs": [], + "source": [ + "sg.name_h_m = 'P n m a'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6626a960-6390-4e76-8f27-dc2cebbdd123", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb299acb-0920-43f3-be25-c432544e1198", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (Pixi)", + "language": "python", + "name": "pixi-kernel-python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tmp/Untitled1.ipynb b/tmp/Untitled1.ipynb new file mode 100644 index 00000000..6ebdcb97 --- /dev/null +++ b/tmp/Untitled1.ipynb @@ -0,0 +1,149 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "e7e00e10-ee5c-48cb-87f0-a34f8162a39d", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import SampleModelFactory" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8aadcc06-0b34-40ea-a921-b326d2aa83e1", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction.utils.logging import Logger\n", + "\n", + "Logger.configure(\n", + " level=Logger.Level.WARNING,\n", + " mode=Logger.Mode.VERBOSE,\n", + " reaction=Logger.Reaction.WARN,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "26c42a06-7efc-468a-9c57-640447900d83", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮\n",
+       " in <module>:1                                                                                    \n",
+       "                                                                                                  \n",
+       " 1 model = SampleModelFactory.create(name='name', cif_path='path')                              \n",
+       "   2                                                                                              \n",
+       "                                                                                                  \n",
+       " /Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/samp \n",
+       " le_models/sample_model/factory.py:28 in create                                                   \n",
+       "                                                                                                  \n",
+       "    25 │   │   \"\"\"                                                                                \n",
+       "    26 │   │   # Check for valid argument combinations                                            \n",
+       "    27 │   │   user_args = {k for k, v in kwargs.items() if v is not None}                        \n",
+       "  28 │   │   cls._validate_args(user_args, cls._ALLOWED_ARG_SPECS, cls.__name__)                \n",
+       "    29 │   │                                                                                      \n",
+       "    30 │   │   if 'cif_path' in kwargs:                                                           \n",
+       "    31 │   │   │   return cls._create_from_cif_path(kwargs['cif_path'])                           \n",
+       "                                                                                                  \n",
+       " /Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/core \n",
+       " /factory.py:29 in _validate_args                                                                 \n",
+       "                                                                                                  \n",
+       "   26 │   │   │   │   combos.append(f'({req}[, {opt}])')                                          \n",
+       "   27 │   │   │   else:                                                                           \n",
+       "   28 │   │   │   │   combos.append(f'({req})')                                                   \n",
+       " 29 │   │   raise ValueError(                                                                   \n",
+       "   30 │   │   │   f'Invalid argument combination for {factory_name} creation.\\n'                  \n",
+       "   31 │   │   │   f'Provided: {sorted(present)}\\n'                                                \n",
+       "   32 │   │   │   f'Allowed combinations:\\n  ' + '\\n  '.join(combos)                              \n",
+       "╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
+       "ValueError: Invalid argument combination for SampleModelFactory creation.\n",
+       "Provided: ['cif_path', 'name']\n",
+       "Allowed combinations:\n",
+       "  (name)\n",
+       "  (cif_path)\n",
+       "  (cif_str)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[31m╭─\u001b[0m\u001b[31m──────────────────────────────\u001b[0m\u001b[31m \u001b[0m\u001b[1;31mTraceback \u001b[0m\u001b[1;2;31m(most recent call last)\u001b[0m\u001b[31m \u001b[0m\u001b[31m───────────────────────────────\u001b[0m\u001b[31m─╮\u001b[0m\n", + "\u001b[31m│\u001b[0m in :1 \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m1 model = \u001b[1;4mSampleModelFactory.create(name=\u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4;33mname\u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4m, cif_path=\u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4;33mpath\u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4m)\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m2 \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m/Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/samp\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2mle_models/sample_model/\u001b[0m\u001b[1mfactory.py\u001b[0m:28 in create \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 25 \u001b[0m\u001b[2;33m│ │ \u001b[0m\u001b[33m\"\"\"\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 26 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[2m# Check for valid argument combinations\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 27 \u001b[0m\u001b[2m│ │ \u001b[0muser_args = {k \u001b[94mfor\u001b[0m k, v \u001b[95min\u001b[0m kwargs.items() \u001b[94mif\u001b[0m v \u001b[95mis\u001b[0m \u001b[95mnot\u001b[0m \u001b[94mNone\u001b[0m} \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 28 \u001b[2m│ │ \u001b[0m\u001b[1;4;96mcls\u001b[0m\u001b[1;4m._validate_args(user_args, \u001b[0m\u001b[1;4;96mcls\u001b[0m\u001b[1;4m._ALLOWED_ARG_SPECS, \u001b[0m\u001b[1;4;96mcls\u001b[0m\u001b[1;4m.\u001b[0m\u001b[1;4;91m__name__\u001b[0m\u001b[1;4m)\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 29 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 30 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94mif\u001b[0m \u001b[33m'\u001b[0m\u001b[33mcif_path\u001b[0m\u001b[33m'\u001b[0m \u001b[95min\u001b[0m kwargs: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 31 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94mreturn\u001b[0m \u001b[96mcls\u001b[0m._create_from_cif_path(kwargs[\u001b[33m'\u001b[0m\u001b[33mcif_path\u001b[0m\u001b[33m'\u001b[0m]) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m/Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/core\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m/\u001b[0m\u001b[1mfactory.py\u001b[0m:29 in _validate_args \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m26 \u001b[0m\u001b[2m│ │ │ │ \u001b[0mcombos.append(\u001b[33mf\u001b[0m\u001b[33m'\u001b[0m\u001b[33m(\u001b[0m\u001b[33m{\u001b[0mreq\u001b[33m}\u001b[0m\u001b[33m[, \u001b[0m\u001b[33m{\u001b[0mopt\u001b[33m}\u001b[0m\u001b[33m])\u001b[0m\u001b[33m'\u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m27 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94melse\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m28 \u001b[0m\u001b[2m│ │ │ │ \u001b[0mcombos.append(\u001b[33mf\u001b[0m\u001b[33m'\u001b[0m\u001b[33m(\u001b[0m\u001b[33m{\u001b[0mreq\u001b[33m}\u001b[0m\u001b[33m)\u001b[0m\u001b[33m'\u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m29 \u001b[2m│ │ \u001b[0m\u001b[1;4;94mraise\u001b[0m\u001b[1;4m \u001b[0m\u001b[1;4;96mValueError\u001b[0m\u001b[1;4m(\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m30 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[1;4;33mf\u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4;33mInvalid argument combination for \u001b[0m\u001b[1;4;33m{\u001b[0m\u001b[1;4mfactory_name\u001b[0m\u001b[1;4;33m}\u001b[0m\u001b[1;4;33m creation.\u001b[0m\u001b[1;4;33m\\n\u001b[0m\u001b[1;4;33m'\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m31 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[1;4;33mf\u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4;33mProvided: \u001b[0m\u001b[1;4;33m{\u001b[0m\u001b[1;4;96msorted\u001b[0m\u001b[1;4m(present)\u001b[0m\u001b[1;4;33m}\u001b[0m\u001b[1;4;33m\\n\u001b[0m\u001b[1;4;33m'\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m32 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[1;4;33mf\u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4;33mAllowed combinations:\u001b[0m\u001b[1;4;33m\\n\u001b[0m\u001b[1;4;33m \u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4m + \u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4;33m\\n\u001b[0m\u001b[1;4;33m \u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4m.join(combos)\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n", + "\u001b[1;91mValueError: \u001b[0mInvalid argument combination for SampleModelFactory creation.\n", + "Provided: \u001b[1m[\u001b[0m\u001b[32m'cif_path'\u001b[0m, \u001b[32m'name'\u001b[0m\u001b[1m]\u001b[0m\n", + "Allowed combinations:\n", + " \u001b[1m(\u001b[0mname\u001b[1m)\u001b[0m\n", + " \u001b[1m(\u001b[0mcif_path\u001b[1m)\u001b[0m\n", + " \u001b[1m(\u001b[0mcif_str\u001b[1m)\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "model = SampleModelFactory.create(name='name', cif_path='path')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d6b0cec6-220d-476b-b918-b66e8322a3e9", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (Pixi)", + "language": "python", + "name": "pixi-kernel-python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials-drafts/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py b/tmp/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py similarity index 71% rename from tutorials-drafts/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py rename to tmp/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py index 3a5c8174..b4ee7e15 100644 --- a/tutorials-drafts/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py +++ b/tmp/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py @@ -41,17 +41,17 @@ # #### Set Atom Sites # %% -model_1.atom_sites.add('Bi1', 'Bi', 0.702, 0.114, 0, wyckoff_letter='g', b_iso=0.0, occupancy=0.88) -model_1.atom_sites.add('Sm1', 'Sm', 0.702, 0.114, 0, wyckoff_letter='g', b_iso=0.0, occupancy=0.12) -model_1.atom_sites.add('Bi2', 'Bi', 0.751, 0.132, 0.5, wyckoff_letter='h', b_iso=0.0, occupancy=0.88) -model_1.atom_sites.add('Sm2', 'Sm', 0.751, 0.132, 0.5, wyckoff_letter='h', b_iso=0.0, occupancy=0.12) -model_1.atom_sites.add('Fe', 'Fe', 0.236, 0.121, 0.259, wyckoff_letter='i', b_iso=0.0, occupancy=0.94) -model_1.atom_sites.add('Ti', 'Ti', 0.236, 0.121, 0.259, wyckoff_letter='i', b_iso=0.0, occupancy=0.06) -model_1.atom_sites.add('O1', 'O', 0.258, 0.151, 0, wyckoff_letter='g', b_iso=0.0, occupancy=1.0) -model_1.atom_sites.add('O2', 'O', 0.316, 0.093, 0.5, wyckoff_letter='h', b_iso=0.0, occupancy=1.0) -model_1.atom_sites.add('O3', 'O', 0.002, 0.258, 0.299, wyckoff_letter='i', b_iso=0.0, occupancy=1.0) -model_1.atom_sites.add('O4', 'O', 0, 0.5, 0.264, wyckoff_letter='f', b_iso=0.0, occupancy=1.0) -model_1.atom_sites.add('O5', 'O', 0, 0, 0.198, wyckoff_letter='e', b_iso=0.0, occupancy=1.0) +model_1.atom_sites.add('Bi1', 'Bi', 0.702, 0.114, 0, wyckoff_letter='g', b_iso=0.0, occupancy=0.88) +model_1.atom_sites.add('Sm1', 'Sm', 0.702, 0.114, 0, wyckoff_letter='g', b_iso=0.0, occupancy=0.12) +model_1.atom_sites.add('Bi2', 'Bi', 0.751, 0.132, 0.5, wyckoff_letter='h', b_iso=0.0, occupancy=0.88) +model_1.atom_sites.add('Sm2', 'Sm', 0.751, 0.132, 0.5, wyckoff_letter='h', b_iso=0.0, occupancy=0.12) +model_1.atom_sites.add('Fe', 'Fe', 0.236, 0.121, 0.259, wyckoff_letter='i', b_iso=0.0, occupancy=0.94) +model_1.atom_sites.add('Ti', 'Ti', 0.236, 0.121, 0.259, wyckoff_letter='i', b_iso=0.0, occupancy=0.06) +model_1.atom_sites.add('O1', 'O', 0.258, 0.151, 0, wyckoff_letter='g', b_iso=0.0, occupancy=1.0) +model_1.atom_sites.add('O2', 'O', 0.316, 0.093, 0.5, wyckoff_letter='h', b_iso=0.0, occupancy=1.0) +model_1.atom_sites.add('O3', 'O', 0.002, 0.258, 0.299, wyckoff_letter='i', b_iso=0.0, occupancy=1.0) +model_1.atom_sites.add('O4', 'O', 0, 0.5, 0.264, wyckoff_letter='f', b_iso=0.0, occupancy=1.0) +model_1.atom_sites.add('O5', 'O', 0, 0, 0.198, wyckoff_letter='e', b_iso=0.0, occupancy=1.0) # %% [markdown] # ### Create Sample Model 2: Rhombohedral phase @@ -77,11 +77,11 @@ # #### Set Atom Sites # %% -model_2.atom_sites.add('Bi', 'Bi', 0, 0, 0, wyckoff_letter='a', b_iso=0.0, occupancy=0.88) -model_2.atom_sites.add('Sm', 'Sm', 0, 0, 0, wyckoff_letter='a', b_iso=0.0, occupancy=0.12) -model_2.atom_sites.add('Fe', 'Fe', 0, 0, 0.223, wyckoff_letter='a', b_iso=0.0, occupancy=0.94) -model_2.atom_sites.add('Ti', 'Ti', 0, 0, 0.223, wyckoff_letter='a', b_iso=0.0, occupancy=0.06) -model_2.atom_sites.add('O', 'O', 0.436, 0.022, 0.958, wyckoff_letter='b', b_iso=0.0, occupancy=1.0) +model_2.atom_sites.add('Bi', 'Bi', 0, 0, 0, wyckoff_letter='a', b_iso=0.0, occupancy=0.88) +model_2.atom_sites.add('Sm', 'Sm', 0, 0, 0, wyckoff_letter='a', b_iso=0.0, occupancy=0.12) +model_2.atom_sites.add('Fe', 'Fe', 0, 0, 0.223, wyckoff_letter='a', b_iso=0.0, occupancy=0.94) +model_2.atom_sites.add('Ti', 'Ti', 0, 0, 0.223, wyckoff_letter='a', b_iso=0.0, occupancy=0.06) +model_2.atom_sites.add('O', 'O', 0.436, 0.022, 0.958, wyckoff_letter='b', b_iso=0.0, occupancy=1.0) # %% [markdown] # ## Define Experiment @@ -92,7 +92,7 @@ # #### Download Data # %% -#download_from_repository('hrpt_n_Bi0p88Sm0p12Fe0p94Ti0p06O3_DW_V_9x8x52_1p49_HI.xye', +# download_from_repository('hrpt_n_Bi0p88Sm0p12Fe0p94Ti0p06O3_DW_V_9x8x52_1p49_HI.xye', # branch='develop', # destination='data') @@ -237,11 +237,11 @@ # Set sample model parameters to be optimized. # %% -#model_1.cell.length_a.free = True -#model_1.atom_sites['Co'].b_iso.free = True -#model_1.atom_sites['O'].b_iso.free = True +# model_1.cell.length_a.free = True +# model_1.atom_sites['Co'].b_iso.free = True +# model_1.atom_sites['O'].b_iso.free = True -#model_2.cell.length_a.free = True +# model_2.cell.length_a.free = True # %% [markdown] # Set experiment parameters to be optimized. diff --git a/tutorials-drafts/data/DREAM_mantle_bc240_nist_cif.xye b/tmp/data/DREAM_mantle_bc240_nist_cif.xye similarity index 100% rename from tutorials-drafts/data/DREAM_mantle_bc240_nist_cif.xye rename to tmp/data/DREAM_mantle_bc240_nist_cif.xye diff --git a/tutorials-drafts/data/DREAM_mantle_bc240_nist_cif_2.xye b/tmp/data/DREAM_mantle_bc240_nist_cif_2.xye similarity index 100% rename from tutorials-drafts/data/DREAM_mantle_bc240_nist_cif_2.xye rename to tmp/data/DREAM_mantle_bc240_nist_cif_2.xye diff --git a/tutorials-drafts/data/DREAM_mantle_bc240_nist_nc.xye b/tmp/data/DREAM_mantle_bc240_nist_nc.xye similarity index 100% rename from tutorials-drafts/data/DREAM_mantle_bc240_nist_nc.xye rename to tmp/data/DREAM_mantle_bc240_nist_nc.xye diff --git a/tutorials-drafts/data/DREAM_mantle_bc240_nist_nc_2.xye b/tmp/data/DREAM_mantle_bc240_nist_nc_2.xye similarity index 100% rename from tutorials-drafts/data/DREAM_mantle_bc240_nist_nc_2.xye rename to tmp/data/DREAM_mantle_bc240_nist_nc_2.xye diff --git a/tutorials-drafts/data/Si_mp-149_symmetrized_mcstas.cif b/tmp/data/Si_mp-149_symmetrized_mcstas.cif similarity index 100% rename from tutorials-drafts/data/Si_mp-149_symmetrized_mcstas.cif rename to tmp/data/Si_mp-149_symmetrized_mcstas.cif diff --git a/tmp/data/hrpt_lbco.xye b/tmp/data/hrpt_lbco.xye new file mode 100644 index 00000000..0b9b63e3 --- /dev/null +++ b/tmp/data/hrpt_lbco.xye @@ -0,0 +1,3099 @@ +# 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 diff --git a/tmp/data/lbco.cif b/tmp/data/lbco.cif new file mode 100644 index 00000000..e80dfecb --- /dev/null +++ b/tmp/data/lbco.cif @@ -0,0 +1,36 @@ + data_lbco + + _space_group.IT_coordinate_system_code 1 + _space_group.name_H-M_alt "P m -3 m" + + _cell.angle_alpha 90 + _cell.angle_beta 90 + _cell.length_a 3.88(1) + _cell.length_b 3.88 + _cell.length_c 3.88 + + loop_ + _atom_site.ADP_type + _atom_site.B_iso_or_equiv + _atom_site.fract_x + _atom_site.fract_y + _atom_site.fract_z + _atom_site.label + _atom_site.occupancy + _atom_site.type_symbol + _atom_site.Wyckoff_letter + Biso 0.5 0.0 0.0 0.0 La 0.5 La a + Biso 0.5 0.0 0.0 0.0 Ba 0.5 Ba a + Biso 0.5 0.5 0.5 0.5 Co 1.0 Co b + Biso 0.5 0.0 0.5 0.5 O 1.0 O c + + loop_ + _pd_meas.2theta_scan + _pd_proc.intensity_total + _pd_calc.intensity_total_su + 10.0 167.0 12.6 + 10.05 157.0 12.5 + 10.1 187.0 13.3 + 10.15 197.0 14.0 + 10.2 164.0 12.5 + 10.25 171.0 13.0 diff --git a/tmp/generate_overview_mermaid.py b/tmp/generate_overview_mermaid.py new file mode 100644 index 00000000..67dce703 --- /dev/null +++ b/tmp/generate_overview_mermaid.py @@ -0,0 +1,309 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""Generate an overview Mermaid class diagram dynamically from code. + +This script scans the source tree with Python's AST to discover: + - Classes and their inheritance + - Key composition relationships (via __init__ assignments or property + return annotations) + - Collection "contains" relationships for DatablockCollection + +It renders an overview Mermaid classDiagram that automatically adapts +to project structure changes. Analysis internals are intentionally +omitted; we only keep the top-level link from Project to Analysis. + +Output: docs/architecture/overview-diagram.md + +Run (from repo root): + pixi run python tools/generate_overview_mermaid.py +""" + +from __future__ import annotations + +import ast +from dataclasses import dataclass +from dataclasses import field +from pathlib import Path +from typing import Dict +from typing import Iterable +from typing import List +from typing import Optional +from typing import Set +from typing import Tuple + +REPO_ROOT = Path(__file__).resolve().parents[1] +SRC_ROOT = REPO_ROOT / 'src' / 'easydiffraction' +DOCS_OUT_DIR = REPO_ROOT / 'docs' / 'architecture' +OUT_MD = DOCS_OUT_DIR / 'overview-diagram.md' + + +@dataclass +class ClassInfo: + name: str + module: str # e.g., experiments/experiment_types/powder.py + bases: List[str] # base class names (unqualified) + category: Optional[str] = None # container | collection | baseclass | measurement | model + compositions: Set[str] = field(default_factory=set) # composed-with class names + contains: Set[str] = field(default_factory=set) # collection element types + + +INCLUDE_FILES = [ + # Project and Summary + SRC_ROOT / 'project' / 'project.py', + SRC_ROOT / 'project' / 'project_info.py', + SRC_ROOT / 'summary' / 'summary.py', + SRC_ROOT / 'analysis' / 'analysis.py', + # Sample models + SRC_ROOT / 'sample_models' / 'sample_models.py', + SRC_ROOT / 'sample_models' / 'sample_model.py', + SRC_ROOT / 'sample_models' / 'sample_model_types' / 'base.py', + # Experiments + SRC_ROOT / 'experiments' / 'experiments.py', + SRC_ROOT / 'experiments' / 'experiment.py', + SRC_ROOT / 'experiments' / 'experiment_types' / 'base.py', +] + + +def iter_experiment_type_files() -> Iterable[Path]: + expt_dir = SRC_ROOT / 'experiments' / 'experiment_types' + if not expt_dir.exists(): + return [] + skip = {'__init__.py', 'base.py', 'enums.py', 'instrument_mixin.py'} + for py in sorted(expt_dir.glob('*.py')): + if py.name in skip: + continue + yield py + + +def parse_file(py_path: Path) -> Optional[ast.Module]: + try: + src = py_path.read_text(encoding='utf-8') + return ast.parse(src) + except Exception: + return None + + +def name_from_node(n: ast.AST) -> Optional[str]: + if isinstance(n, ast.Name): + return n.id + if isinstance(n, ast.Attribute): + # Return attribute tail (unqualified) + return n.attr + return None + + +def discover_classes() -> Dict[str, ClassInfo]: + files = list(INCLUDE_FILES) + list(iter_experiment_type_files()) + classes: Dict[str, ClassInfo] = {} + + for py in files: + mod = parse_file(py) + if not mod: + continue + rel_module = str(py.relative_to(SRC_ROOT)) + for node in mod.body: + if isinstance(node, ast.ClassDef): + base_names = [name_from_node(b) for b in node.bases] + base_names = [n for n in base_names if n] + ci = ClassInfo(name=node.name, module=rel_module, bases=base_names) + classes[ci.name] = ci + + # Second pass: gather compositions and contains + for node in mod.body: + if isinstance(node, ast.ClassDef): + ci = classes.get(node.name) + if not ci: + continue + for inner in node.body: + # __init__ assignments: self.attr = SomeClass(...) + if isinstance(inner, ast.FunctionDef) and inner.name == '__init__': + for stmt in ast.walk(inner): + if isinstance(stmt, ast.Assign): + # Look for Call on RHS + if isinstance(stmt.value, ast.Call): + callee = name_from_node(stmt.value.func) + if callee: + ci.compositions.add(callee) + if isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call): + # super().__init__(item_type=SomeClass) + call = stmt.value + func_name = name_from_node(call.func) + if func_name == '__init__' and isinstance(call, ast.Call): + for kw in call.keywords: + if kw.arg == 'item_type': + n = name_from_node(kw.value) + if n: + ci.contains.add(n) + # @property return annotation: def x(self) -> SomeClass + if isinstance(inner, ast.FunctionDef): + if inner.returns is not None: + ann = name_from_node(inner.returns) + if ann: + ci.compositions.add(ann) + + # Categorize + for ci in classes.values(): + path = ci.module + # Collections + if 'DatablockCollection' in ci.bases: + ci.category = 'collection' + # Measurement (concrete experiment types) + elif ( + 'experiments/experiment_types/' in path + and ci.name.endswith('Experiment') + and not ci.name.startswith('Base') + ): + ci.category = 'measurement' + # Base classes (heuristic) + elif ci.name.startswith('Base'): + ci.category = 'baseclass' + # Models + elif 'sample_models/' in path and ci.name in {'BaseSampleModel', 'SampleModel'}: + ci.category = 'model' + # Containers + elif path.endswith('project.py') and ci.name == 'Project' or path.endswith('project_info.py') and ci.name == 'ProjectInfo' or path.endswith('summary.py') and ci.name == 'Summary' or path.endswith('analysis/analysis.py') and ci.name == 'Analysis': + ci.category = 'container' + # Keep others uncategorized (they will still participate via edges) + + return classes + + +# ----------------- +# Mermaid rendering +# ----------------- + + +def mermaid_header() -> str: + return 'classDiagram\n' + + +def mermaid_classes(classes: Dict[str, ClassInfo]) -> str: + lines: List[str] = [] + for ci in classes.values(): + # Limit to overview classes: containers, collections, models, baseclasses, measurements + if ci.category in {'container', 'collection', 'model', 'baseclass', 'measurement'}: + style = f':::{ci.category}' if ci.category else '' + lines.append(f' class {ci.name}{style}') + return '\n'.join(lines) + ('\n\n' if lines else '') + + +def build_edges(classes: Dict[str, ClassInfo]) -> Tuple[List[str], List[str], List[str]]: + """Return (inheritance, composition, contains) edge lists.""" + # Work only with classes we render as overview categories + showable = { + name + for name, ci in classes.items() + if ci.category in {'container', 'collection', 'model', 'baseclass', 'measurement'} + } + + inheritance: List[str] = [] + composition: List[str] = [] + contains: List[str] = [] + + # Inheritance: Parent <|-- Child + for child_name, ci in classes.items(): + for base in ci.bases: + if base in showable and child_name in showable: + inheritance.append(f' {base} <|-- {child_name}') + + # Composition: A *-- B (if A composes B) + for a_name, ci in classes.items(): + if a_name not in showable: + continue + for b in ci.compositions: + if b in showable: + # Special-case: hide Analysis internals; keep only Project *-- Analysis + if b == 'Analysis' and a_name != 'Project': + continue + composition.append(f' {a_name} *-- {b}') + + # Contains: Collections "1" -- "*" T : contains + for a_name, ci in classes.items(): + if ci.category != 'collection': + continue + for t in ci.contains: + if t in classes: + # Expand Experiments contains Experiment into all concrete measurements + if a_name == 'Experiments' and t == 'Experiment': + for name, c2 in classes.items(): + if c2.category == 'measurement': + contains.append(f' {a_name} "1" -- "*" {name} : contains') + else: + contains.append(f' {a_name} "1" -- "*" {t} : contains') + + return inheritance, composition, contains + + +def mermaid_relationships(classes: Dict[str, ClassInfo]) -> str: + inh, comp, cont = build_edges(classes) + lines: List[str] = [] + lines.append(' %% Relationships %%\n') + if comp or cont or inh: + lines.append('') + lines.extend(inh) + if inh and (comp or cont): + lines.append('') + lines.extend(cont) + if cont and comp: + lines.append('') + lines.extend(comp) + lines.append('\n') + return '\n'.join(lines) + + +def mermaid_styles() -> str: + return ( + ' %%%%%%%%%%%%%\n' + ' %% STYLING %%\n' + ' %%%%%%%%%%%%%\n\n' + ' %% Abstract Base Classes\n' + ' classDef baseclass fill:#6A5ACD,stroke:#333,stroke-width:1px,color:white;\n\n' + ' %% Containers (Project, ProjectInfo, Summary, Analysis)\n' + ' classDef container fill:#455A64,stroke:#333,stroke-width:1px,color:white;\n\n' + ' %% Collections (SampleModels, Experiments)\n' + ' classDef collection fill:#607D8B,stroke:#333,stroke-width:1px,color:white;\n\n' + ' %% Concrete Experiments\n' + ' classDef measurement fill:#4682B4,stroke:#0D47A1,stroke-width:1px,color:white;\n\n' + ' %% Models (SampleModel, StructuralModel)\n' + ' classDef model fill:#009688,stroke:#004D40,stroke-width:1px,color:white;\n' + ) + + +def build_mermaid() -> str: + classes = discover_classes() + parts = [ + mermaid_header(), + mermaid_classes(classes), + mermaid_relationships(classes), + mermaid_styles(), + ] + return ''.join(parts) + + +def write_markdown(mermaid: str) -> None: + DOCS_OUT_DIR.mkdir(parents=True, exist_ok=True) + content = [ + '# Architecture Overview', + '', + '```mermaid', + mermaid, + '```', + '', + '> Note: This diagram is auto-generated. Edit tools/generate_overview_mermaid.py to change structure or style.', + '', + ] + OUT_MD.write_text('\n'.join(content), encoding='utf-8') + rel = OUT_MD.relative_to(REPO_ROOT) + print(f'Wrote: {rel}') + + +def main() -> None: + if not SRC_ROOT.exists(): + raise SystemExit(f'Source root not found: {SRC_ROOT}') + mermaid = build_mermaid() + write_markdown(mermaid) + + +if __name__ == '__main__': + main() diff --git a/tutorials-drafts/hrpt_n_Bi0p88Sm0p12Fe0p94Ti0p06O3_DW_V_9x8x52_1p49_HI.xye b/tmp/hrpt_n_Bi0p88Sm0p12Fe0p94Ti0p06O3_DW_V_9x8x52_1p49_HI.xye similarity index 100% rename from tutorials-drafts/hrpt_n_Bi0p88Sm0p12Fe0p94Ti0p06O3_DW_V_9x8x52_1p49_HI.xye rename to tmp/hrpt_n_Bi0p88Sm0p12Fe0p94Ti0p06O3_DW_V_9x8x52_1p49_HI.xye diff --git a/tmp/short.py b/tmp/short.py new file mode 100644 index 00000000..87f90d00 --- /dev/null +++ b/tmp/short.py @@ -0,0 +1,94 @@ +from easydiffraction import Experiment +from easydiffraction import Experiments +from easydiffraction import Logger +from easydiffraction import Project +from easydiffraction import SampleModel +from easydiffraction import SampleModels +from easydiffraction.experiments.categories.linked_phases import LinkedPhase +from easydiffraction.experiments.categories.linked_phases import LinkedPhases +from easydiffraction.sample_models.categories.atom_sites import AtomSite +from easydiffraction.sample_models.categories.atom_sites import AtomSites +from easydiffraction.sample_models.categories.cell import Cell +from easydiffraction.sample_models.categories.space_group import SpaceGroup + +Logger.configure(mode=Logger.Mode.LOG, level=Logger.Level.DEBUG) +# Logger.configure(mode=Logger.Mode.RAISE, level=Logger.Level.DEBUG) + +sg = SpaceGroup() +sg.name_h_m = 'P n m a' +sg.it_coordinate_system_code = 'cab' + +cell = Cell() +cell.length_a = 5.4603 + +site = AtomSite() +site.type_symbol = 'Si' + +sites = AtomSites() +sites.add_from_args(site) + +model = SampleModel(name='mdl') +model.space_group = sg +model.cell = cell +model.atom_sites = sites + + +print(model.parameters) + +models = SampleModels() +# models.add_from_args(model) +models.add_from_cif_path('tutorials/data/lbco.cif') + +print(models) +for p in models.parameters: + print(p) +print(models.as_cif) + +exp = Experiment(name='hrpt', data_path='tutorials/data/hrpt_lbco.xye') +print(exp) + +linked_phases = LinkedPhases() +linked_phase = LinkedPhase(id='lbco', scale=10.0) +linked_phases.add_from_args(linked_phase) + +exp.linked_phases = linked_phases + +exp.instrument.setup_wavelength = 1.494 +exp.instrument.calib_twotheta_offset = 0.6 + +exp.peak.broad_gauss_u = 0.1 +exp.peak.broad_gauss_v = -0.1 +exp.peak.broad_gauss_w = 0.1 +exp.peak.broad_lorentz_y = 0.1 + +# exp.background.add_from_args(x=10, y=170) +# exp.background.add_from_args(x=30, y=170) +# exp.background.add_from_args(x=50, y=170) +# exp.background.add_from_args(x=110, y=170) +# exp.background.add_from_args(x=165, y=170) + +experiments = Experiments() +print(experiments) + +experiments.add(exp) +print(experiments) +for p in experiments.parameters: + print(p) +# print(experiments.as_cif) + + +proj = Project(name='PROJ') +print(proj) + +proj.sample_models = models +proj.experiments = experiments + + +# proj.plotter.engine = 'plotly' + +proj.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41) + + +models['lbco'].cell.length_a.free = True +print('----', models['lbco'].cell.length_a.free) +# proj.analysis.show_free_params() diff --git a/tmp/short2.py b/tmp/short2.py new file mode 100644 index 00000000..d1bd5eb1 --- /dev/null +++ b/tmp/short2.py @@ -0,0 +1,228 @@ +import easydiffraction as ed +from easydiffraction import Experiment +from easydiffraction import Experiments +from easydiffraction import Project +from easydiffraction import SampleModel +from easydiffraction import SampleModels +from easydiffraction.experiments.categories.background import LineSegmentBackground +from easydiffraction.experiments.categories.background import Point +from easydiffraction.experiments.categories.linked_phases import LinkedPhase +from easydiffraction.experiments.categories.linked_phases import LinkedPhases +from easydiffraction.sample_models.categories.atom_sites import AtomSite +from easydiffraction.sample_models.categories.atom_sites import AtomSites +from easydiffraction.sample_models.categories.cell import Cell +from easydiffraction.sample_models.categories.space_group import SpaceGroup + +# from easydiffraction.core.parameters import BaseDescriptor, GenericDescriptor, Descriptor +# from easydiffraction.core.parameters import BaseParameter, GenericParameter, Parameter + +# Logger.configure(mode=Logger.Mode.VERBOSE, level=Logger.Level.DEBUG) +# Logger.configure(mode=Logger.Mode.COMPACT, level=Logger.Level.DEBUG) + + +# bd = BaseDescriptor() +# gd = GenericDescriptor() +# d = Descriptor() + +# bp = BaseParameter() +# gp = GenericParameter() +# p = Parameter() + +project = ed.Project() + + +sg = SpaceGroup() +sg.name_h_m = 'P n m a' +sg.name_h_m = 33.3 + +sg.it_coordinate_system_code = 'cab' + +cell = Cell() +cell.length_a = 5.4603 +cell.length_a = '5.4603' +cell.length_a = -5.4603 + +cell.lengtha = -5.4603 + + +exit() + +site = AtomSite() +site.label = 'Si' +site.type_symbol = 'Si' + +sites = AtomSites() +sites.add_from_args(site) + + +model = SampleModel(name='mdl') +model.space_group = sg +model.cell = cell +model.atom_sites = sites + +site = AtomSite() +site.label = 'Tb' +site.type_symbol = 'Tb' +sites.add_from_args(site) + + +# model.cell = 'k' + + +# print(model.parameters) +for p in model.parameters: + print(p) + +# exit() + +print('================================') + +models = SampleModels() +# models.add_from_args(model) +models.add_from_cif_path('tutorials/data/lbco.cif') + +print(models) +for p in models.parameters: + print(p) +print(models.as_cif) + +exp = Experiment(name='hrpt', data_path='tutorials/data/hrpt_lbco.xye') +print(exp) + +linked_phases = LinkedPhases() +linked_phase = LinkedPhase(id='lbco', scale=10.0) +linked_phases.add_from_args(linked_phase) + +exp.linked_phases = linked_phases + +exp.instrument.setup_wavelength = 1.494 +exp.instrument.calib_twotheta_offset = 0.6 + +exp.peak.broad_gauss_u = 0.1 +exp.peak.broad_gauss_v = -0.1 +exp.peak.broad_gauss_w = 0.1 +exp.peak.broad_lorentz_y = 0.1 + +bkg = LineSegmentBackground() +point1 = Point(x=10, y=170) +point2 = Point(x=30, y=170) +point3 = Point(x=50, y=170) +point4 = Point(x=110, y=170) +point5 = Point(x=165, y=170) +bkg.add_from_args(point1) +bkg.add_from_args(point2) +bkg.add_from_args(point3) +bkg.add_from_args(point4) +bkg.add_from_args(point5) +# exp.background.add_from_args(bkg) +exp.background = bkg + +# exp.background.add_from_args(x=10, y=170) +# exp.background.add_from_args(x=30, y=170) +# exp.background.add_from_args(x=50, y=170) +# exp.background.add_from_args(x=110, y=170) +# exp.background.add_from_args(x=165, y=170) + +experiments = Experiments() +print(experiments) + +experiments.add(exp) +print(experiments) +for p in experiments.parameters: + print(p) +# print(experiments.as_cif) +# exit() + +proj = Project(name='PROJ') +print(proj) + +proj.sample_models = models +proj.experiments = experiments + + +proj.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41) + + +def set_as_online(): + m = proj.sample_models['lbco'] + m.cell.length_a = 3.8909 + m.cell.length_b = 3.8909 + m.cell.length_c = 3.8909 + m.atom_sites['La'].b_iso = 0.5052 + m.atom_sites['Ba'].b_iso = 0.5049 + m.atom_sites['Co'].b_iso = 0.2370 + m.atom_sites['O'].b_iso = 1.3935 + e = proj.experiments['hrpt'] + e.linked_phases['lbco'].scale = 9.1351 + e.instrument.calib_twotheta_offset = 0.6226 + e.peak.broad_gauss_u = 0.0816 + e.peak.broad_gauss_v = -0.1159 + e.peak.broad_gauss_w = 0.1204 + e.peak.broad_lorentz_y = 0.0844 + e.background[10].y = 168.5585 + e.background[30].y = 164.3357 + e.background[50].y = 166.8881 + e.background[110].y = 175.4006 + e.background[165].y = 174.2813 + + +def set_as_initial(): + m = proj.sample_models['lbco'] + m.cell.length_a.uncertainty = None + m.cell.length_a = 3.885 + m.cell.length_b = 3.885 + m.cell.length_c = 3.885 + # m.atom_sites['La'].b_iso = 0.5052 + # m.atom_sites['Ba'].b_iso = 0.5049 + # m.atom_sites['Co'].b_iso = 0.2370 + # m.atom_sites['O'].b_iso = 1.3935 + # e = proj.experiments['hrpt'] + # e.linked_phases['lbco'].scale = 9.1351 + # e.instrument.calib_twotheta_offset = 0.6226 + # e.peak.broad_gauss_u = 0.0816 + # e.peak.broad_gauss_v = -0.1159 + # e.peak.broad_gauss_w = 0.1204 + # e.peak.broad_lorentz_y = 0.0844 + # e.background[10].y = 168.5585 + # e.background[30].y = 164.3357 + # e.background[50].y = 166.8881 + # e.background[110].y = 175.4006 + # e.background[165].y = 174.2813 + + +set_as_online() +# set_as_initial() +proj.plotter.engine = 'plotly' +proj.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) +# exit() + +models['lbco'].cell.length_a.free = True + +models['lbco'].atom_sites['La'].b_iso.free = True +models['lbco'].atom_sites['Ba'].b_iso.free = True +models['lbco'].atom_sites['Co'].b_iso.free = True +models['lbco'].atom_sites['O'].b_iso.free = True + +exp.instrument.calib_twotheta_offset.free = True + +exp.peak.broad_gauss_u.free = True +exp.peak.broad_gauss_v.free = True +exp.peak.broad_gauss_w.free = True +exp.peak.broad_lorentz_y.free = True + +exp.background[10].y.free = True +exp.background[30].y.free = True +exp.background[50].y.free = True +exp.background[110].y.free = True +exp.background[165].y.free = True + +exp.linked_phases['lbco'].scale.free = True + + +print('----', models['lbco'].cell.length_a.free) +proj.analysis.show_free_params() +proj.analysis.fit() + +# proj.plotter.engine = 'plotly' +# proj.plot_meas_vs_calc(expt_name='hrpt') +proj.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41) diff --git a/tmp/short3.py b/tmp/short3.py new file mode 100644 index 00000000..a261a944 --- /dev/null +++ b/tmp/short3.py @@ -0,0 +1,138 @@ +# %% [markdown] +# # Structure Refinement: LBCO, HRPT +# +# This minimalistic example is designed to be as compact as possible for +# a Rietveld refinement of a crystal structure using constant-wavelength +# neutron powder diffraction data for La0.5Ba0.5CoO3 from HRPT at PSI. +# +# It does not contain any advanced features or options, and includes no +# comments or explanations—these can be found in the other tutorials. +# Default values are used for all parameters if not specified. Only +# essential and self-explanatory code is provided. +# +# The example is intended for users who are already familiar with the +# EasyDiffraction library and want to quickly get started with a simple +# refinement. It is also useful for those who want to see what a +# refinement might look like in code. For a more detailed explanation of +# the code, please refer to the other tutorials. + +# %% [markdown] +# ## Import Library + +# %% +import easydiffraction as ed + +# %% [markdown] +# ## Step 1: Define Project + +# %% +project = ed.Project() + +project.plotter.x_min = 38 +project.plotter.x_max = 41 + +# %% [markdown] +# ## Step 2: Define Sample Model + +# %% +project.sample_models.add_minimal(name='lbco') + +# %% +sample_model = project.sample_models['lbco'] + +# %% +sample_model.space_group.name_h_m = 'P m -3 m' +sample_model.space_group.it_coordinate_system_code = '1' + +# %% +sample_model.cell.length_a = 3.88 + +# %% +sample_model.atom_sites.add_from_args( + label='La', type_symbol='La', fract_x=0., fract_y=0., fract_z=0, b_iso=0.5, occupancy=0.5, + wyckoff_letter='a' +) +sample_model.atom_sites.add_from_args( + label='Ba', type_symbol='Ba', fract_x=0, fract_y=0, fract_z=0, b_iso=0.5, occupancy=0.5, wyckoff_letter='a' +) +sample_model.atom_sites.add_from_args(label='Co', type_symbol='Co', fract_x=0.5, fract_y=0.5, fract_z=0.5, b_iso=0.5, wyckoff_letter='b') +sample_model.atom_sites.add_from_args(label='O', type_symbol='O', fract_x=0, fract_y=0.5, fract_z=0.5, b_iso=0.5, wyckoff_letter='c') + +# %% [markdown] +# ## Step 3: Define Experiment + +# %% +ed.download_from_repository('hrpt_lbco.xye', destination='data') + +# %% +project.experiments.add_from_data_path( + name='hrpt', + data_path='data/hrpt_lbco.xye', + sample_form='powder', + beam_mode='constant wavelength', + radiation_probe='neutron', +) + +# %% +experiment = project.experiments['hrpt'] + +# %% +experiment.instrument.setup_wavelength = 1.494 +experiment.instrument.calib_twotheta_offset = 0.6 + +# %% +experiment.peak.broad_gauss_u = 0.1 +experiment.peak.broad_gauss_v = -0.1 +experiment.peak.broad_gauss_w = 0.1 +experiment.peak.broad_lorentz_y = 0.1 + +# %% +experiment.background.add_from_args(x=10, y=170) +experiment.background.add_from_args(x=30, y=170) +experiment.background.add_from_args(x=50, y=170) +experiment.background.add_from_args(x=110, y=170) + +# %% +experiment.excluded_regions.add_from_args(start=0, end=5) +experiment.excluded_regions.add_from_args(start=130, end=180) + +# %% +experiment.linked_phases.add_from_args(id='lbco', scale=10.0) + +# %% [markdown] +# ## Step 4: Perform Analysis + +# %% +sample_model.cell.length_a.free = True + +sample_model.atom_sites['La'].b_iso.free = True +sample_model.atom_sites['Ba'].b_iso.free = True +sample_model.atom_sites['Co'].b_iso.free = True +sample_model.atom_sites['O'].b_iso.free = True + +# %% +experiment.instrument.calib_twotheta_offset.free = True + +experiment.peak.broad_gauss_u.free = True +experiment.peak.broad_gauss_v.free = True +experiment.peak.broad_gauss_w.free = True +experiment.peak.broad_lorentz_y.free = True + +experiment.background['10'].y.free = True +experiment.background['30'].y.free = True +experiment.background['50'].y.free = True +experiment.background['110'].y.free = True + +experiment.linked_phases['lbco'].scale.free = True + + +# sample_model.show_as_cif() +# experiment.show_as_cif() +# exit() + + +# %% +project.analysis.fit() + +# %% +project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) diff --git a/tmp/short5.py b/tmp/short5.py new file mode 100644 index 00000000..843b252d --- /dev/null +++ b/tmp/short5.py @@ -0,0 +1,316 @@ +from __future__ import annotations + +from typing import ParamSpec +from typing import TypeVar + +from easydiffraction.analysis.categories.constraints import Constraint +from easydiffraction.analysis.categories.constraints import Constraints +from easydiffraction.sample_models.categories.atom_sites import AtomSite # type: ignore +from easydiffraction.sample_models.categories.atom_sites import AtomSites # type: ignore +from easydiffraction.sample_models.categories.cell import Cell # type: ignore +from easydiffraction.sample_models.categories.space_group import SpaceGroup # type: ignore +from easydiffraction.sample_models.sample_model.base import SampleModelBase +from easydiffraction.sample_models.sample_model.factory import SampleModel +from easydiffraction.sample_models.sample_models import SampleModels +from easydiffraction.utils.logging import log # type: ignore + +P = ParamSpec('P') +R = TypeVar('R') + + +# --------------------------------------------------------------------- +# Example usage +# --------------------------------------------------------------------- +if __name__ == '__main__': + + log.info('-------- Types --------') + + s1 = AtomSite(label='La', type_symbol='La') + s1.fract_x.value = 1.234 + assert s1.fract_x.value == 1.234 + s1.fract_x.value = 'xyz' + assert s1.fract_x.value == 1.234 + s1.fract_x = 'qwe' + assert s1.fract_x.value == 1.234 + + assert s1.fract_x.free == False + s1.fract_x.free = True + assert s1.fract_x.free == True + s1.fract_x.free = 'abc' + assert s1.fract_x.free == True + + s1 = AtomSite(label='Si', type_symbol='Si', fract_x='uuuu') + assert s1.fract_x.value == 0.0 + + log.info('-------- Cell --------') + + c = Cell() + assert c.length_b.value == 10.0 + c = Cell(length_b=-8.8) + assert c.length_b.value == 10.0 + c = Cell(length_b='7.7') # type: ignore + assert c.length_b.value == 10.0 + c = Cell(length_b=6.6) + assert c.length_b.value == 6.6 + c.length_b.value = -5.5 + assert c.length_b.value == 6.6 + c.length_b = -4.4 + assert c.length_b.value == 6.6 + c.length_b = 3.3 + assert c.length_b.value == 3.3 + c.length_b = 2222.2 + assert c.length_b.value == 3.3 + c.length_b.free = 'qwe' # type: ignore + assert c.length_b.free is False + c.length_b.fre = 'fre' # type: ignore + assert getattr(c.length_b, 'fre', None) is None + c.length_b.qwe = 'qwe' # type: ignore + assert getattr(c.length_b, 'qwe', None) is None + c.length_b.description = 'desc' # type: ignore + assert c.length_b.description == 'Length of the b axis of the unit cell.' # type: ignore + assert c.length_b._public_readonly_attrs() == {'as_cif', 'constrained', + 'description', + 'unique_name', 'name', 'parameters', + 'uid', 'units'} + assert c.length_b._public_writable_attrs() == {'fit_max', 'fit_min', 'free', 'uncertainty', + 'value'} + c.qwe = 'qwe' + assert getattr(c.length_b, 'qwe', None) is None + assert c.length_b._cif_handler.names == ['_cell.length_b'] + assert len(c.length_b._minimizer_uid) == 16 + assert (c.parameters[1].value == 3.3) # type: ignore + + log.info('-------- SpaceGroup --------') + + sg = SpaceGroup() + assert sg.name_h_m.value == 'P 1' + sg = SpaceGroup(name_h_m='qwe') + assert sg.name_h_m.value == 'P 1' + sg = SpaceGroup(name_h_m='P b n m', it_coordinate_system_code='cab') + assert sg.name_h_m.value == 'P 1' + assert sg.it_coordinate_system_code.value == '' + sg = SpaceGroup(name_h_m='P n m a', it_coordinate_system_code='cab') + assert sg.name_h_m.value == 'P n m a' + assert sg.it_coordinate_system_code.value == 'cab' + sg.name_h_m = 34.9 + assert sg.name_h_m.value == 'P n m a' + sg.name_h_m = 'P 1' + assert sg.name_h_m.value == 'P 1' + assert sg.it_coordinate_system_code.value == '' + sg.name_h_m = 'P n m a' + assert sg.name_h_m.value == 'P n m a' + assert sg.it_coordinate_system_code.value == 'abc' + + log.info('-------- AtomSites --------') + + s1 = AtomSite(label='La', type_symbol='La') + assert s1.label.value == 'La' + assert s1.type_symbol.value == 'La' + s2 = AtomSite(label='Si', type_symbol='Si') + assert s2.label.value == 'Si' + assert s2.type_symbol.value == 'Si' + sites = AtomSites() + assert len(sites) == 0 + sites.add(s1) + sites.add(s2) + assert len(sites) == 2 + s1.label = 'Tb' + assert s1.label.value == 'Tb' + assert list(sites.keys()) == ['Tb', 'Si'] + assert sites['Tb'] is s1 + assert sites['Tb'].fract_x.value == 0.0 + s2.fract_x.value = 0.123 + assert s2.fract_x.value == 0.123 + s2.fract_x = 0.456 + assert s2.fract_x.value == 0.456 + sites['Tb'].fract_x = 0.789 + assert sites['Tb'].fract_x.value == 0.789 + sites['Tb'].qwe = 'qwe' # type: ignore + assert getattr(sites['Tb'], 'qwe', None) is None + sites.abc = 'abc' # type: ignore + assert getattr(sites, 'abc', None) is None + sites['Tb'].label = 'a b c' + assert sites['Tb'].label.value == 'Tb' + + assert sites['Tb']._label.value == 'Tb' + assert sites['Tb'].label.value == 'Tb' + assert sites['Tb'].name is None + + log.info('-------- SampleModel --------') + + model = SampleModel(name='lbco') + assert model.name == 'lbco' + assert model.cell.length_b.value == 10.0 + assert len(model.atom_sites) == 0 + model.atom_sites.add(s1) + model.atom_sites.add(s2) + assert len(model.atom_sites) == 2 + assert model.atom_sites.names == ['Tb', 'Si'] + assert model.atom_sites._items[0].label.value == 'Tb' + assert model.atom_sites._items[1].label.value == 'Si' + + log.info('-------- SampleModels --------') + + models = SampleModels() + assert len(models) == 0 + models.add(model) + assert len(models) == 1 + assert models._items[0].name == 'lbco' + + log.info('-------- PARENTS --------') + + assert models._parent is None + assert type(models['lbco']._parent) is SampleModels + assert type(models['lbco'].cell._parent) is SampleModelBase + assert type(models['lbco'].cell.length_b._parent) is Cell + assert type(models['lbco'].atom_sites._parent) is SampleModelBase + assert type(models['lbco'].atom_sites['Tb']._parent) is AtomSites + assert type(models['lbco'].atom_sites['Tb'].fract_x._parent) is AtomSite + + assert type(s1._parent) is AtomSites + assert type(models['lbco'].atom_sites) is AtomSites + assert len(models['lbco'].atom_sites) == 2 + del models['lbco'].atom_sites['Tb'] + assert len(models['lbco'].atom_sites) == 1 + assert s1._parent is None + assert type(models['lbco'].atom_sites) is AtomSites + + log.info('-------- PARAMETERS --------') + + assert len(models['lbco'].atom_sites['Si'].parameters) == 9 + assert models['lbco'].atom_sites['Si'].parameters[0].value == 'Si' + assert len(models['lbco'].atom_sites.parameters) == 9 + assert len(models['lbco'].cell.parameters) == 6 + assert len(models['lbco'].parameters) == 17 + assert len(models.parameters) == 17 + + log.info('-------- CIF HANDLERS --------') + + s3 = AtomSite(label='La', type_symbol='La') + assert s3.label.value == 'La' + assert s3.type_symbol.value == 'La' + + assert len(models['lbco'].atom_sites) == 1 + models['lbco'].atom_sites.add(s3) + assert len(models['lbco'].atom_sites) == 2 + assert models['lbco'].cell.length_b.as_cif == '_cell.length_b 10.0' + assert models['lbco'].cell.as_cif == """_cell.length_a 10.0 +_cell.length_b 10.0 +_cell.length_c 10.0 +_cell.angle_alpha 90.0 +_cell.angle_beta 90.0 +_cell.angle_gamma 90.0""" + + assert models['lbco'].atom_sites.as_cif == """loop_ +_atom_site.label +_atom_site.type_symbol +_atom_site.fract_x +_atom_site.fract_y +_atom_site.fract_z +_atom_site.Wyckoff_letter +_atom_site.occupancy +_atom_site.B_iso_or_equiv +_atom_site.adp_type +Si Si 0.456 0.0 0.0 a 1.0 0.0 Biso +La La 0.0 0.0 0.0 a 1.0 0.0 Biso""" + + print(models['lbco'].as_cif) + + assert models['lbco'].as_cif == """data_lbco + +_cell.length_a 10.0 +_cell.length_b 10.0 +_cell.length_c 10.0 +_cell.angle_alpha 90.0 +_cell.angle_beta 90.0 +_cell.angle_gamma 90.0 + +_space_group.name_H-M_alt "P 1" +_space_group.IT_coordinate_system_code + +loop_ +_atom_site.label +_atom_site.type_symbol +_atom_site.fract_x +_atom_site.fract_y +_atom_site.fract_z +_atom_site.Wyckoff_letter +_atom_site.occupancy +_atom_site.B_iso_or_equiv +_atom_site.adp_type +Si Si 0.456 0.0 0.0 a 1.0 0.0 Biso +La La 0.0 0.0 0.0 a 1.0 0.0 Biso""" + + assert models.as_cif == """data_lbco + +_cell.length_a 10.0 +_cell.length_b 10.0 +_cell.length_c 10.0 +_cell.angle_alpha 90.0 +_cell.angle_beta 90.0 +_cell.angle_gamma 90.0 + +_space_group.name_H-M_alt "P 1" +_space_group.IT_coordinate_system_code + +loop_ +_atom_site.label +_atom_site.type_symbol +_atom_site.fract_x +_atom_site.fract_y +_atom_site.fract_z +_atom_site.Wyckoff_letter +_atom_site.occupancy +_atom_site.B_iso_or_equiv +_atom_site.adp_type +Si Si 0.456 0.0 0.0 a 1.0 0.0 Biso +La La 0.0 0.0 0.0 a 1.0 0.0 Biso""" + + log.info('-------- Full Names --------') + + cell = Cell() + assert cell.unique_name == 'cell' + + assert cell.length_b.unique_name == 'cell.length_b' + + site = AtomSite(label='Tb', type_symbol='Tb') + assert site.unique_name == 'atom_site.Tb' + + sites = AtomSites() # + assert sites.unique_name is None + + sites.add(site) + assert site.unique_name == 'atom_site.Tb' + assert sites['Tb'].unique_name == 'atom_site.Tb' + + model = SampleModel(name='lbco') # + assert model.unique_name == 'lbco' + + model.cell = cell + assert cell.unique_name == 'lbco.cell' + assert cell.length_b.unique_name == 'lbco.cell.length_b' + assert model.cell.unique_name == 'lbco.cell' + assert model.cell.length_b.unique_name == 'lbco.cell.length_b' + + model.atom_sites = sites + assert sites.unique_name is None + assert model.atom_sites.unique_name is None + assert model.atom_sites['Tb'].unique_name == 'lbco.atom_site.Tb' + + models = SampleModels() # + assert models.unique_name is None + + models.add(model) + assert models['lbco'].cell.unique_name == 'lbco.cell' + assert models['lbco'].cell.length_b.unique_name == 'lbco.cell.length_b' + assert models['lbco'].atom_sites.unique_name is None + assert models['lbco'].atom_sites['Tb'].unique_name == 'lbco.atom_site.Tb' + + log.info('-------- Constraints --------') + con = Constraint(lhs_alias='cell.length_a', rhs_expr='2 * cell.length_b + 1.0') + assert con.lhs_alias.value == 'cell.length_a' + assert con.rhs_expr.value == '2 * cell.length_b + 1.0' + cons = Constraints() + assert len(cons) == 0 + cons.add(con) + assert len(cons) == 1 diff --git a/tmp/short6.py b/tmp/short6.py new file mode 100644 index 00000000..ef027c51 --- /dev/null +++ b/tmp/short6.py @@ -0,0 +1,85 @@ +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import CifHandler +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import RangeValidator +from easydiffraction.utils.logging import Logger +from easydiffraction.utils.logging import log + +Logger.configure( + level=Logger.Level.DEBUG, + mode=Logger.Mode.COMPACT, + reaction=Logger.Reaction.WARN, +) + + +class Cell(CategoryItem): + def __init__(self, *, length_a=None): + super().__init__() + + self._length_a = Parameter( + value_spec=AttributeSpec( + value=length_a, + default=10.0, + content_validator=RangeValidator(ge=0, le=1000), + ), + name='length_a', + description='Length of the a-axis of the unit cell.', + units='Å', + cif_handler=CifHandler(names=['_cell.length_a']), + ) + + @property + def length_a(self) -> Parameter: + """Parameter representing the a-axis length of the unit cell.""" + return self._length_a + + @length_a.setter + def length_a(self, v): + """Assign a raw value to length_a (validated internally).""" + self._length_a.value = v + + +# ---------------------- Example usage ---------------------- # + +if __name__ == '__main__': + c = Cell() + + c.length_a.value = 1.234 + log.info(f'c.length_a.value: {c.length_a.value}') + + c.length_a.value = -5.5 + log.info(f'c.length_a.value: {c.length_a.value}') + + c.length_a.value = 'xyz' + log.info(f'c.length_a.value: {c.length_a.value}') + + c.length_a.free = True + log.info(f'c.length_a.free: {c.length_a.free}') + + c.length_a.free = 'oops' + log.info(f'c.length_a.free: {c.length_a.free}') + + c.length_a = 'xyz' + log.info(f'c.length_a.value (after direct assign attempt): {c.length_a.value}') + + c_bad = Cell(length_a='xyz') + log.info(f'c_bad.length_a.value: {c_bad.length_a.value}') + + c_ok = Cell(length_a=2.5) + log.info(f'c_ok.length_a.value: {c_ok.length_a.value}') + + c_ok.length_a.description = 'read-only' + log.info(f'c_ok.length_a.description: {c_ok.length_a.description}') + + c_ok.length_a.aaa = 'aaa' + log.info(f'c_ok.length_a.aaa: {c_ok.length_a.aaa}') + + log.info(f'c_ok.length_a.bbb: {c_ok.length_a.bbb}') + + log.info(f'c_ok.length_a.fre: {c_ok.length_a.fre}') + + log.info(c.as_cif) + log.info(c.length_a.as_cif) + + log.info(c.length_a._cif_handler.uid) diff --git a/tmp/short7.py b/tmp/short7.py new file mode 100644 index 00000000..dfec2cd8 --- /dev/null +++ b/tmp/short7.py @@ -0,0 +1,129 @@ +import os +import tempfile + +from numpy.testing import assert_almost_equal + +from easydiffraction import Project +from easydiffraction import download_from_repository + +TEMP_DIR = tempfile.gettempdir() + + +def single_fit_neutron_pd_cwl_lbco() -> None: + # Create project + project = Project() + + # Set sample model + project.sample_models.add_minimal(name='lbco') + model = project.sample_models['lbco'] + model.space_group.name_h_m = 'P m -3 m' + model.cell.length_a = 3.88 + model.atom_sites.add_from_args( + label='La', + type_symbol='La', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + occupancy=0.5, + b_iso=0.1, + ) + model.atom_sites.add_from_args( + label='Ba', + type_symbol='Ba', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + occupancy=0.5, + b_iso=0.1, + ) + model.atom_sites.add_from_args( + label='Co', + type_symbol='Co', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.1, + ) + model.atom_sites.add_from_args( + label='O', + type_symbol='O', + fract_x=0, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='c', + b_iso=0.1, + ) + + # Set experiment + data_file = 'hrpt_lbco.xye' + download_from_repository(data_file, destination=TEMP_DIR) + project.experiments.add_from_data_path(name='hrpt', data_path=os.path.join(TEMP_DIR, data_file)) + expt = project.experiments['hrpt'] + expt.instrument.setup_wavelength = 1.494 + expt.instrument.calib_twotheta_offset = 0 + expt.peak.broad_gauss_u = 0.1 + expt.peak.broad_gauss_v = -0.1 + expt.peak.broad_gauss_w = 0.2 + expt.peak.broad_lorentz_x = 0 + expt.peak.broad_lorentz_y = 0 + expt.linked_phases.add_from_args(id='lbco', scale=5.0) + expt.background.add_from_args(x=10, y=170) + expt.background.add_from_args(x=165, y=170) + + expt.show_as_cif() + + # Prepare for fitting + project.analysis.current_calculator = 'cryspy' + project.analysis.current_minimizer = 'lmfit (leastsq)' + + # ------------ 1st fitting ------------ + + # Select fitting parameters + model.cell.length_a.free = True + expt.linked_phases['lbco'].scale.free = True + expt.instrument.calib_twotheta_offset.free = True + expt.background['10'].y.free = True + expt.background['165'].y.free = True + + # Perform fit + project.analysis.fit() + + # Compare fit quality + assert_almost_equal(project.analysis.fit_results.reduced_chi_square, desired=5.79, decimal=1) + + # ------------ 2nd fitting ------------ + + # Select fitting parameters + expt.peak.broad_gauss_u.free = True + expt.peak.broad_gauss_v.free = True + expt.peak.broad_gauss_w.free = True + expt.peak.broad_lorentz_y.free = True + + # Perform fit + project.analysis.fit() + + # Compare fit quality + assert_almost_equal(project.analysis.fit_results.reduced_chi_square, desired=4.41, decimal=1) + + # ------------ 3rd fitting ------------ + + # Select fitting parameters + model.atom_sites['La'].b_iso.free = True + model.atom_sites['Ba'].b_iso.free = True + model.atom_sites['Co'].b_iso.free = True + model.atom_sites['O'].b_iso.free = True + + # Perform fit + project.analysis.fit() + + # Show chart + project.plot_meas_vs_calc(expt_name='hrpt') + + # Compare fit quality + assert_almost_equal(project.analysis.fit_results.reduced_chi_square, desired=1.3, decimal=1) + + +single_fit_neutron_pd_cwl_lbco() diff --git a/tutorials-drafts/test_single-fit_pd-neut-tof_Si-DREAM_nc.py b/tmp/test_single-fit_pd-neut-tof_Si-DREAM_nc.py similarity index 59% rename from tutorials-drafts/test_single-fit_pd-neut-tof_Si-DREAM_nc.py rename to tmp/test_single-fit_pd-neut-tof_Si-DREAM_nc.py index 84090290..aa5bfee5 100644 --- a/tutorials-drafts/test_single-fit_pd-neut-tof_Si-DREAM_nc.py +++ b/tmp/test_single-fit_pd-neut-tof_Si-DREAM_nc.py @@ -2,6 +2,9 @@ # # Structure Refinement: Si (NCrystal sim), DREAM # %% + +import pytest + import easydiffraction as ed # %% [markdown] @@ -17,7 +20,7 @@ # ## Step 2: Sample Model # %% -project.sample_models.add(name='si') +project.sample_models.add_minimal(name='si') sample_model = project.sample_models['si'] # %% @@ -25,51 +28,64 @@ sample_model.space_group.it_coordinate_system_code = '1' # %% -sample_model.cell.length_a = 5.46872800 # 5.43146 +sample_model.cell.length_a = 5.46872800 # 5.43146 # %% -sample_model.atom_sites.add(label='Si', - type_symbol='Si', - fract_x=0., - fract_y=0., - fract_z=0.5, - wyckoff_letter='b', - b_iso=0.5) +import pathlib + +from easydiffraction.sample_models.categories.atom_sites import AtomSite + +sample_model.atom_sites.add( + AtomSite( + label='Si', + type_symbol='Si', + fract_x=0.0, + fract_y=0.0, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.5, + ) +) # %% [markdown] # ## Step 3: Experiment # %% -#ed.download_from_repository('NOM_9999_Si_640g_PAC_50_ff_ftfrgr_up-to-50.gr', +# ed.download_from_repository('NOM_9999_Si_640g_PAC_50_ff_ftfrgr_up-to-50.gr', # branch='d-spacing', # destination='data') # %% -project.experiments.add(name='dream', - sample_form='powder', - beam_mode='time-of-flight', - radiation_probe='neutron', - scattering_type='bragg', - data_path='tutorials/data/DREAM_mantle_bc240_nist_cif_2.xye') - #data_path='tutorials/data/DREAM_mantle_bc240_nist_nc_2.xye') +data_path = 'tutorials/data/DREAM_mantle_bc240_nist_cif_2.xye' +if not pathlib.Path(data_path).exists(): # pragma: no cover - environment dependent + pytest.skip(f'Missing data file: {data_path}', allow_module_level=True) +project.experiments.add_from_data_path( + name='dream', + sample_form='powder', + beam_mode='time-of-flight', + radiation_probe='neutron', + scattering_type='bragg', + data_path=data_path, +) +# data_path='tutorials/data/DREAM_mantle_bc240_nist_nc_2.xye') experiment = project.experiments['dream'] # %% -experiment.instrument.setup_twotheta_bank = 90.20761742567521 # 144.845 # 90.20761742567521 +experiment.instrument.setup_twotheta_bank = 90.20761742567521 # 144.845 # 90.20761742567521 experiment.instrument.calib_d_to_tof_offset = 0.0 -experiment.instrument.calib_d_to_tof_linear = 27896.388403762866 # 7476.91 # 278963884037.62866 +experiment.instrument.calib_d_to_tof_linear = 27896.388403762866 # 7476.91 # 278963884037.62866 experiment.instrument.calib_d_to_tof_linear = 26935.57560870018 -experiment.instrument.calib_d_to_tof_quad = -0.00001 # -1.54 # -1.0 +experiment.instrument.calib_d_to_tof_quad = -0.00001 # -1.54 # -1.0 # %% experiment.peak_profile_type = 'pseudo-voigt * ikeda-carpenter' experiment.peak.broad_gauss_sigma_0 = 3.0 experiment.peak.broad_gauss_sigma_1 = 40.0 experiment.peak.broad_gauss_sigma_2 = 0.0 -experiment.peak.broad_mix_beta_0 = 0.024 # 0.04221 -experiment.peak.broad_mix_beta_1 = 0 # 0.00946 +experiment.peak.broad_mix_beta_0 = 0.024 # 0.04221 +experiment.peak.broad_mix_beta_1 = 0 # 0.00946 experiment.peak.asym_alpha_0 = 0.14 -experiment.peak.asym_alpha_1 = 0.0 # 0.5971 +experiment.peak.asym_alpha_1 = 0.0 # 0.5971 # %% experiment.background_type = 'line-segment' @@ -77,30 +93,30 @@ experiment.background.add(x=x, y=0.2) # %% -experiment.linked_phases.add('si', scale=1) +from easydiffraction.experiments.categories.linked_phases import LinkedPhase + +experiment.linked_phases.add(LinkedPhase(id='si', scale=1)) # %% [markdown] # ## Step 4: Analysis # %% -project.plot_meas_vs_calc(expt_name='dream', - show_residual=True) -#exit() +project.plot_meas_vs_calc(expt_name='dream', show_residual=True) +# exit() # %% -#sample_model.cell.length_a.free = True +# sample_model.cell.length_a.free = True experiment.linked_phases['si'].scale.free = True -#experiment.instrument.calib_d_to_tof_offset.free = True +# experiment.instrument.calib_d_to_tof_offset.free = True experiment.peak.broad_gauss_sigma_0.free = True experiment.peak.broad_gauss_sigma_1.free = True -#experiment.peak.broad_gauss_sigma_2.free = True +# experiment.peak.broad_gauss_sigma_2.free = True experiment.peak.broad_mix_beta_0.free = True -#experiment.peak.broad_mix_beta_1.free = True +# experiment.peak.broad_mix_beta_1.free = True experiment.peak.asym_alpha_0.free = True -#experiment.peak.asym_alpha_1.free = True - +# experiment.peak.asym_alpha_1.free = True project.analysis.fit() @@ -109,7 +125,6 @@ exit() - project.sample_models['lbco'].atom_sites['La'].b_iso.free = True project.sample_models['lbco'].atom_sites['Ba'].b_iso.free = True project.sample_models['lbco'].atom_sites['Co'].b_iso.free = True diff --git a/tools/gen_tests_scaffold.py b/tools/gen_tests_scaffold.py new file mode 100644 index 00000000..336bebb9 --- /dev/null +++ b/tools/gen_tests_scaffold.py @@ -0,0 +1,107 @@ +# SPDX-FileCopyrightText: 2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""Generate a one-to-one unit test scaffold mirroring src/ folder +structure. +- Creates tests/unit//test_.py for each Python + file in src +- Inserts a minimal, consistent pytest skeleton with TODO markers. + +Usage: + pixi run python tools/gen_tests_scaffold.py +""" + +from __future__ import annotations + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SRC = ROOT / 'src' +TESTS_ROOT = ROOT / 'tests' / 'unit' + +IGNORED_DIRS = {'__pycache__'} +IGNORED_FILES = {'__init__.py'} + +HEADER = """# Auto-generated scaffold. Replace TODOs with concrete tests. +import pytest +import numpy as np + +# expected vs actual helpers + +def _assert_equal(expected, actual): + assert expected == actual + +""" + +TEMPLATE = """ +# Module under test: {module_import} + +# TODO: Replace with real, small tests per class/method. +# Keep names explicit: expected_*, actual_*; compare in a single assert. + +def test_module_import(): + import {module_import} as MUT + expected_module_name = "{module_import}" + actual_module_name = MUT.__name__ + _assert_equal(expected_module_name, actual_module_name) +""" + + +def module_import_from_path(py_path: Path) -> str: + rel = py_path.relative_to(SRC) + parts = list(rel.parts) + parts[-1] = parts[-1].removesuffix('.py') + # Build import path directly from src-relative parts; do not prefix + # with the top-level package name to avoid duplication when the + # first part is already the package (e.g., 'easydiffraction'). + return '.'.join(parts) + + +def ensure_file(path: Path, content: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + # Always (re)write to keep scaffold in sync when the generator + # evolves + path.write_text(content) + + +def ensure_package_dirs(dir_path: Path) -> None: + """Ensure every directory from TESTS_ROOT to dir_path has an + __init__.py. + + This makes each folder a package so pytest assigns unique module + names and avoids import file mismatch when multiple files share the + same basename (e.g., test_base.py) across different subdirectories. + """ + # Walk from TESTS_ROOT down to dir_path, creating __init__.py at + # each level + dir_path.mkdir(parents=True, exist_ok=True) + current = TESTS_ROOT + # If dir_path is the same as TESTS_ROOT, the loop will be skipped, + # but we still want to ensure __init__.py at TESTS_ROOT + for part in dir_path.relative_to(TESTS_ROOT).parts: + (current / '__init__.py').touch(exist_ok=True) + current = current / part + # Ensure the final directory also has __init__.py + (current / '__init__.py').touch(exist_ok=True) + + +def main(): + for py in SRC.rglob('*.py'): + if py.name in IGNORED_FILES: + continue + if any(p in IGNORED_DIRS for p in py.parts): + continue + module_import = module_import_from_path(py) + rel_dir = py.parent.relative_to(SRC) + test_dir = TESTS_ROOT / rel_dir + # Ensure package __init__.py files exist to avoid name + # collisions + ensure_package_dirs(test_dir) + test_file = test_dir / f'test_{py.stem}.py' + content = HEADER + TEMPLATE.format(module_import=module_import) + ensure_file(test_file, content) + print(f'Scaffold created/updated under: {TESTS_ROOT}') + + +if __name__ == '__main__': + main() diff --git a/tools/generate_package_docs.py b/tools/generate_package_docs.py new file mode 100644 index 00000000..671a4f0e --- /dev/null +++ b/tools/generate_package_docs.py @@ -0,0 +1,172 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""Generate project package structure markdown files. + +Outputs two docs under docs/architecture/: + - package-structure-short.md (folders/files only) + - package-structure-full.md (folders/files and classes) + +Run (from repo root): + pixi run python tools/generate_package_docs.py +""" + +from __future__ import annotations + +import ast +from dataclasses import dataclass +from dataclasses import field +from pathlib import Path +from typing import List + +REPO_ROOT = Path(__file__).resolve().parents[1] +SRC_ROOT = REPO_ROOT / 'src' / 'easydiffraction' +DOCS_OUT_DIR = REPO_ROOT / 'docs' / 'architecture' + + +IGNORE_DIRS = { + '__pycache__', + '.pytest_cache', + '.mypy_cache', + '.ruff_cache', + '.ipynb_checkpoints', +} + + +@dataclass +class Node: + name: str + path: Path + type: str # 'dir' | 'file' + children: List['Node'] = field(default_factory=list) + classes: List[str] = field(default_factory=list) + + +def parse_classes(py_path: Path) -> List[str]: + try: + src = py_path.read_text(encoding='utf-8') + except Exception: + return [] + try: + tree = ast.parse(src) + except Exception: + return [] + classes: List[str] = [] + for node in tree.body: + if isinstance(node, ast.ClassDef): + classes.append(node.name) + return classes + + +def build_tree(root: Path) -> Node: + def _walk(p: Path) -> Node: + if p.is_dir(): + node = Node(name=p.name, path=p, type='dir') + try: + entries = sorted(p.iterdir(), key=lambda q: (q.is_file(), q.name.lower())) + except PermissionError: + entries = [] + for child in entries: + if child.name in IGNORE_DIRS: + continue + if child.is_dir(): + node.children.append(_walk(child)) + elif child.suffix == '.py': + file_node = Node(name=child.name, path=child, type='file') + file_node.classes = parse_classes(child) + node.children.append(file_node) + return node + else: + n = Node(name=p.name, path=p, type='file') + n.classes = parse_classes(p) if p.suffix == '.py' else [] + return n + + return _walk(root) + + +def _branch(prefix: str, is_last: bool) -> str: + return f'{prefix}{"└── " if is_last else "├── "}' + + +def render_short(root: Node) -> List[str]: + lines: List[str] = [] + lines.append(f'📦 {root.name}') + + def _render(node: Node, prefix: str = '') -> None: + for idx, child in enumerate(node.children): + is_last = idx == len(node.children) - 1 + line_prefix = _branch(prefix, is_last) + if child.type == 'dir': + lines.append(f'{line_prefix}📁 {child.name}') + _render(child, prefix + (' ' if is_last else '│ ')) + else: + lines.append(f'{line_prefix}📄 {child.name}') + + _render(root) + return lines + + +def render_full(root: Node) -> List[str]: + lines: List[str] = [] + lines.append(f'📦 {root.name}') + + def _render(node: Node, prefix: str = '') -> None: + for idx, child in enumerate(node.children): + is_last = idx == len(node.children) - 1 + line_prefix = _branch(prefix, is_last) + if child.type == 'dir': + lines.append(f'{line_prefix}📁 {child.name}') + _render(child, prefix + (' ' if is_last else '│ ')) + else: + lines.append(f'{line_prefix}📄 {child.name}') + # Classes under file + for c_idx, cls in enumerate(child.classes): + c_last = c_idx == len(child.classes) - 1 + sub_prefix = prefix + (' ' if is_last else '│ ') + lines.append(f'{_branch(sub_prefix, c_last)}🏷️ class {cls}') + + _render(root) + return lines + + +def write_markdown(short_lines: List[str], full_lines: List[str]) -> None: + DOCS_OUT_DIR.mkdir(parents=True, exist_ok=True) + + short_md = DOCS_OUT_DIR / 'package-structure-short.md' + full_md = DOCS_OUT_DIR / 'package-structure-full.md' + + short_content = [ + '# Package Structure (short)', + '', + '```', + *short_lines, + '```', + '', + ] + full_content = [ + '# Package Structure (full)', + '', + '```', + *full_lines, + '```', + '', + ] + + short_md.write_text('\n'.join(short_content), encoding='utf-8') + full_md.write_text('\n'.join(full_content), encoding='utf-8') + + print(f'Wrote: {short_md.relative_to(REPO_ROOT)}') + print(f'Wrote: {full_md.relative_to(REPO_ROOT)}') + + +def main() -> None: + if not SRC_ROOT.exists(): + raise SystemExit(f'Source root not found: {SRC_ROOT}') + root = build_tree(SRC_ROOT) + short_lines = render_short(root) + full_lines = render_full(root) + write_markdown(short_lines, full_lines) + + +if __name__ == '__main__': + main() diff --git a/tools/naming_check.py b/tools/naming_check.py new file mode 100644 index 00000000..8160775c --- /dev/null +++ b/tools/naming_check.py @@ -0,0 +1,117 @@ +"""Check naming consistency across the EasyDiffraction source tree. + +Rules enforced: +- Concrete implementations: .py in s/ directories → + +- Abstract bases: _base.py → Base +- Factories: _factory.py → Factory +- Enums: _enum.py → Enum +- Mixins: _mixins.py → Mixin (or multiple classes) +- Directories like fit_support/, calculators/, minimizers/, etc. are + scanned recursively. + +Exit code 1 if any mismatches are found. +""" + +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] / 'src' / 'easydiffraction' +PATTERNS = { + '_base': r'(?P[A-Z][a-zA-Z0-9]*)Base', + '_factory': r'(?P[A-Z][a-zA-Z0-9]*)Factory', + '_enum': r'(?P[A-Z][a-zA-Z0-9]*)Enum', + '_mixins?': r'(?P[A-Z][a-zA-Z0-9]*)Mixin', +} + + +def extract_classes(file_path: Path): + """Return a list of class names defined in the file.""" + content = file_path.read_text(encoding='utf-8', errors='ignore') + return re.findall(r'^\s*class\s+([A-Z][A-Za-z0-9_]*)', content, flags=re.MULTILINE) + + +def snake_to_pascal(snake: str) -> str: + """Convert 'crysfml_calculator' → 'CrysfmlCalculator'.""" + return ''.join(part.capitalize() for part in snake.split('_')) + + +def check_file(file_path: Path): + """Validate naming conventions for a single file.""" + rel = file_path.relative_to(ROOT) + stem = file_path.stem + classes = extract_classes(file_path) + problems = [] + + # Ignore __init__.py and private modules + if stem.startswith('__'): + return [] + + # Expected class names + expected = [] + + # Handle base/factory/enum/mixin + for key, pattern in PATTERNS.items(): + if stem.endswith(key): + role = stem.replace(key, '') + expected.append( + re.compile(pattern.replace('(?P', f'(?P{snake_to_pascal(role)}')) + ) + break + else: + # Default: if file is in a known role directory, use that role + parent = file_path.parent.name + known_roles = ('calculators', 'minimizers', 'plotters', 'instruments', 'peaks') + if parent in known_roles: + role = snake_to_pascal(parent[:-1]) # remove trailing 's' for role + if stem == 'base': + expected_name = f'{role}Base' + elif stem == 'factory': + expected_name = f'{role}Factory' + else: + variant = snake_to_pascal(stem) + expected_name = f'{variant}{role}' + expected.append(re.compile(f'^{expected_name}$')) + else: + # Otherwise, expect class name to match the PascalCase file + # stem + expected_name = snake_to_pascal(stem) + expected.append(re.compile(f'^{expected_name}$')) + + # Compare classes + if not classes: + problems.append(f'{rel}: No class definitions found') + return problems + + match_found = False + for exp in expected: + for cls in classes: + if exp.match(cls): + match_found = True + break + if not match_found: + problems.append(f'{rel}: Unexpected class name(s): {", ".join(classes)}') + + return problems + + +def main(): + problems = [] + for file_path in ROOT.rglob('*.py'): + if any(skip in file_path.parts for skip in ('__pycache__', 'venv', '.venv', 'tests')): + continue + problems.extend(check_file(file_path)) + + if problems: + print('❌ Naming convention violations found:\n') + for p in problems: + print(' -', p) + print(f'\nTotal issues: {len(problems)}') + sys.exit(1) + + print('✅ All file/class names follow naming conventions.') + + +if __name__ == '__main__': + main() diff --git a/tools/test_scripts.py b/tools/test_scripts.py index be4320ed..88747b76 100644 --- a/tools/test_scripts.py +++ b/tools/test_scripts.py @@ -11,7 +11,12 @@ import pytest -TUTORIALS = list(Path('tutorials').rglob('*.py')) +# Mark this module as 'functional' so it's excluded by default +# (see pytest.ini) +pytestmark = pytest.mark.functional + +# Discover tutorial scripts, excluding temporary checkpoint files +TUTORIALS = [p for p in Path('tutorials').rglob('*.py') if '.ipynb_checkpoints' not in p.parts] @pytest.mark.parametrize('script_path', TUTORIALS) diff --git a/tools/update_spdx.py b/tools/update_spdx.py index e05e1174..ebfe7299 100644 --- a/tools/update_spdx.py +++ b/tools/update_spdx.py @@ -19,35 +19,64 @@ 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: - lines = f.readlines() + original_lines = f.readlines() - # Patterns to match existing SPDX lines + # Regexes for SPDX lines copy_re = re.compile(r'^#\s*SPDX-FileCopyrightText:.*$') lic_re = re.compile(r'^#\s*SPDX-License-Identifier:.*$') - new_lines = [] - found_copy = False - found_lic = False - - for line in lines: - if copy_re.match(line): - new_lines.append(COPYRIGHT_TEXT + '\n') - found_copy = True - elif lic_re.match(line): - new_lines.append(LICENSE_TEXT + '\n') - found_lic = True - else: - new_lines.append(line) - - # If not found, insert at top - insert_pos = 0 - if not found_copy: - new_lines.insert(insert_pos, COPYRIGHT_TEXT + '\n') - insert_pos += 1 - if not found_lic: - new_lines.insert(insert_pos, LICENSE_TEXT + '\n') - insert_pos += 1 - new_lines.insert(insert_pos, '\n') + # 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) @@ -55,13 +84,11 @@ def update_spdx_header(file_path: Path): def main(): """Recursively update or insert SPDX headers in all Python files - under the 'src' directory, skipping files located in virtual - environment folders ('venv' or '.venv'). + under the 'src' and 'tests' directories. """ - for py_file in Path('src').rglob('*.py'): - if 'venv' in py_file.parts or '.venv' in py_file.parts: - continue - update_spdx_header(py_file) + for base_dir in ('src', 'tests'): + for py_file in Path(base_dir).rglob('*.py'): + update_spdx_header(py_file) if __name__ == '__main__': diff --git a/tutorials/advanced_joint-fit_pd-neut-xray-cwl_PbSO4.py b/tutorials/advanced_joint-fit_pd-neut-xray-cwl_PbSO4.py index f3d064e0..4cfd0d70 100644 --- a/tutorials/advanced_joint-fit_pd-neut-xray-cwl_PbSO4.py +++ b/tutorials/advanced_joint-fit_pd-neut-xray-cwl_PbSO4.py @@ -15,9 +15,9 @@ # ## Import Library # %% -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository # %% [markdown] @@ -29,7 +29,7 @@ # #### Create Sample Model # %% -model = SampleModel('pbso4') +model = SampleModelFactory.create(name='pbso4') # %% [markdown] # #### Set Space Group @@ -49,11 +49,51 @@ # #### Set Atom Sites # %% -model.atom_sites.add('Pb', 'Pb', 0.1876, 0.25, 0.167, b_iso=1.37) -model.atom_sites.add('S', 'S', 0.0654, 0.25, 0.684, b_iso=0.3777) -model.atom_sites.add('O1', 'O', 0.9082, 0.25, 0.5954, b_iso=1.9764) -model.atom_sites.add('O2', 'O', 0.1935, 0.25, 0.5432, b_iso=1.4456) -model.atom_sites.add('O3', 'O', 0.0811, 0.0272, 0.8086, b_iso=1.2822) +model.atom_sites.add_from_args( + label='Pb', + type_symbol='Pb', + fract_x=0.1876, + fract_y=0.25, + fract_z=0.167, + wyckoff_letter='c', + b_iso=1.37, +) +model.atom_sites.add_from_args( + label='S', + type_symbol='S', + fract_x=0.0654, + fract_y=0.25, + fract_z=0.684, + wyckoff_letter='c', + b_iso=0.3777, +) +model.atom_sites.add_from_args( + label='O1', + type_symbol='O', + fract_x=0.9082, + fract_y=0.25, + fract_z=0.5954, + wyckoff_letter='c', + b_iso=1.9764, +) +model.atom_sites.add_from_args( + label='O2', + type_symbol='O', + fract_x=0.1935, + fract_y=0.25, + fract_z=0.5432, + wyckoff_letter='c', + b_iso=1.4456, +) +model.atom_sites.add_from_args( + label='O3', + type_symbol='O', + fract_x=0.0811, + fract_y=0.0272, + fract_z=0.8086, + wyckoff_letter='d', + b_iso=1.2822, +) # %% [markdown] @@ -73,7 +113,7 @@ # #### Create Experiment # %% -expt1 = Experiment( +expt1 = ExperimentFactory.create( name='npd', data_path='data/d1a_pbso4.dat', radiation_probe='neutron', @@ -119,13 +159,13 @@ (120.0, 244.4525), (153.0, 226.0595), ]: - expt1.background.add(x, y) + expt1.background.add_from_args(x=x, y=y) # %% [markdown] # #### Set Linked Phases # %% -expt1.linked_phases.add('pbso4', scale=1.5) +expt1.linked_phases.add_from_args(id='pbso4', scale=1.5) # %% [markdown] # ### Experiment 2: xrd @@ -139,7 +179,7 @@ # #### Create Experiment # %% -expt2 = Experiment( +expt2 = ExperimentFactory.create( name='xrd', data_path='data/lab_pbso4.dat', radiation_probe='xray', @@ -183,13 +223,13 @@ (4, 54.552), (5, -20.661), ]: - expt2.background.add(x, y) + expt2.background.add_from_args(order=x, coef=y) # %% [markdown] # #### Set Linked Phases # %% -expt2.linked_phases.add('pbso4', scale=0.001) +expt2.linked_phases.add_from_args(id='pbso4', scale=0.001) # %% [markdown] # ## Define Project diff --git a/tutorials/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py b/tutorials/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py index 94e9c62f..3db13fd6 100644 --- a/tutorials/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py +++ b/tutorials/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py @@ -91,7 +91,7 @@ # #### Add Sample Model # %% -project.sample_models.add(name='lbco') +project.sample_models.add_minimal(name='lbco') # %% [markdown] # #### Show Defined Sample Models @@ -126,7 +126,7 @@ # Add atom sites to the sample model. # %% -project.sample_models['lbco'].atom_sites.add( +project.sample_models['lbco'].atom_sites.add_from_args( label='La', type_symbol='La', fract_x=0, @@ -136,7 +136,7 @@ b_iso=0.5, occupancy=0.5, ) -project.sample_models['lbco'].atom_sites.add( +project.sample_models['lbco'].atom_sites.add_from_args( label='Ba', type_symbol='Ba', fract_x=0, @@ -146,7 +146,7 @@ b_iso=0.5, occupancy=0.5, ) -project.sample_models['lbco'].atom_sites.add( +project.sample_models['lbco'].atom_sites.add_from_args( label='Co', type_symbol='Co', fract_x=0.5, @@ -155,7 +155,7 @@ wyckoff_letter='b', b_iso=0.5, ) -project.sample_models['lbco'].atom_sites.add( +project.sample_models['lbco'].atom_sites.add_from_args( label='O', type_symbol='O', fract_x=0, @@ -295,11 +295,11 @@ # Add background points. # %% -project.experiments['hrpt'].background.add(x=10, y=170) -project.experiments['hrpt'].background.add(x=30, y=170) -project.experiments['hrpt'].background.add(x=50, y=170) -project.experiments['hrpt'].background.add(x=110, y=170) -project.experiments['hrpt'].background.add(x=165, y=170) +project.experiments['hrpt'].background.add_from_args(x=10, y=170) +project.experiments['hrpt'].background.add_from_args(x=30, y=170) +project.experiments['hrpt'].background.add_from_args(x=50, y=170) +project.experiments['hrpt'].background.add_from_args(x=110, y=170) +project.experiments['hrpt'].background.add_from_args(x=165, y=170) # %% [markdown] # Show current background points. @@ -313,7 +313,7 @@ # Link the sample model defined in the previous step to the experiment. # %% -project.experiments['hrpt'].linked_phases.add(id='lbco', scale=10.0) +project.experiments['hrpt'].linked_phases.add_from_args(id='lbco', scale=10.0) # %% [markdown] # #### Show Experiment as CIF @@ -564,11 +564,11 @@ # Set aliases for parameters. # %% -project.analysis.aliases.add( +project.analysis.aliases.add_from_args( label='biso_La', param_uid=project.sample_models['lbco'].atom_sites['La'].b_iso.uid, ) -project.analysis.aliases.add( +project.analysis.aliases.add_from_args( label='biso_Ba', param_uid=project.sample_models['lbco'].atom_sites['Ba'].b_iso.uid, ) @@ -577,7 +577,7 @@ # Set constraints. # %% -project.analysis.constraints.add(lhs_alias='biso_Ba', rhs_expr='biso_La') +project.analysis.constraints.add_from_args(lhs_alias='biso_Ba', rhs_expr='biso_La') # %% [markdown] # Show defined constraints. @@ -632,11 +632,11 @@ # Set more aliases for parameters. # %% -project.analysis.aliases.add( +project.analysis.aliases.add_from_args( label='occ_La', param_uid=project.sample_models['lbco'].atom_sites['La'].occupancy.uid, ) -project.analysis.aliases.add( +project.analysis.aliases.add_from_args( label='occ_Ba', param_uid=project.sample_models['lbco'].atom_sites['Ba'].occupancy.uid, ) @@ -645,7 +645,7 @@ # Set more constraints. # %% -project.analysis.constraints.add( +project.analysis.constraints.add_from_args( lhs_alias='occ_Ba', rhs_expr='1 - occ_La', ) @@ -703,5 +703,6 @@ # %% [markdown] # #### Show Project Summary + # %% project.summary.show_report() diff --git a/tutorials/cryst-struct_pd-neut-cwl_CoSiO4-D20.py b/tutorials/cryst-struct_pd-neut-cwl_CoSiO4-D20.py index 95a7f9f0..5c643747 100644 --- a/tutorials/cryst-struct_pd-neut-cwl_CoSiO4-D20.py +++ b/tutorials/cryst-struct_pd-neut-cwl_CoSiO4-D20.py @@ -9,9 +9,9 @@ # ## Import Library # %% -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository # %% [markdown] @@ -23,7 +23,7 @@ # #### Create Sample Model # %% -model = SampleModel('cosio') +model = SampleModelFactory.create(name='cosio') # %% [markdown] # #### Set Space Group @@ -44,12 +44,54 @@ # #### Set Atom Sites # %% -model.atom_sites.add('Co1', 'Co', 0, 0, 0, wyckoff_letter='a', b_iso=0.5) -model.atom_sites.add('Co2', 'Co', 0.279, 0.25, 0.985, wyckoff_letter='c', b_iso=0.5) -model.atom_sites.add('Si', 'Si', 0.094, 0.25, 0.429, wyckoff_letter='c', b_iso=0.5) -model.atom_sites.add('O1', 'O', 0.091, 0.25, 0.771, wyckoff_letter='c', b_iso=0.5) -model.atom_sites.add('O2', 'O', 0.448, 0.25, 0.217, wyckoff_letter='c', b_iso=0.5) -model.atom_sites.add('O3', 'O', 0.164, 0.032, 0.28, wyckoff_letter='d', b_iso=0.5) +model.atom_sites.add_from_args( + label='Co1', type_symbol='Co', fract_x=0, fract_y=0, fract_z=0, wyckoff_letter='a', b_iso=0.5 +) +model.atom_sites.add_from_args( + label='Co2', + type_symbol='Co', + fract_x=0.279, + fract_y=0.25, + fract_z=0.985, + wyckoff_letter='c', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='Si', + type_symbol='Si', + fract_x=0.094, + fract_y=0.25, + fract_z=0.429, + wyckoff_letter='c', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='O1', + type_symbol='O', + fract_x=0.091, + fract_y=0.25, + fract_z=0.771, + wyckoff_letter='c', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='O2', + type_symbol='O', + fract_x=0.448, + fract_y=0.25, + fract_z=0.217, + wyckoff_letter='c', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='O3', + type_symbol='O', + fract_x=0.164, + fract_y=0.032, + fract_z=0.28, + wyckoff_letter='d', + b_iso=0.5, +) # %% [markdown] # #### Symmetry Constraints @@ -86,7 +128,7 @@ # #### Create Experiment # %% -expt = Experiment(name='d20', data_path='data/co2sio4_d20.xye') +expt = ExperimentFactory.create(name='d20', data_path='data/co2sio4_d20.xye') # %% [markdown] # #### Set Instrument @@ -107,26 +149,26 @@ # #### Set Background # %% -expt.background.add(x=8, y=500) -expt.background.add(x=9, y=500) -expt.background.add(x=10, y=500) -expt.background.add(x=11, y=500) -expt.background.add(x=12, y=500) -expt.background.add(x=15, y=500) -expt.background.add(x=25, y=500) -expt.background.add(x=30, y=500) -expt.background.add(x=50, y=500) -expt.background.add(x=70, y=500) -expt.background.add(x=90, y=500) -expt.background.add(x=110, y=500) -expt.background.add(x=130, y=500) -expt.background.add(x=150, y=500) +expt.background.add_from_args(x=8, y=500) +expt.background.add_from_args(x=9, y=500) +expt.background.add_from_args(x=10, y=500) +expt.background.add_from_args(x=11, y=500) +expt.background.add_from_args(x=12, y=500) +expt.background.add_from_args(x=15, y=500) +expt.background.add_from_args(x=25, y=500) +expt.background.add_from_args(x=30, y=500) +expt.background.add_from_args(x=50, y=500) +expt.background.add_from_args(x=70, y=500) +expt.background.add_from_args(x=90, y=500) +expt.background.add_from_args(x=110, y=500) +expt.background.add_from_args(x=130, y=500) +expt.background.add_from_args(x=150, y=500) # %% [markdown] # #### Set Linked Phases # %% -expt.linked_phases.add('cosio', scale=1.0) +expt.linked_phases.add_from_args(id='cosio', scale=1.0) # %% [markdown] # ## Define Project @@ -229,11 +271,11 @@ # Set aliases for parameters. # %% -project.analysis.aliases.add( +project.analysis.aliases.add_from_args( label='biso_Co1', param_uid=project.sample_models['cosio'].atom_sites['Co1'].b_iso.uid, ) -project.analysis.aliases.add( +project.analysis.aliases.add_from_args( label='biso_Co2', param_uid=project.sample_models['cosio'].atom_sites['Co2'].b_iso.uid, ) @@ -242,7 +284,7 @@ # Set constraints. # %% -project.analysis.constraints.add( +project.analysis.constraints.add_from_args( lhs_alias='biso_Co2', rhs_expr='biso_Co1', ) diff --git a/tutorials/cryst-struct_pd-neut-cwl_HS-HRPT.py b/tutorials/cryst-struct_pd-neut-cwl_HS-HRPT.py index 515445c2..641a9c9c 100644 --- a/tutorials/cryst-struct_pd-neut-cwl_HS-HRPT.py +++ b/tutorials/cryst-struct_pd-neut-cwl_HS-HRPT.py @@ -9,9 +9,9 @@ # ## Import Library # %% -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository # %% [markdown] @@ -23,7 +23,7 @@ # #### Create Sample Model # %% -model = SampleModel('hs') +model = SampleModelFactory.create(name='hs') # %% [markdown] # #### Set Space Group @@ -35,6 +35,7 @@ # %% [markdown] # #### Set Unit Cell + # %% model.cell.length_a = 6.9 model.cell.length_c = 14.1 @@ -43,11 +44,51 @@ # #### Set Atom Sites # %% -model.atom_sites.add('Zn', 'Zn', 0, 0, 0.5, wyckoff_letter='b', b_iso=0.5) -model.atom_sites.add('Cu', 'Cu', 0.5, 0, 0, wyckoff_letter='e', b_iso=0.5) -model.atom_sites.add('O', 'O', 0.21, -0.21, 0.06, wyckoff_letter='h', b_iso=0.5) -model.atom_sites.add('Cl', 'Cl', 0, 0, 0.197, wyckoff_letter='c', b_iso=0.5) -model.atom_sites.add('H', '2H', 0.13, -0.13, 0.08, wyckoff_letter='h', b_iso=0.5) +model.atom_sites.add_from_args( + label='Zn', + type_symbol='Zn', + fract_x=0, + fract_y=0, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='Cu', + type_symbol='Cu', + fract_x=0.5, + fract_y=0, + fract_z=0, + wyckoff_letter='e', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='O', + type_symbol='O', + fract_x=0.21, + fract_y=-0.21, + fract_z=0.06, + wyckoff_letter='h', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='Cl', + type_symbol='Cl', + fract_x=0, + fract_y=0, + fract_z=0.197, + wyckoff_letter='c', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='H', + type_symbol='2H', + fract_x=0.13, + fract_y=-0.13, + fract_z=0.08, + wyckoff_letter='h', + b_iso=0.5, +) # %% [markdown] # #### Symmetry constraints @@ -84,7 +125,7 @@ # #### Create Experiment # %% -expt = Experiment(name='hrpt', data_path='data/hrpt_hs.xye') +expt = ExperimentFactory.create(name='hrpt', data_path='data/hrpt_hs.xye') # %% [markdown] # #### Set Instrument @@ -107,21 +148,21 @@ # #### Set Background # %% -expt.background.add(x=4.4196, y=500) -expt.background.add(x=6.6207, y=500) -expt.background.add(x=10.4918, y=500) -expt.background.add(x=15.4634, y=500) -expt.background.add(x=45.6041, y=500) -expt.background.add(x=74.6844, y=500) -expt.background.add(x=103.4187, y=500) -expt.background.add(x=121.6311, y=500) -expt.background.add(x=159.4116, y=500) +expt.background.add_from_args(x=4.4196, y=500) +expt.background.add_from_args(x=6.6207, y=500) +expt.background.add_from_args(x=10.4918, y=500) +expt.background.add_from_args(x=15.4634, y=500) +expt.background.add_from_args(x=45.6041, y=500) +expt.background.add_from_args(x=74.6844, y=500) +expt.background.add_from_args(x=103.4187, y=500) +expt.background.add_from_args(x=121.6311, y=500) +expt.background.add_from_args(x=159.4116, y=500) # %% [markdown] # #### Set Linked Phases # %% -expt.linked_phases.add('hs', scale=0.5) +expt.linked_phases.add_from_args(id='hs', scale=0.5) # %% [markdown] # ## Define Project diff --git a/tutorials/cryst-struct_pd-neut-tof_Si-SEPD.py b/tutorials/cryst-struct_pd-neut-tof_Si-SEPD.py index 0edbdbb0..0a9ef1fb 100644 --- a/tutorials/cryst-struct_pd-neut-tof_Si-SEPD.py +++ b/tutorials/cryst-struct_pd-neut-tof_Si-SEPD.py @@ -9,9 +9,9 @@ # ## Import Library # %% -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository # %% [markdown] @@ -23,7 +23,7 @@ # #### Create Sample Model # %% -model = SampleModel('si') +model = SampleModelFactory.create(name='si') # %% [markdown] # #### Set Space Group @@ -42,7 +42,14 @@ # #### Set Atom Sites # %% -model.atom_sites.add('Si', 'Si', 0.125, 0.125, 0.125, b_iso=0.5) +model.atom_sites.add_from_args( + label='Si', + type_symbol='Si', + fract_x=0.125, + fract_y=0.125, + fract_z=0.125, + b_iso=0.5, +) # %% [markdown] # ## Define Experiment @@ -59,7 +66,9 @@ # #### Create Experiment # %% -expt = Experiment(name='sepd', data_path='data/sepd_si.xye', beam_mode='time-of-flight') +expt = ExperimentFactory.create( + name='sepd', data_path='data/sepd_si.xye', beam_mode='time-of-flight' +) # %% [markdown] # #### Set Instrument @@ -94,13 +103,13 @@ # %% expt.background_type = 'line-segment' for x in range(0, 35000, 5000): - expt.background.add(x=x, y=200) + expt.background.add_from_args(x=x, y=200) # %% [markdown] # #### Set Linked Phases # %% -expt.linked_phases.add('si', scale=10.0) +expt.linked_phases.add_from_args(id='si', scale=10.0) # %% [markdown] # ## Define Project diff --git a/tutorials/cryst-struct_pd-neut-tof_multidata_NCAF-WISH.py b/tutorials/cryst-struct_pd-neut-tof_multidata_NCAF-WISH.py index 4394121a..aacf644d 100644 --- a/tutorials/cryst-struct_pd-neut-tof_multidata_NCAF-WISH.py +++ b/tutorials/cryst-struct_pd-neut-tof_multidata_NCAF-WISH.py @@ -12,9 +12,9 @@ # ## Import Library # %% -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository # %% [markdown] @@ -26,7 +26,7 @@ # #### Create Sample Model # %% -model = SampleModel('ncaf') +model = SampleModelFactory.create(name='ncaf') # %% [markdown] # #### Set Space Group @@ -45,12 +45,60 @@ # #### Set Atom Sites # %% -model.atom_sites.add('Ca', 'Ca', 0.4663, 0.0, 0.25, wyckoff_letter='b', b_iso=0.92) -model.atom_sites.add('Al', 'Al', 0.2521, 0.2521, 0.2521, wyckoff_letter='a', b_iso=0.73) -model.atom_sites.add('Na', 'Na', 0.0851, 0.0851, 0.0851, wyckoff_letter='a', b_iso=2.08) -model.atom_sites.add('F1', 'F', 0.1377, 0.3054, 0.1195, wyckoff_letter='c', b_iso=0.90) -model.atom_sites.add('F2', 'F', 0.3625, 0.3633, 0.1867, wyckoff_letter='c', b_iso=1.37) -model.atom_sites.add('F3', 'F', 0.4612, 0.4612, 0.4612, wyckoff_letter='a', b_iso=0.88) +model.atom_sites.add_from_args( + label='Ca', + type_symbol='Ca', + fract_x=0.4663, + fract_y=0.0, + fract_z=0.25, + wyckoff_letter='b', + b_iso=0.92, +) +model.atom_sites.add_from_args( + label='Al', + type_symbol='Al', + fract_x=0.2521, + fract_y=0.2521, + fract_z=0.2521, + wyckoff_letter='a', + b_iso=0.73, +) +model.atom_sites.add_from_args( + label='Na', + type_symbol='Na', + fract_x=0.0851, + fract_y=0.0851, + fract_z=0.0851, + wyckoff_letter='a', + b_iso=2.08, +) +model.atom_sites.add_from_args( + label='F1', + type_symbol='F', + fract_x=0.1377, + fract_y=0.3054, + fract_z=0.1195, + wyckoff_letter='c', + b_iso=0.90, +) +model.atom_sites.add_from_args( + label='F2', + type_symbol='F', + fract_x=0.3625, + fract_y=0.3633, + fract_z=0.1867, + wyckoff_letter='c', + b_iso=1.37, +) +model.atom_sites.add_from_args( + label='F3', + type_symbol='F', + fract_x=0.4612, + fract_y=0.4612, + fract_z=0.4612, + wyckoff_letter='a', + b_iso=0.88, +) # %% [markdown] # ## Define Experiment @@ -76,14 +124,14 @@ # #### Create Experiment # %% -expt56 = Experiment( +expt56 = ExperimentFactory.create( name='wish_5_6', data_path='data/wish_ncaf_5_6.xys', beam_mode='time-of-flight', ) # %% -expt47 = Experiment( +expt47 = ExperimentFactory.create( name='wish_4_7', data_path='data/wish_ncaf_4_7.xys', beam_mode='time-of-flight', @@ -160,7 +208,7 @@ (91958, 268), (102712, 262), ]: - expt56.background.add(x, y) + expt56.background.add_from_args(x=x, y=y) # %% expt47.background_type = 'line-segment' @@ -193,27 +241,27 @@ (92770, 255), (101524, 260), ]: - expt47.background.add(x, y) + expt47.background.add_from_args(x=x, y=y) # %% [markdown] # #### Set Linked Phases # %% -expt56.linked_phases.add('ncaf', scale=1.0) +expt56.linked_phases.add_from_args(id='ncaf', scale=1.0) # %% -expt47.linked_phases.add('ncaf', scale=2.0) +expt47.linked_phases.add_from_args(id='ncaf', scale=2.0) # %% [markdown] # #### Set Excluded Regions # %% -expt56.excluded_regions.add(start=0, end=10010) -expt56.excluded_regions.add(start=100010, end=200000) +expt56.excluded_regions.add_from_args(start=0, end=10010) +expt56.excluded_regions.add_from_args(start=100010, end=200000) # %% -expt47.excluded_regions.add(start=0, end=10006) -expt47.excluded_regions.add(start=100004, end=200000) +expt47.excluded_regions.add_from_args(start=0, end=10006) +expt47.excluded_regions.add_from_args(start=100004, end=200000) # %% [markdown] # ## Define Project diff --git a/tutorials/cryst-struct_pd-neut-tof_multiphase-LBCO-Si_McStas.py b/tutorials/cryst-struct_pd-neut-tof_multiphase-LBCO-Si_McStas.py index a334a77f..5db8150b 100644 --- a/tutorials/cryst-struct_pd-neut-tof_multiphase-LBCO-Si_McStas.py +++ b/tutorials/cryst-struct_pd-neut-tof_multiphase-LBCO-Si_McStas.py @@ -9,9 +9,9 @@ # ## Import Library # %% -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository # %% [markdown] @@ -23,7 +23,7 @@ # ### Create Sample Model 1: LBCO # %% -model_1 = SampleModel('lbco') +model_1 = SampleModelFactory.create(name='lbco') # %% [markdown] # #### Set Space Group @@ -42,16 +42,50 @@ # #### Set Atom Sites # %% -model_1.atom_sites.add('La', 'La', 0, 0, 0, wyckoff_letter='a', b_iso=0.2, occupancy=0.5) -model_1.atom_sites.add('Ba', 'Ba', 0, 0, 0, wyckoff_letter='a', b_iso=0.2, occupancy=0.5) -model_1.atom_sites.add('Co', 'Co', 0.5, 0.5, 0.5, wyckoff_letter='b', b_iso=0.2567) -model_1.atom_sites.add('O', 'O', 0, 0.5, 0.5, wyckoff_letter='c', b_iso=1.4041) +model_1.atom_sites.add_from_args( + label='La', + type_symbol='La', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.2, + occupancy=0.5, +) +model_1.atom_sites.add_from_args( + label='Ba', + type_symbol='Ba', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.2, + occupancy=0.5, +) +model_1.atom_sites.add_from_args( + label='Co', + type_symbol='Co', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.2567, +) +model_1.atom_sites.add_from_args( + label='O', + type_symbol='O', + fract_x=0, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='c', + b_iso=1.4041, +) # %% [markdown] # ### Create Sample Model 2: Si # %% -model_2 = SampleModel('si') +model_2 = SampleModelFactory.create(name='si') # %% [markdown] # #### Set Space Group @@ -70,12 +104,12 @@ # #### Set Atom Sites # %% -model_2.atom_sites.add( - 'Si', - 'Si', - 0.0, - 0.0, - 0.0, +model_2.atom_sites.add_from_args( + label='Si', + type_symbol='Si', + fract_x=0.0, + fract_y=0.0, + fract_z=0.0, wyckoff_letter='a', b_iso=0.0, ) @@ -95,7 +129,7 @@ # #### Create Experiment # %% -experiment = Experiment( +experiment = ExperimentFactory.create( name='mcstas', data_path='data/mcstas_lbco-si.xye', sample_form='powder', @@ -139,26 +173,26 @@ # Add background points. # %% -experiment.background.add(x=45000, y=0.2) -experiment.background.add(x=50000, y=0.2) -experiment.background.add(x=55000, y=0.2) -experiment.background.add(x=65000, y=0.2) -experiment.background.add(x=70000, y=0.2) -experiment.background.add(x=75000, y=0.2) -experiment.background.add(x=80000, y=0.2) -experiment.background.add(x=85000, y=0.2) -experiment.background.add(x=90000, y=0.2) -experiment.background.add(x=95000, y=0.2) -experiment.background.add(x=100000, y=0.2) -experiment.background.add(x=105000, y=0.2) -experiment.background.add(x=110000, y=0.2) +experiment.background.add_from_args(x=45000, y=0.2) +experiment.background.add_from_args(x=50000, y=0.2) +experiment.background.add_from_args(x=55000, y=0.2) +experiment.background.add_from_args(x=65000, y=0.2) +experiment.background.add_from_args(x=70000, y=0.2) +experiment.background.add_from_args(x=75000, y=0.2) +experiment.background.add_from_args(x=80000, y=0.2) +experiment.background.add_from_args(x=85000, y=0.2) +experiment.background.add_from_args(x=90000, y=0.2) +experiment.background.add_from_args(x=95000, y=0.2) +experiment.background.add_from_args(x=100000, y=0.2) +experiment.background.add_from_args(x=105000, y=0.2) +experiment.background.add_from_args(x=110000, y=0.2) # %% [markdown] # #### Set Linked Phases # %% -experiment.linked_phases.add('lbco', scale=4.0) -experiment.linked_phases.add('si', scale=0.2) +experiment.linked_phases.add_from_args(id='lbco', scale=4.0) +experiment.linked_phases.add_from_args(id='si', scale=0.2) # %% [markdown] # ## Define Project @@ -208,8 +242,8 @@ # Add excluded regions. # %% -experiment.excluded_regions.add(start=0, end=40000) -experiment.excluded_regions.add(start=108000, end=200000) +experiment.excluded_regions.add_from_args(start=0, end=40000) +experiment.excluded_regions.add_from_args(start=108000, end=200000) # %% [markdown] # Show excluded regions. diff --git a/tutorials/data/lbco.cif b/tutorials/data/lbco.cif new file mode 100644 index 00000000..e80dfecb --- /dev/null +++ b/tutorials/data/lbco.cif @@ -0,0 +1,36 @@ + data_lbco + + _space_group.IT_coordinate_system_code 1 + _space_group.name_H-M_alt "P m -3 m" + + _cell.angle_alpha 90 + _cell.angle_beta 90 + _cell.length_a 3.88(1) + _cell.length_b 3.88 + _cell.length_c 3.88 + + loop_ + _atom_site.ADP_type + _atom_site.B_iso_or_equiv + _atom_site.fract_x + _atom_site.fract_y + _atom_site.fract_z + _atom_site.label + _atom_site.occupancy + _atom_site.type_symbol + _atom_site.Wyckoff_letter + Biso 0.5 0.0 0.0 0.0 La 0.5 La a + Biso 0.5 0.0 0.0 0.0 Ba 0.5 Ba a + Biso 0.5 0.5 0.5 0.5 Co 1.0 Co b + Biso 0.5 0.0 0.5 0.5 O 1.0 O c + + loop_ + _pd_meas.2theta_scan + _pd_proc.intensity_total + _pd_calc.intensity_total_su + 10.0 167.0 12.6 + 10.05 157.0 12.5 + 10.1 187.0 13.3 + 10.15 197.0 14.0 + 10.2 164.0 12.5 + 10.25 171.0 13.0 diff --git a/tutorials/dmsc-summer-school-2025_analysis-powder-diffraction.py b/tutorials/dmsc-summer-school-2025_analysis-powder-diffraction.py index 7cd784ad..f2d553d0 100644 --- a/tutorials/dmsc-summer-school-2025_analysis-powder-diffraction.py +++ b/tutorials/dmsc-summer-school-2025_analysis-powder-diffraction.py @@ -183,8 +183,8 @@ # for more details about excluding regions from the measured data. # %% -project_1.experiments['sim_si'].excluded_regions.add(start=0, end=55000) -project_1.experiments['sim_si'].excluded_regions.add(start=105500, end=200000) +project_1.experiments['sim_si'].excluded_regions.add_from_args(start=0, end=55000) +project_1.experiments['sim_si'].excluded_regions.add_from_args(start=105500, end=200000) # %% [markdown] # To visualize the effect of excluding the high TOF region, we can plot @@ -353,13 +353,13 @@ # %% project_1.experiments['sim_si'].background_type = 'line-segment' -project_1.experiments['sim_si'].background.add(x=50000, y=0.01) -project_1.experiments['sim_si'].background.add(x=60000, y=0.01) -project_1.experiments['sim_si'].background.add(x=70000, y=0.01) -project_1.experiments['sim_si'].background.add(x=80000, y=0.01) -project_1.experiments['sim_si'].background.add(x=90000, y=0.01) -project_1.experiments['sim_si'].background.add(x=100000, y=0.01) -project_1.experiments['sim_si'].background.add(x=110000, y=0.01) +project_1.experiments['sim_si'].background.add_from_args(x=50000, y=0.01) +project_1.experiments['sim_si'].background.add_from_args(x=60000, y=0.01) +project_1.experiments['sim_si'].background.add_from_args(x=70000, y=0.01) +project_1.experiments['sim_si'].background.add_from_args(x=80000, y=0.01) +project_1.experiments['sim_si'].background.add_from_args(x=90000, y=0.01) +project_1.experiments['sim_si'].background.add_from_args(x=100000, y=0.01) +project_1.experiments['sim_si'].background.add_from_args(x=110000, y=0.01) # %% [markdown] # ### 🧩 Create a Sample Model – Si @@ -446,7 +446,7 @@ # #### Add Sample Model # %% -project_1.sample_models.add(name='si') +project_1.sample_models.add_minimal(name='si') # %% [markdown] # #### Set Space Group @@ -480,7 +480,7 @@ # for more details about the atom sites category. # %% -project_1.sample_models['si'].atom_sites.add( +project_1.sample_models['si'].atom_sites.add_from_args( label='Si', type_symbol='Si', fract_x=0, @@ -504,7 +504,7 @@ # for more details about linking a sample model to an experiment. # %% -project_1.experiments['sim_si'].linked_phases.add(id='si', scale=1.0) +project_1.experiments['sim_si'].linked_phases.add_from_args(id='si', scale=1.0) # %% [markdown] # ### 🚀 Analyze and Fit the Data @@ -781,8 +781,8 @@ project_2.plotter.engine = 'plotly' project_2.plot_meas(expt_name='sim_lbco') -project_2.experiments['sim_lbco'].excluded_regions.add(start=0, end=55000) -project_2.experiments['sim_lbco'].excluded_regions.add(start=105500, end=200000) +project_2.experiments['sim_lbco'].excluded_regions.add_from_args(start=0, end=55000) +project_2.experiments['sim_lbco'].excluded_regions.add_from_args(start=105500, end=200000) project_2.plot_meas(expt_name='sim_lbco') @@ -830,7 +830,7 @@ # %% tags=["solution", "hide-input"] # # Create a reference to the peak profile parameters from the Si sim_si_peak = project_1.experiments['sim_si'].peak -project_2.peak_profile_type = 'pseudo-voigt * ikeda-carpenter' +project_2.experiments['sim_lbco'].peak_profile_type = 'pseudo-voigt * ikeda-carpenter' project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_0 = sim_si_peak.broad_gauss_sigma_0.value project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_1 = sim_si_peak.broad_gauss_sigma_1.value project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_2 = sim_si_peak.broad_gauss_sigma_2.value @@ -859,13 +859,13 @@ # %% tags=["solution", "hide-input"] project_2.experiments['sim_lbco'].background_type = 'line-segment' -project_2.experiments['sim_lbco'].background.add(x=50000, y=0.2) -project_2.experiments['sim_lbco'].background.add(x=60000, y=0.2) -project_2.experiments['sim_lbco'].background.add(x=70000, y=0.2) -project_2.experiments['sim_lbco'].background.add(x=80000, y=0.2) -project_2.experiments['sim_lbco'].background.add(x=90000, y=0.2) -project_2.experiments['sim_lbco'].background.add(x=100000, y=0.2) -project_2.experiments['sim_lbco'].background.add(x=110000, y=0.2) +project_2.experiments['sim_lbco'].background.add_from_args(x=50000, y=0.2) +project_2.experiments['sim_lbco'].background.add_from_args(x=60000, y=0.2) +project_2.experiments['sim_lbco'].background.add_from_args(x=70000, y=0.2) +project_2.experiments['sim_lbco'].background.add_from_args(x=80000, y=0.2) +project_2.experiments['sim_lbco'].background.add_from_args(x=90000, y=0.2) +project_2.experiments['sim_lbco'].background.add_from_args(x=100000, y=0.2) +project_2.experiments['sim_lbco'].background.add_from_args(x=110000, y=0.2) # %% [markdown] # ### 🧩 Exercise 3: Define a Sample Model – LBCO @@ -951,7 +951,7 @@ # **Solution:** # %% tags=["solution", "hide-input"] -project_2.sample_models.add(name='lbco') +project_2.sample_models.add_minimal(name='lbco') # %% [markdown] # #### Exercise 3.2: Set Space Group @@ -1005,7 +1005,7 @@ # **Solution:** # %% tags=["solution", "hide-input"] -project_2.sample_models['lbco'].atom_sites.add( +project_2.sample_models['lbco'].atom_sites.add_from_args( label='La', type_symbol='La', fract_x=0, @@ -1015,7 +1015,7 @@ b_iso=0.95, occupancy=0.5, ) -project_2.sample_models['lbco'].atom_sites.add( +project_2.sample_models['lbco'].atom_sites.add_from_args( label='Ba', type_symbol='Ba', fract_x=0, @@ -1025,7 +1025,7 @@ b_iso=0.95, occupancy=0.5, ) -project_2.sample_models['lbco'].atom_sites.add( +project_2.sample_models['lbco'].atom_sites.add_from_args( label='Co', type_symbol='Co', fract_x=0.5, @@ -1034,7 +1034,7 @@ wyckoff_letter='b', b_iso=0.80, ) -project_2.sample_models['lbco'].atom_sites.add( +project_2.sample_models['lbco'].atom_sites.add_from_args( label='O', type_symbol='O', fract_x=0, @@ -1060,7 +1060,7 @@ # **Solution:** # %% tags=["solution", "hide-input"] -project_2.experiments['sim_lbco'].linked_phases.add(id='lbco', scale=1.0) +project_2.experiments['sim_lbco'].linked_phases.add_from_args(id='lbco', scale=1.0) # %% [markdown] # ### 🚀 Exercise 5: Analyze and Fit the Data @@ -1366,7 +1366,7 @@ # %% tags=["solution", "hide-input"] # Set Space Group -project_2.sample_models.add(name='si') +project_2.sample_models.add_minimal(name='si') project_2.sample_models['si'].space_group.name_h_m = 'F d -3 m' project_2.sample_models['si'].space_group.it_coordinate_system_code = '2' @@ -1374,7 +1374,7 @@ project_2.sample_models['si'].cell.length_a = 5.43 # Set Atom Sites -project_2.sample_models['si'].atom_sites.add( +project_2.sample_models['si'].atom_sites.add_from_args( label='Si', type_symbol='Si', fract_x=0, @@ -1385,7 +1385,7 @@ ) # Assign Sample Model to Experiment -project_2.experiments['sim_lbco'].linked_phases.add(id='si', scale=1.0) +project_2.experiments['sim_lbco'].linked_phases.add_from_args(id='si', scale=1.0) # %% [markdown] # #### Exercise 5.11: Refine the Scale of the Si Phase diff --git a/tutorials/pdf_pd-neut-cwl_Ni.py b/tutorials/pdf_pd-neut-cwl_Ni.py index 68569520..eb90d442 100644 --- a/tutorials/pdf_pd-neut-cwl_Ni.py +++ b/tutorials/pdf_pd-neut-cwl_Ni.py @@ -30,13 +30,13 @@ # ## Add Sample Model # %% -project.sample_models.add(name='ni') +project.sample_models.add_minimal(name='ni') # %% project.sample_models['ni'].space_group.name_h_m = 'F m -3 m' project.sample_models['ni'].space_group.it_coordinate_system_code = '1' project.sample_models['ni'].cell.length_a = 3.52387 -project.sample_models['ni'].atom_sites.add( +project.sample_models['ni'].atom_sites.add_from_args( label='Ni', type_symbol='Ni', fract_x=0.0, @@ -63,7 +63,7 @@ ) # %% -project.experiments['pdf'].linked_phases.add(id='ni', scale=1.0) +project.experiments['pdf'].linked_phases.add_from_args(id='ni', scale=1.0) project.experiments['pdf'].peak.damp_q = 0 project.experiments['pdf'].peak.broad_q = 0.03 project.experiments['pdf'].peak.cutoff_q = 27.0 diff --git a/tutorials/pdf_pd-neut-tof_Si-NOMAD.py b/tutorials/pdf_pd-neut-tof_Si-NOMAD.py index d35062f9..9bc66bb6 100644 --- a/tutorials/pdf_pd-neut-tof_Si-NOMAD.py +++ b/tutorials/pdf_pd-neut-tof_Si-NOMAD.py @@ -28,14 +28,14 @@ # ## Add Sample Model # %% -project.sample_models.add(name='si') +project.sample_models.add_minimal(name='si') # %% sample_model = project.sample_models['si'] sample_model.space_group.name_h_m.value = 'F d -3 m' sample_model.space_group.it_coordinate_system_code = '1' sample_model.cell.length_a = 5.43146 -sample_model.atom_sites.add( +sample_model.atom_sites.add_from_args( label='Si', type_symbol='Si', fract_x=0, @@ -63,7 +63,7 @@ # %% experiment = project.experiments['nomad'] -experiment.linked_phases.add(id='si', scale=1.0) +experiment.linked_phases.add_from_args(id='si', scale=1.0) experiment.peak.damp_q = 0.02 experiment.peak.broad_q = 0.03 experiment.peak.cutoff_q = 35.0 diff --git a/tutorials/pdf_pd-xray_NaCl.py b/tutorials/pdf_pd-xray_NaCl.py index a650129c..c872fe72 100644 --- a/tutorials/pdf_pd-xray_NaCl.py +++ b/tutorials/pdf_pd-xray_NaCl.py @@ -32,16 +32,16 @@ # ## Add Sample Model # %% -project.sample_models.add(name='nacl') +project.sample_models.add_minimal(name='nacl') # %% project.sample_models['nacl'].space_group.name_h_m = 'F m -3 m' project.sample_models['nacl'].space_group.it_coordinate_system_code = '1' project.sample_models['nacl'].cell.length_a = 5.62 -project.sample_models['nacl'].atom_sites.add( +project.sample_models['nacl'].atom_sites.add_from_args( label='Na', type_symbol='Na', fract_x=0, fract_y=0, fract_z=0, wyckoff_letter='a', b_iso=1.0 ) -project.sample_models['nacl'].atom_sites.add( +project.sample_models['nacl'].atom_sites.add_from_args( label='Cl', type_symbol='Cl', fract_x=0.5, @@ -85,7 +85,7 @@ project.experiments['xray_pdf'].peak.damp_particle_diameter = 0 # %% -project.experiments['xray_pdf'].linked_phases.add(id='nacl', scale=0.5) +project.experiments['xray_pdf'].linked_phases.add_from_args(id='nacl', scale=0.5) # %% [markdown] # ## Select Fitting Parameters diff --git a/tutorials/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py b/tutorials/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py index 019f1c40..83597257 100644 --- a/tutorials/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py +++ b/tutorials/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py @@ -32,7 +32,7 @@ # ## Step 2: Define Sample Model # %% -project.sample_models.add(name='lbco') +project.sample_models.add_minimal(name='lbco') # %% sample_model = project.sample_models['lbco'] @@ -45,10 +45,38 @@ sample_model.cell.length_a = 3.88 # %% -sample_model.atom_sites.add('La', 'La', 0, 0, 0, b_iso=0.5, occupancy=0.5) -sample_model.atom_sites.add('Ba', 'Ba', 0, 0, 0, b_iso=0.5, occupancy=0.5) -sample_model.atom_sites.add('Co', 'Co', 0.5, 0.5, 0.5, b_iso=0.5) -sample_model.atom_sites.add('O', 'O', 0, 0.5, 0.5, b_iso=0.5) +sample_model.atom_sites.add_from_args( + label='La', + type_symbol='La', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.5, + occupancy=0.5, +) +sample_model.atom_sites.add_from_args( + label='Ba', + type_symbol='Ba', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.5, + occupancy=0.5, +) +sample_model.atom_sites.add_from_args( + label='Co', + type_symbol='Co', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.5, +) +sample_model.atom_sites.add_from_args( + label='O', type_symbol='O', fract_x=0, fract_y=0.5, fract_z=0.5, wyckoff_letter='c', b_iso=0.5 +) # %% [markdown] # ## Step 3: Define Experiment @@ -79,18 +107,18 @@ experiment.peak.broad_lorentz_y = 0.1 # %% -experiment.background.add(x=10, y=170) -experiment.background.add(x=30, y=170) -experiment.background.add(x=50, y=170) -experiment.background.add(x=110, y=170) -experiment.background.add(x=165, y=170) +experiment.background.add_from_args(x=10, y=170) +experiment.background.add_from_args(x=30, y=170) +experiment.background.add_from_args(x=50, y=170) +experiment.background.add_from_args(x=110, y=170) +experiment.background.add_from_args(x=165, y=170) # %% -experiment.excluded_regions.add(start=0, end=5) -experiment.excluded_regions.add(start=165, end=180) +experiment.excluded_regions.add_from_args(start=0, end=5) +experiment.excluded_regions.add_from_args(start=165, end=180) # %% -experiment.linked_phases.add(id='lbco', scale=10.0) +experiment.linked_phases.add_from_args(id='lbco', scale=10.0) # %% [markdown] # ## Step 4: Perform Analysis