From 6a52ea90a505f1ea480fb72748efb0ad3478f933 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sat, 14 Mar 2026 21:10:31 +0100 Subject: [PATCH 1/5] Add single-crystal diffraction support via cryspy (#127) * Implement the extinction category * Implement the linked crystal category * Add base class for single crystal experiments * Clarify sy handling; move background property * Update BraggScExperiment class * Add SC Bragg reflection data and linked crystal * Add single-crystal Bragg CW data mapping * Remove unused linked crystal validator * Add minimizer flag to calculator API * Remove lru_cache; update data index hash * Add single-crystal Tb2TiO7 HEiDi tutorial * Add TODO about sy handling when loading CIF * Add single-crystal support to Cryspy calculator * Add stol propagation and d-spacing for SC data * Add d-spacing computation and fix plotting checks * Update SC tutorial * Use Cryspy-calculated intensity output * Update SC tutorial * Update data index checksum * Add new SC-TOF tutorial * Add TOF single-crystal support via Cryspy * Apply formatting * Cache tutorials index; adjust Bragg SC test * Add single-crystal F2 meas-vs-calc plot * Update SG tutorials * Add Single Crystal Diffraction section to docs * Update SC tutorial * Lower coverage threshold to 60% * Clarify Miller index and intensity descriptions * Add TODOs and fix docstring * Remove ED tutorial data; tidy pyproject comments * Remove kwargs from Refln init; add return type * Add units to extinction parameters * Add single-crystal neutron fitting tests * Reduce Ruff excludes; lint/format tests * Simplify single-crystal category initializers * Rename refln_id to id per coreCIF * Add CW/TOF SC ASCII import with wavelength * Rename plot_sg to plot_sc for single-crystal * Add single-crystal scatter comparison plotting * Extract scatter trace creation to helper * Rename traces for powder and single-crystal * Use Plotly autorange; drop fixed axis limits * Extract Plotly config to helper * Centralize Plotly figure display logic * Refactor Plotly figure creation and formatting * Draw identity line in paper coordinates * Add reusable Plotly layout helper * Extract diagonal reference line helper * Rename plot methods for powder/single-crystal * Reformat Plotly plotter for readability * Clarify diffraction plotter docstrings * Add single-crystal plotting tests * Remove deprecated plot alias; update tests * Add sin(theta)/lambda to d-spacing conversion * Update SC data API; compute d-spacing via util * Fix reflection index attribute names * Add single-crystal support to plotter base * Unify powder/SC plotting with axis auto-detect * Unify plotting API; replace d_spacing with x * Rename all_x to unfiltered_x * Rename meas/calc to intensity_*; add axis aliases * Rename TotalData module to total_pd * Update tests for sample-form and intensity API * Log read/validation errors; import numpy globally * Move instrument and extinction to SC base * Split Bragg SC into CWL/TOF; update factory * Refactor single-crystal instrument support * Move instrument init to BraggPdExperiment * Update tests for Pd/Sc instrument classes * Add scattering-type aware x-axis defaults * Increase CIF float precision to 8 decimals * Update pixi lock * Update docs code examples for style consistency * Pin scipp for ARM macOS; use editable local pkg * Refactor plotting; unify axis and data prep * Add macOS 14.0 minimum version requirement * Update plot_meas_vs_calc calls to use 'x' parameter for d_spacing --- docs/mkdocs.yml | 5 +- docs/tutorials/index.md | 11 +- docs/user-guide/analysis-workflow/analysis.md | 12 +- .../analysis-workflow/experiment.md | 43 +- docs/user-guide/analysis-workflow/model.md | 4 +- docs/user-guide/analysis-workflow/project.md | 4 +- docs/user-guide/first-steps.md | 18 +- pixi.lock | 7600 ++++++++++------- pixi.toml | 29 +- pyproject.toml | 16 +- .../analysis/calculators/base.py | 1 + .../analysis/calculators/cryspy.py | 365 +- .../analysis/fit_helpers/metrics.py | 6 +- src/easydiffraction/analysis/fitting.py | 6 +- src/easydiffraction/display/plotters/ascii.py | 80 +- src/easydiffraction/display/plotters/base.py | 155 +- .../display/plotters/plotly.py | 266 +- src/easydiffraction/display/plotting.py | 538 +- .../categories/background/chebyshev.py | 4 +- .../categories/background/line_segment.py | 4 +- .../experiments/categories/data/bragg_pd.py | 223 +- .../experiments/categories/data/bragg_sc.py | 329 +- .../experiments/categories/data/factory.py | 11 +- .../categories/data/{total.py => total_pd.py} | 111 +- .../categories/excluded_regions.py | 2 +- .../experiments/categories/extinction.py | 65 + .../experiments/categories/instrument/cwl.py | 42 +- .../categories/instrument/factory.py | 31 +- .../experiments/categories/instrument/tof.py | 22 +- .../experiments/categories/linked_crystal.py | 71 + .../experiments/categories/linked_phases.py | 1 + .../experiments/experiment/__init__.py | 6 +- .../experiments/experiment/base.py | 75 +- .../experiments/experiment/bragg_pd.py | 50 +- .../experiments/experiment/bragg_sc.py | 111 +- .../experiments/experiment/factory.py | 56 +- .../experiment/instrument_mixin.py | 46 - .../experiments/experiment/total_pd.py | 6 +- src/easydiffraction/io/cif/serialize.py | 9 +- src/easydiffraction/project/project.py | 12 +- src/easydiffraction/utils/utils.py | 23 +- ..._powder-diffraction_constant-wavelength.py | 26 +- .../test_powder-diffraction_time-of-flight.py | 1 - .../test_single-crystal-diffraction.py | 90 + .../analysis/fit_helpers/test_metrics.py | 6 +- .../easydiffraction/core/test_parameters.py | 2 +- .../display/plotters/test_ascii.py | 29 +- .../display/plotters/test_base.py | 12 +- .../display/plotters/test_plotly.py | 94 +- .../easydiffraction/display/test_plotting.py | 46 +- .../categories/background/test_chebyshev.py | 2 +- .../background/test_line_segment.py | 2 +- .../categories/instrument/test_cwl.py | 4 +- .../categories/instrument/test_factory.py | 6 +- .../categories/instrument/test_tof.py | 4 +- .../categories/test_excluded_regions.py | 2 +- .../experiments/experiment/test_bragg_pd.py | 4 +- .../experiments/experiment/test_bragg_sc.py | 11 +- .../experiment/test_instrument_mixin.py | 9 - .../experiments/experiment/test_total_pd.py | 4 +- .../easydiffraction/io/cif/test_serialize.py | 6 +- .../io/cif/test_serialize_more.py | 2 +- tutorials/ed-13.py | 14 +- tutorials/ed-14.py | 109 + tutorials/ed-15.py | 100 + tutorials/index.json | 7 + 66 files changed, 7200 insertions(+), 3861 deletions(-) rename src/easydiffraction/experiments/categories/data/{total.py => total_pd.py} (79%) create mode 100644 src/easydiffraction/experiments/categories/extinction.py create mode 100644 src/easydiffraction/experiments/categories/linked_crystal.py delete mode 100644 src/easydiffraction/experiments/experiment/instrument_mixin.py create mode 100644 tests/integration/fitting/test_single-crystal-diffraction.py delete mode 100644 tests/unit/easydiffraction/experiments/experiment/test_instrument_mixin.py create mode 100644 tutorials/ed-14.py create mode 100644 tutorials/ed-15.py diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 6641a35b..cf1795b4 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -73,12 +73,15 @@ nav: - LBCO quick code: tutorials/ed-2.ipynb - LBCO basic: tutorials/ed-3.ipynb - PbSO4 advanced: tutorials/ed-4.ipynb - - Standard Diffraction: + - Powder Diffraction: - Co2SiO4 pd-neut-cwl: tutorials/ed-5.ipynb - HS pd-neut-cwl: tutorials/ed-6.ipynb - Si pd-neut-tof: tutorials/ed-7.ipynb - NCAF pd-neut-tof: tutorials/ed-8.ipynb - LBCO+Si McStas: tutorials/ed-9.ipynb + - Single Crystal Diffraction: + - Tb2TiO7 sg-neut-cwl: tutorials/ed-14.ipynb + - Taurine sg-neut-tof: tutorials/ed-15.ipynb - Pair Distribution Function: - Ni pd-neut-cwl: tutorials/ed-10.ipynb - Si pd-neut-tof: tutorials/ed-11.ipynb diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index 55f406a4..154fd5c6 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -42,7 +42,7 @@ The tutorials are organized into the following categories. refinement of the PbSO4 crystal structure based on the joint fit of both X-ray and neutron diffraction data. -## Standard Diffraction +## Powder Diffraction - [Co2SiO4 `pd-neut-cwl`](ed-5.ipynb) – Demonstrates a Rietveld refinement of the Co2SiO4 crystal structure using constant wavelength neutron powder @@ -61,6 +61,15 @@ The tutorials are organized into the following categories. secondary phase using time-of-flight neutron powder diffraction data simulated with McStas. +## Single Crystal Diffraction + +- [Tb2TiO7 `sg-neut-cwl`](ed-14.ipynb) – Demonstrates structure refinement of + Tb2TiO7 using constant wavelength neutron single crystal diffraction data from + HEiDi at FRM II. +- [Taurine `sg-neut-tof`](ed-15.ipynb) – Demonstrates structure refinement of + Taurine using time-of-flight neutron single crystal diffraction data from + SENJU at J-PARC. + ## Pair Distribution Function (PDF) - [Ni `pd-neut-cwl`](ed-10.ipynb) – Demonstrates a PDF analysis of Ni using data diff --git a/docs/user-guide/analysis-workflow/analysis.md b/docs/user-guide/analysis-workflow/analysis.md index cdb948d2..d22d19f1 100644 --- a/docs/user-guide/analysis-workflow/analysis.md +++ b/docs/user-guide/analysis-workflow/analysis.md @@ -271,21 +271,21 @@ An example of setting aliases for parameters in a sample model: # Set aliases for the atomic displacement parameters project.analysis.aliases.add( label='biso_La', - param_uid=project.sample_models['lbco'].atom_sites['La'].b_iso.uid + param_uid=project.sample_models['lbco'].atom_sites['La'].b_iso.uid, ) project.analysis.aliases.add( label='biso_Ba', - param_uid=project.sample_models['lbco'].atom_sites['Ba'].b_iso.uid + param_uid=project.sample_models['lbco'].atom_sites['Ba'].b_iso.uid, ) # Set aliases for the occupancies of the atom sites project.analysis.aliases.add( label='occ_La', - param_uid=project.sample_models['lbco'].atom_sites['La'].occupancy.uid + param_uid=project.sample_models['lbco'].atom_sites['La'].occupancy.uid, ) project.analysis.aliases.add( label='occ_Ba', - param_uid=project.sample_models['lbco'].atom_sites['Ba'].occupancy.uid + param_uid=project.sample_models['lbco'].atom_sites['Ba'].occupancy.uid, ) ``` @@ -302,12 +302,12 @@ An example of setting constraints for the aliases defined above: ```python project.analysis.constraints.add( lhs_alias='biso_Ba', - rhs_expr='biso_La' + rhs_expr='biso_La', ) project.analysis.constraints.add( lhs_alias='occ_Ba', - rhs_expr='1 - occ_La' + rhs_expr='1 - occ_La', ) ``` diff --git a/docs/user-guide/analysis-workflow/experiment.md b/docs/user-guide/analysis-workflow/experiment.md index 3bbc9fdc..da08cd43 100644 --- a/docs/user-guide/analysis-workflow/experiment.md +++ b/docs/user-guide/analysis-workflow/experiment.md @@ -105,20 +105,24 @@ explicitly defined: ```python # Add an experiment with default parameters, based on the specified type. -project.experiments.add_from_data_path(name='hrpt', - data_path='data/hrpt_lbco.xye', - sample_form='powder', - beam_mode='constant wavelength', - radiation_probe='neutron', - scattering_type='bragg') +project.experiments.add_from_data_path( + name='hrpt', + data_path='data/hrpt_lbco.xye', + sample_form='powder', + beam_mode='constant wavelength', + radiation_probe='neutron', + scattering_type='bragg', +) ``` To add an experiment of default type, you can simply do: ```python # Add an experiment of default type -project.experiments.add_from_data_path(name='hrpt', - data_path='data/hrpt_lbco.xye') +project.experiments.add_from_data_path( + name='hrpt', + data_path='data/hrpt_lbco.xye', +) ``` If you do not have measured data for fitting and only want to view the simulated @@ -127,10 +131,12 @@ pattern, you can define an experiment without measured data using the ```python # Add an experiment without measured data -project.experiments.add_without_data(name='hrpt', - sample_form='powder', - beam_mode='constant wavelength', - radiation_probe='x-ray') +project.experiments.add_without_data( + name='hrpt', + sample_form='powder', + beam_mode='constant wavelength', + radiation_probe='x-ray', +) ``` Finally, you can also add an experiment by passing the experiment object @@ -139,11 +145,14 @@ directly using the `add` method: ```python # Add an experiment by passing the experiment object directly from easydiffraction import Experiment -experiment = Experiment(name='hrpt', - sample_form='powder', - beam_mode='constant wavelength', - radiation_probe='neutron', - scattering_type='bragg') + +experiment = Experiment( + name='hrpt', + sample_form='powder', + beam_mode='constant wavelength', + radiation_probe='neutron', + scattering_type='bragg', +) project.experiments.add(experiment) ``` diff --git a/docs/user-guide/analysis-workflow/model.md b/docs/user-guide/analysis-workflow/model.md index 69dbe765..9cdb4c9c 100644 --- a/docs/user-guide/analysis-workflow/model.md +++ b/docs/user-guide/analysis-workflow/model.md @@ -104,7 +104,7 @@ project.sample_models['nacl'].atom_sites.append( fract_y=0, fract_z=0, occupancy=1, - b_iso_or_equiv=0.5 + b_iso_or_equiv=0.5, ) project.sample_models['nacl'].atom_sites.append( label='Cl', @@ -113,7 +113,7 @@ project.sample_models['nacl'].atom_sites.append( fract_y=0, fract_z=0.5, occupancy=1, - b_iso_or_equiv=0.5 + b_iso_or_equiv=0.5, ) ``` diff --git a/docs/user-guide/analysis-workflow/project.md b/docs/user-guide/analysis-workflow/project.md index 6987f10d..542e9dad 100644 --- a/docs/user-guide/analysis-workflow/project.md +++ b/docs/user-guide/analysis-workflow/project.md @@ -30,10 +30,10 @@ project = ed.Project(name='lbco_hrpt') # Define project info project.info.title = 'La0.5Ba0.5CoO3 from neutron diffraction at HRPT@PSI' -project.info.description = '''This project demonstrates a standard refinement +project.info.description = """This project demonstrates a standard refinement of La0.5Ba0.5CoO3, which crystallizes in a perovskite-type structure, using neutron powder diffraction data collected in constant wavelength mode at the -HRPT diffractometer (PSI).''' +HRPT diffractometer (PSI).""" ``` ## Saving a Project diff --git a/docs/user-guide/first-steps.md b/docs/user-guide/first-steps.md index 0500b189..d5a832d6 100644 --- a/docs/user-guide/first-steps.md +++ b/docs/user-guide/first-steps.md @@ -41,12 +41,10 @@ example, you can import the `Project`, `SampleModel`, `Experiment` classes and `download_from_repository` method like this: ```python -from easydiffraction import ( - Project, - SampleModel, - Experiment, - download_from_repository -) +from easydiffraction import Project +from easydiffraction import SampleModel +from easydiffraction import Experiment +from easydiffraction import download_from_repository ``` This enables you to use these classes and methods directly without the package @@ -73,9 +71,11 @@ For example, you can download a sample data file like this: ```python import easydiffraction as ed -ed.download_from_repository('hrpt_lbco.xye', - branch='docs', - destination='data') +ed.download_from_repository( + 'hrpt_lbco.xye', + branch='docs', + destination='data', +) ``` This command will download the `hrpt_lbco.xye` file from the `docs` branch of diff --git a/pixi.lock b/pixi.lock index c8913145..106286d9 100644 --- a/pixi.lock +++ b/pixi.lock @@ -5,142 +5,154 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple + options: + pypi-prerelease-mode: if-necessary-or-explicit packages: 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/linux-64/bzip2-1.0.8-hda65f42_8.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gsl-2.8-hbf7d49c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-default_hbd61a6d_105.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250512.1-cxx17_hba17884_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_16.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/libexpat-2.7.4-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.1-h0c1763c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.2.1-he2c55a7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.11-hc97d973_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.7.0-he4ff34a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.12-hc97d973_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-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/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-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/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/12/da/91a52516e9d5aea87d32d1523f9cdcf7a35a3b298e6be05d6509ba3cfab2/coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/af/3b86dbd18d8dab5646f5b7c7db5bd9c43108e093864032aabd35d41b127d/diffpy_pdffit2-1.5.2.tar.gz - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/ee/c9ca3f81d69ec6c99ca41e10432a58c60fef3a6e63cce6df7e796f90fe22/easydiffraction-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.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/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/bc/60d93477b653eeb1ddf5f9ec34be689b79234d82dbdded269ac0252715b8/fonttools-4.62.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/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/ed/34/a6536afaeee07fa351e2087bf7b5b1522aa703bc1f6e29d53c27a722ac33/gemmi-0.7.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/8c/db8e79c4c744ebae1dcf25f7dbcc5d7df912cdbcdf7221e761479e8bd04b/gemmi-0.7.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/86/92/162cfaee4ccf370465c5af1ce36a9eacec1becb552f2033bb3584e6f640a/ipython-9.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c0/8cc378aad5ccfcc37adeda27d3864f20ea808ad5b63572f5b986a42a649c/jupyter_dark_detect-0.1.0-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/af/c3/acced767eecc11a70c65c45295db5396c4f0c1937874937d5a76d7b177b6/jupyterlab-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/0d/2d240e7098e0cafba4d25e9530e7596b1bb1bd4476e41b10346bcaaa36d6/jupytext-1.18.1-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/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.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/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/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/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl @@ -148,75 +160,85 @@ environments: - 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/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-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/28/83/36557b04cfdc317ed8a525c4993b23e43a8fbcddaddd78619112ca07138c/msgspec-0.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/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/3d/2e/cf2ffeb386ac3763526151163ad7da9f1b586aac96d2b4f7de1eaebf0c61/narwhals-2.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-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/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - 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/12/e5/838eb1004fb9b6f0383c47c0d902eb698fda052e8e64ca48c70a2144a48c/nbstripout-0.8.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/39/dfdef4cfbfcf7c80cd144b1b2e262c2e785f18b152eeebe14ecd70ee7455/ncrystal-4.2.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e3/8b/1f02771d91ceafec996cef7f92f6a24010fedc47fd9404f8e11772d8501c/ncrystal_core-4.2.12-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/78/98/20fdd6825d1876fc54bb098509e736073d20f569b033f2f23bf74aa1df9c/ncrystal_python-4.2.12-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/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-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/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-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/05/bb/39e6768529454cc2b57e1e2fa0a0a18ff64397a16303270e215a3e03285f/pathspec-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-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/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.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/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_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/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-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/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/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/50/74/c655a6eda0fd188d490c14142a0f0380655ac7099604e1fbf8fa1a97f0a1/python_engineio-4.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/d2/2ccc2b69a187b80fda3152745670cfba936704f296a9fa54c6c8ac694d12/python_socketio-5.16.0-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/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -227,181 +249,203 @@ environments: - 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/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1b/5a/f2f2e5eda25579f754acd83399c522ee03d6acbe001dfe53c8a1ec928b44/send2trash-2.0.0-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/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/98/aa2d4b9d28969cc7f62409f9f9fc5b5a853af651255eba03e9bee8546dd9/scipp-25.12.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-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/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0e/50/80a8d080ac7d3d321e5e5d420c9a522b0aa770ec7013ea91f9a8b7d36e4a/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/53/01/1c0485ae02e645bc517bf5d5a6ca674f62c97e247890b954cbfe85c64dae/spglib-2.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - 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/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-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/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/15/63fb7a6908db2f03716c4a50aea7e27a7440fe6a09854282c401139afaf7/uv-0.9.22-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/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/34/b104c413079874493eed7bf11838b47b697cf1f0ed7e9de374ea37b4e4e0/uv-0.10.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-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/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: ./ osx-64: - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.6-hb5e19a0_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/gsl-2.8-hc707ee6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20250512.1-cxx17_hfc00f1c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.2-h14c5de8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20260107.1-cxx17_h7ed6875_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.8-h3d58e20_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.3-heffb93a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-h750e83c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_15.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_15.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_15.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/libexpat-2.7.4-h991f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.2-h11316ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-hf3981d6_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.51.1-hb99441e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.52.0-h77d7759_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-21.1.8-h472b3d1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.2.1-h5523da6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.0-h230baf5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.11-h17c18a5_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.7.0-hf6efa0e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.12-h894a449_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-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/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-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/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/eb/bb3ff420acdaf9bcaf94c510f42df11974bc3fc475ef50d619366f33fda3/diffpy_pdffit2-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/ee/c9ca3f81d69ec6c99ca41e10432a58c60fef3a6e63cce6df7e796f90fe22/easydiffraction-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/dc/c409c8ceec0d3119e9ab0b7b1a2e3c76d1f4d66e4a9db5c59e6b7652e7df/fonttools-4.62.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/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/3e/e7/b88b72919c910d3233065680cbc74a2a9d00ed65a06b100751d5b78e08e1/gemmi-0.7.4-cp313-cp313-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c4/80/fd758344a72ca7b5e1c5bbdc1d263f3b215d3897941b5f450380445ca0a9/gemmi-0.7.5-cp313-cp313-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl - - pypi: https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/86/92/162cfaee4ccf370465c5af1ce36a9eacec1becb552f2033bb3584e6f640a/ipython-9.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c0/8cc378aad5ccfcc37adeda27d3864f20ea808ad5b63572f5b986a42a649c/jupyter_dark_detect-0.1.0-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/af/c3/acced767eecc11a70c65c45295db5396c4f0c1937874937d5a76d7b177b6/jupyterlab-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/0d/2d240e7098e0cafba4d25e9530e7596b1bb1bd4476e41b10346bcaaa36d6/jupytext-1.18.1-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/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.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/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl @@ -409,75 +453,85 @@ environments: - 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/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-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/8a/d1/b902d38b6e5ba3bdddbec469bba388d647f960aeed7b5b3623a8debe8a76/msgspec-0.20.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/3d/2e/cf2ffeb386ac3763526151163ad7da9f1b586aac96d2b4f7de1eaebf0c61/narwhals-2.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-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/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - 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/12/e5/838eb1004fb9b6f0383c47c0d902eb698fda052e8e64ca48c70a2144a48c/nbstripout-0.8.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/39/dfdef4cfbfcf7c80cd144b1b2e262c2e785f18b152eeebe14ecd70ee7455/ncrystal-4.2.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/ee/0d9d9218d2081e56828194f83d0eac6292b7182708fd07a62756c66f7194/ncrystal_core-4.2.12-py3-none-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/78/98/20fdd6825d1876fc54bb098509e736073d20f569b033f2f23bf74aa1df9c/ncrystal_python-4.2.12-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/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-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/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/0b/48/aad6ec4f8d007534c091e9a7172b3ec1b1ee6d99a9cbb936b5eab6c6cf58/pandas-3.0.1-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/05/bb/39e6768529454cc2b57e1e2fa0a0a18ff64397a16303270e215a3e03285f/pathspec-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-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/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.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/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-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/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-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/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/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/50/74/c655a6eda0fd188d490c14142a0f0380655ac7099604e1fbf8fa1a97f0a1/python_engineio-4.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/d2/2ccc2b69a187b80fda3152745670cfba936704f296a9fa54c6c8ac694d12/python_socketio-5.16.0-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/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -488,180 +542,202 @@ environments: - 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/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1b/5a/f2f2e5eda25579f754acd83399c522ee03d6acbe001dfe53c8a1ec928b44/send2trash-2.0.0-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/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/22/75e119e0a200914f88f121cd956e1eb7f72c8ace4b63171f52ba0334d142/scipp-25.12.0-cp313-cp313-macosx_11_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-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/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1b/a5/174d33068d4383df4be9ee1ea9251f17820a622f3be744ca2ab7334818ee/spglib-2.6.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - 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/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-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/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5e/68/bb76c97c284ce7fb8efa868994c2510588faa7075e60d8865d1373e54b7b/uv-0.9.22-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/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/34/2e5cd576d312eb1131b615f49ee95ff6efb740965324843617adae729cf2/uv-0.10.9-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-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/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/b6/8925d68af039b835ae876db5838e82e76ec87b9782ecc97e192b809c4831/yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: ./ osx-arm64: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gsl-2.8-h8d0574d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20250512.1-cxx17_hd41c47c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.8-hf598326_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.3-haf25636_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_16.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_16.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_16.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/libexpat-2.7.4-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.51.1-h1b79a29_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-21.1.8-h4a912ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.2.1-h5230ea7_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.0-h5503f6c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.11-hfc2f54d_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.7.0-hbfc8e16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.12-h20e6be0_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-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/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-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/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/28/d050c2716c74c6fce9ace360e727e6f86b68212fb6b0ea57c005ebe574ea/diffpy_pdffit2-1.5.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/ee/c9ca3f81d69ec6c99ca41e10432a58c60fef3a6e63cce6df7e796f90fe22/easydiffraction-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/c7/985c1670aa6d82ef270f04cde11394c168f2002700353bd2bde405e59b8f/fonttools-4.62.0-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/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/19/5b/0976c1af0dd59a6850e9ea3b6c6d28f3dff0651c694a6a6192a2933e8feb/gemmi-0.7.4-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/9c/1236dd7d22ed48527286b613c84e3376ea731b65e6734b6e6a0b4d03744c/gemmi-0.7.5-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/86/92/162cfaee4ccf370465c5af1ce36a9eacec1becb552f2033bb3584e6f640a/ipython-9.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c0/8cc378aad5ccfcc37adeda27d3864f20ea808ad5b63572f5b986a42a649c/jupyter_dark_detect-0.1.0-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/af/c3/acced767eecc11a70c65c45295db5396c4f0c1937874937d5a76d7b177b6/jupyterlab-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/0d/2d240e7098e0cafba4d25e9530e7596b1bb1bd4476e41b10346bcaaa36d6/jupytext-1.18.1-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/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.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/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl @@ -669,75 +745,85 @@ environments: - 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/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-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/57/b6/eff0305961a1d9447ec2b02f8c73c8946f22564d302a504185b730c9a761/msgspec-0.20.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/3d/2e/cf2ffeb386ac3763526151163ad7da9f1b586aac96d2b4f7de1eaebf0c61/narwhals-2.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-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/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - 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/12/e5/838eb1004fb9b6f0383c47c0d902eb698fda052e8e64ca48c70a2144a48c/nbstripout-0.8.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/39/dfdef4cfbfcf7c80cd144b1b2e262c2e785f18b152eeebe14ecd70ee7455/ncrystal-4.2.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/8d/2b26572e909238bb114d50fb0d1b6b54eb6dafa2d83a7264f18146796b0d/ncrystal_core-4.2.12-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/78/98/20fdd6825d1876fc54bb098509e736073d20f569b033f2f23bf74aa1df9c/ncrystal_python-4.2.12-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/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-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/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a8/14/5990826f779f79148ae9d3a2c39593dc04d61d5d90541e71b5749f35af95/pandas-3.0.1-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/05/bb/39e6768529454cc2b57e1e2fa0a0a18ff64397a16303270e215a3e03285f/pathspec-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-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/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.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/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-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/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-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/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/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/50/74/c655a6eda0fd188d490c14142a0f0380655ac7099604e1fbf8fa1a97f0a1/python_engineio-4.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/d2/2ccc2b69a187b80fda3152745670cfba936704f296a9fa54c6c8ac694d12/python_socketio-5.16.0-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/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -748,76 +834,85 @@ environments: - 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/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/1b/5a/f2f2e5eda25579f754acd83399c522ee03d6acbe001dfe53c8a1ec928b44/send2trash-2.0.0-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/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/5c/bc0ca94bff65fe0d206a369b54625f8ec7852dfd1d835174692026f34df9/scipp-25.12.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-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/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/47/634fe8323c6c2bfa86e10eb41ebfe410db5e6231aa1727a31ce4f002480f/spglib-2.6.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - 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/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-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/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/49/7230b1d56aeaee0eefd346a70f582463f11fb7036d2d020bcf68053bd994/uv-0.9.22-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/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/35/684f641de4de2b20db7d2163c735b2bb211e3b3c84c241706d6448e5e868/uv-0.10.9-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-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/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: ./ win-64: - - 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-2026.1.4-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/gsl-2.8-h5b8d9c4_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/icu-78.1-h637d24d_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.3-hac47afa_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h52bdfb6_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.1-default_h4379cf1_1003.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda - 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/libsqlite-3.51.1-hf5d6505_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_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.1-h3cfd58e_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.1-h779ef1b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.8-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.2.1-he453025_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.0-h725018a_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.11-h09917c8_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.7.0-h80d1838_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.12-h09917c8_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-hd094cb3_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-h3155e25_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.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/win-64/vc-14.3-h41ae7f8_34.conda @@ -826,96 +921,108 @@ environments: - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-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/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-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/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/45/0b/0768b4231d5a044da8f75e097a8714ae1041246bb765d6b5563bab456735/coverage-7.13.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/4e/931480b9552c7d0feebe40c73725dd7703dcc578ba9efc14fe0e6d31cfd1/debugpy-1.8.19-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1f/0c/6826cb2151628c59cca66ca6089ff910ab3ccd62b0524c2b398dc145ee52/diffpy_pdffit2-1.5.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/ee/c9ca3f81d69ec6c99ca41e10432a58c60fef3a6e63cce6df7e796f90fe22/easydiffraction-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/7a/e25245a30457595740041dba9d0ea8ec1b2517f2f1a6a741f15eba1a4edc/fonttools-4.62.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/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/19/f7/1a03aa6b06d26dbd1231b3ac907f8fdfcf02c0b27fc5f4c31493ad6ecdad/gemmi-0.7.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/ab/7d7463cda94f8b68b969ea97aaad679655a0e436efd6a643e528a8de114e/gemmi-0.7.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/86/92/162cfaee4ccf370465c5af1ce36a9eacec1becb552f2033bb3584e6f640a/ipython-9.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c0/8cc378aad5ccfcc37adeda27d3864f20ea808ad5b63572f5b986a42a649c/jupyter_dark_detect-0.1.0-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/af/c3/acced767eecc11a70c65c45295db5396c4f0c1937874937d5a76d7b177b6/jupyterlab-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/0d/2d240e7098e0cafba4d25e9530e7596b1bb1bd4476e41b10346bcaaa36d6/jupytext-1.18.1-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/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.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/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl @@ -923,74 +1030,84 @@ environments: - 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/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-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/f1/25/5e8080fe0117f799b1b68008dc29a65862077296b92550632de015128579/msgspec-0.20.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/3d/2e/cf2ffeb386ac3763526151163ad7da9f1b586aac96d2b4f7de1eaebf0c61/narwhals-2.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-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/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - 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/12/e5/838eb1004fb9b6f0383c47c0d902eb698fda052e8e64ca48c70a2144a48c/nbstripout-0.8.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/39/dfdef4cfbfcf7c80cd144b1b2e262c2e785f18b152eeebe14ecd70ee7455/ncrystal-4.2.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/51/e13a37a8d924feefb444d7eb42094750ba1bba756cbb8c1f9a523414c4fb/ncrystal_core-4.2.12-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/78/98/20fdd6825d1876fc54bb098509e736073d20f569b033f2f23bf74aa1df9c/ncrystal_python-4.2.12-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/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-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/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/d6/7d/216a1588b65a7aa5f4535570418a599d943c85afb1d95b0876fc00aa1468/pandas-3.0.1-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/05/bb/39e6768529454cc2b57e1e2fa0a0a18ff64397a16303270e215a3e03285f/pathspec-1.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-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/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.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/34/68/d9317542e3f2b180c4306e3f45d3c922d7e86d8ce39f941bb9e2e9d8599e/psutil-7.2.1-cp37-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-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/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-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/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/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/50/74/c655a6eda0fd188d490c14142a0f0380655ac7099604e1fbf8fa1a97f0a1/python_engineio-4.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/d2/2ccc2b69a187b80fda3152745670cfba936704f296a9fa54c6c8ac694d12/python_socketio-5.16.0-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/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl @@ -1001,191 +1118,214 @@ environments: - 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/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/1b/5a/f2f2e5eda25579f754acd83399c522ee03d6acbe001dfe53c8a1ec928b44/send2trash-2.0.0-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/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/d1/b3cd2733a96a36c54c36889b2cfdd0331c1e5b57fa1757485a22d0ec3142/scipp-25.12.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-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/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c0/c5/d17113020b2d43073412aeca09b60d2009442420372123b8d49cc253f8b8/sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/56/a31e8d3c9e8d21100b83bbe1c1f3f7c94db317393a229e193461e5e6d2a4/spglib-2.6.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - 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/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-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/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/32/49/9e3e19ba756c4a5e6acb4ea74336d3035f7959254fbb05f5eb77bff067ed/uv-0.9.22-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/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/e9/adf7a12136573937d12ac189569e2e90e7fad18b458192083df6986f3013/uv-0.10.9-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-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/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl + - pypi: ./ py311-dev: channels: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple + options: + pypi-prerelease-mode: if-necessary-or-explicit packages: 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/linux-64/bzip2-1.0.8-hda65f42_8.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gsl-2.8-hbf7d49c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-default_hbd61a6d_105.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250512.1-cxx17_hba17884_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_16.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/libexpat-2.7.4-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.1-h0c1763c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.2.1-he2c55a7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.14-hd63d673_2_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.7.0-he4ff34a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/7e/6815aab7d3a56610891c76ef79095677b8b5be6646aaf00f69b221765021/aiohttp-3.13.3-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/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-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/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/03/f4/44d3b830a20e89ff82a3134912d9a1cf6084d64f3b95dcad40f74449a654/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/af/3b86dbd18d8dab5646f5b7c7db5bd9c43108e093864032aabd35d41b127d/diffpy_pdffit2-1.5.2.tar.gz - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/ee/c9ca3f81d69ec6c99ca41e10432a58c60fef3a6e63cce6df7e796f90fe22/easydiffraction-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/d7/8e4845993ee233c2023d11babe9b3dae7d30333da1d792eeccebcb77baab/fonttools-4.62.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/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/96/ae/41aff180c36dd3c8f0a84faf38ac8683f8dca99250abcfbc3ed19897290b/gemmi-0.7.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/eb/46e443fc70b4aabe6e775521ff476aefb051db9acabb16a5cb51f04e3e2b/gemmi-0.7.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/a6/e959a127b630a58e23529972dbc868c107f9d583b5a9f878fb858c46bc1a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/86/92/162cfaee4ccf370465c5af1ce36a9eacec1becb552f2033bb3584e6f640a/ipython-9.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c0/8cc378aad5ccfcc37adeda27d3864f20ea808ad5b63572f5b986a42a649c/jupyter_dark_detect-0.1.0-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/af/c3/acced767eecc11a70c65c45295db5396c4f0c1937874937d5a76d7b177b6/jupyterlab-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/0d/2d240e7098e0cafba4d25e9530e7596b1bb1bd4476e41b10346bcaaa36d6/jupytext-1.18.1-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/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.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/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/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/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl @@ -1193,76 +1333,86 @@ environments: - 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/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-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/6b/96/5c095b940de3aa6b43a71ec76275ac3537b21bd45c7499b5a17a429110fa/msgspec-0.20.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/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/3d/2e/cf2ffeb386ac3763526151163ad7da9f1b586aac96d2b4f7de1eaebf0c61/narwhals-2.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-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/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - 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/12/e5/838eb1004fb9b6f0383c47c0d902eb698fda052e8e64ca48c70a2144a48c/nbstripout-0.8.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/39/dfdef4cfbfcf7c80cd144b1b2e262c2e785f18b152eeebe14ecd70ee7455/ncrystal-4.2.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e3/8b/1f02771d91ceafec996cef7f92f6a24010fedc47fd9404f8e11772d8501c/ncrystal_core-4.2.12-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/78/98/20fdd6825d1876fc54bb098509e736073d20f569b033f2f23bf74aa1df9c/ncrystal_python-4.2.12-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/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/78/51/9f5d7a41f0b51649ddf2f2320595e15e122a40610b233d51928dd6c92353/numpy-2.4.3-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/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/2e/7c/870c7e7daec2a6c7ff2ac9e33b23317230d4e4e954b35112759ea4a924a7/pandas-3.0.1-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/05/bb/39e6768529454cc2b57e1e2fa0a0a18ff64397a16303270e215a3e03285f/pathspec-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-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/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.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/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_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/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-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/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/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/50/74/c655a6eda0fd188d490c14142a0f0380655ac7099604e1fbf8fa1a97f0a1/python_engineio-4.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/d2/2ccc2b69a187b80fda3152745670cfba936704f296a9fa54c6c8ac694d12/python_socketio-5.16.0-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/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -1273,179 +1423,203 @@ environments: - 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/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/ca/6e/8942461cf2636cdae083e3eb72622a7fbbfa5cf559c7d13ab250a5dbdc01/scipy-1.16.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1b/5a/f2f2e5eda25579f754acd83399c522ee03d6acbe001dfe53c8a1ec928b44/send2trash-2.0.0-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/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4e/b6/ffe0bb67cec66cd450acff599bb07507bbf5ffda1a3a15dd2d8dbe7a6da7/scipp-25.12.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-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/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/2b/60ce3ee7a5ae172bfcd419ce23259bb874d2cddd44f67c5df3760a1e22f9/sqlalchemy-2.0.45-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/41/591cd1e94254c20f00bb1f32c0b1a6de68c03d54e6daf78dd7b146d0b3fc/spglib-2.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/21/dd/3b7c53f1dbbf736fd27041aee68f8ac52226b610f914085b1652c2323442/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - 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/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-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/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/15/63fb7a6908db2f03716c4a50aea7e27a7440fe6a09854282c401139afaf7/uv-0.9.22-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/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/34/b104c413079874493eed7bf11838b47b697cf1f0ed7e9de374ea37b4e4e0/uv-0.10.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-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/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/9a/64/c53487d9f4968045b8afa51aed7ca44f58b2589e772f32745f3744476c82/yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl + - pypi: ./ osx-64: - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.6-hb5e19a0_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/gsl-2.8-hc707ee6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20250512.1-cxx17_hfc00f1c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.2-h14c5de8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20260107.1-cxx17_h7ed6875_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.8-h3d58e20_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.3-heffb93a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-h750e83c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_15.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_15.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_15.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/libexpat-2.7.4-h991f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.2-h11316ed_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.51.1-hb99441e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.52.0-h77d7759_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-21.1.8-h472b3d1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.2.1-h5523da6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.0-h230baf5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.14-h74c2667_2_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.7.0-hf6efa0e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.15-ha9537fe_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/82/71/d5c31390d18d4f58115037c432b7e0348c60f6f53b727cad33172144a112/aiohttp-3.13.3-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/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-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/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/9e/0e27056c6165ab3e2536d5efbc358cf495e6cd57fbaf51e68b1113fa7771/diffpy_pdffit2-1.5.2-cp311-cp311-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/ee/c9ca3f81d69ec6c99ca41e10432a58c60fef3a6e63cce6df7e796f90fe22/easydiffraction-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/7a/9aeec114bc9fc00d757a41f092f7107863d372e684a5b5724c043654477c/fonttools-4.62.0-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/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/28/bc/e943898c25121f36625ab4913b8e24d0bdd054a17e380d19924066102574/gemmi-0.7.4-cp311-cp311-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/79/b13830a65bf9fc85474a984604f094cc18817dc93a784f4c567a2dc05169/gemmi-0.7.5-cp311-cp311-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/cb/48e964c452ca2b92175a9b2dca037a553036cb053ba69e284650ce755f13/greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl - - pypi: https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/41/fd/8349b48b15b47768042cff06ad6e1c229f0a4bd89225bf6b6894fea27e6d/h5py-3.15.1-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ba/95/a825894f3e45cbac7554c4e97314ce886b233a20033787eda755ca8fecc7/h5py-3.16.0-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/86/92/162cfaee4ccf370465c5af1ce36a9eacec1becb552f2033bb3584e6f640a/ipython-9.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c0/8cc378aad5ccfcc37adeda27d3864f20ea808ad5b63572f5b986a42a649c/jupyter_dark_detect-0.1.0-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/af/c3/acced767eecc11a70c65c45295db5396c4f0c1937874937d5a76d7b177b6/jupyterlab-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/0d/2d240e7098e0cafba4d25e9530e7596b1bb1bd4476e41b10346bcaaa36d6/jupytext-1.18.1-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/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.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/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl @@ -1453,76 +1627,86 @@ environments: - 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/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-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/03/59/fdcb3af72f750a8de2bcf39d62ada70b5eb17b06d7f63860e0a679cb656b/msgspec-0.20.0-cp311-cp311-macosx_10_9_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/3d/2e/cf2ffeb386ac3763526151163ad7da9f1b586aac96d2b4f7de1eaebf0c61/narwhals-2.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-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/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - 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/12/e5/838eb1004fb9b6f0383c47c0d902eb698fda052e8e64ca48c70a2144a48c/nbstripout-0.8.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/39/dfdef4cfbfcf7c80cd144b1b2e262c2e785f18b152eeebe14ecd70ee7455/ncrystal-4.2.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/ee/0d9d9218d2081e56828194f83d0eac6292b7182708fd07a62756c66f7194/ncrystal_core-4.2.12-py3-none-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/78/98/20fdd6825d1876fc54bb098509e736073d20f569b033f2f23bf74aa1df9c/ncrystal_python-4.2.12-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/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2b/09/3c4abbc1dcd8010bf1a611d174c7aa689fc505585ec806111b4406f6f1b1/numpy-2.4.3-cp311-cp311-macosx_14_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - 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/ff/07/c7087e003ceee9b9a82539b40414ec557aa795b584a1a346e89180853d79/pandas-3.0.1-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/05/bb/39e6768529454cc2b57e1e2fa0a0a18ff64397a16303270e215a3e03285f/pathspec-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-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/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.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/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-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/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-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/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/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/50/74/c655a6eda0fd188d490c14142a0f0380655ac7099604e1fbf8fa1a97f0a1/python_engineio-4.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/d2/2ccc2b69a187b80fda3152745670cfba936704f296a9fa54c6c8ac694d12/python_socketio-5.16.0-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/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -1533,178 +1717,202 @@ environments: - 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/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/9b/5f/6f37d7439de1455ce9c5a556b8d1db0979f03a796c030bafdf08d35b7bf9/scipy-1.16.3-cp311-cp311-macosx_10_14_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1b/5a/f2f2e5eda25579f754acd83399c522ee03d6acbe001dfe53c8a1ec928b44/send2trash-2.0.0-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/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/0d/3f98a936a30bff4a460b51b9f85c4d994f94249930b2d8bedeb8111a359e/scipp-25.12.0-cp311-cp311-macosx_11_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-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/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/44/30888e2a5b2fa2e6df18606b442cb8b126b0bea5a2f1ec4a2a82538ffecf/spglib-2.6.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - 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/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-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/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5e/68/bb76c97c284ce7fb8efa868994c2510588faa7075e60d8865d1373e54b7b/uv-0.9.22-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/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/34/2e5cd576d312eb1131b615f49ee95ff6efb740965324843617adae729cf2/uv-0.10.9-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-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/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/24/84/e237607faf4e099dbb8a4f511cfd5efcb5f75918baad200ff7380635631b/yarl-1.23.0-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl + - pypi: ./ osx-arm64: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gsl-2.8-h8d0574d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20250512.1-cxx17_hd41c47c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.8-hf598326_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.3-haf25636_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_16.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_16.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_16.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/libexpat-2.7.4-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.51.1-h1b79a29_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-21.1.8-h4a912ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.2.1-h5230ea7_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.0-h5503f6c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.14-h18782d2_2_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.7.0-hbfc8e16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.15-h8561d8f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/c9/741f8ac91e14b1d2e7100690425a5b2b919a87a5075406582991fb7de920/aiohttp-3.13.3-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/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-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/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ce/c9/7b61255980383781774d9857aa9e97fe7e9b8b08f97c0974afeef3083dd9/diffpy_pdffit2-1.5.2-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/ee/c9ca3f81d69ec6c99ca41e10432a58c60fef3a6e63cce6df7e796f90fe22/easydiffraction-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/33/63d79ca41020dd460b51f1e0f58ad1ff0a36b7bcbdf8f3971d52836581e9/fonttools-4.62.0-cp311-cp311-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/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/46/3e/51e7914c8a640548d1b980140b1bd1419c169bee300a556cfd7f4175444d/gemmi-0.7.4-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/15/26cac702cdf6281ddeb185d5912ce14e555e277c6e4caeb1d36966e43822/gemmi-0.7.5-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c1/b0/1c628e26a0b95858f54aba17e1599e7f6cd241727596cc2580b72cb0a9bf/h5py-3.15.1-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/86/92/162cfaee4ccf370465c5af1ce36a9eacec1becb552f2033bb3584e6f640a/ipython-9.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c0/8cc378aad5ccfcc37adeda27d3864f20ea808ad5b63572f5b986a42a649c/jupyter_dark_detect-0.1.0-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/af/c3/acced767eecc11a70c65c45295db5396c4f0c1937874937d5a76d7b177b6/jupyterlab-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/0d/2d240e7098e0cafba4d25e9530e7596b1bb1bd4476e41b10346bcaaa36d6/jupytext-1.18.1-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/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.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/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl @@ -1712,76 +1920,86 @@ environments: - 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/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-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/5a/15/3c225610da9f02505d37d69a77f4a2e7daae2a125f99d638df211ba84e59/msgspec-0.20.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/3d/2e/cf2ffeb386ac3763526151163ad7da9f1b586aac96d2b4f7de1eaebf0c61/narwhals-2.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-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/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - 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/12/e5/838eb1004fb9b6f0383c47c0d902eb698fda052e8e64ca48c70a2144a48c/nbstripout-0.8.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/39/dfdef4cfbfcf7c80cd144b1b2e262c2e785f18b152eeebe14ecd70ee7455/ncrystal-4.2.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/8d/2b26572e909238bb114d50fb0d1b6b54eb6dafa2d83a7264f18146796b0d/ncrystal_core-4.2.12-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/78/98/20fdd6825d1876fc54bb098509e736073d20f569b033f2f23bf74aa1df9c/ncrystal_python-4.2.12-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/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ef/27/d26c85cbcd86b26e4f125b0668e7a7c0542d19dd7d23ee12e87b550e95b5/numpy-2.4.3-cp311-cp311-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - 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/c1/27/90683c7122febeefe84a56f2cde86a9f05f68d53885cebcc473298dfc33e/pandas-3.0.1-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/05/bb/39e6768529454cc2b57e1e2fa0a0a18ff64397a16303270e215a3e03285f/pathspec-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-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/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.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/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-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/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-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/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/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/50/74/c655a6eda0fd188d490c14142a0f0380655ac7099604e1fbf8fa1a97f0a1/python_engineio-4.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/d2/2ccc2b69a187b80fda3152745670cfba936704f296a9fa54c6c8ac694d12/python_socketio-5.16.0-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/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -1792,74 +2010,84 @@ environments: - 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/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/7c/89/d70e9f628749b7e4db2aa4cd89735502ff3f08f7b9b27d2e799485987cd9/scipy-1.16.3-cp311-cp311-macosx_12_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/1b/5a/f2f2e5eda25579f754acd83399c522ee03d6acbe001dfe53c8a1ec928b44/send2trash-2.0.0-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/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/3a/ab0eb61593569d5a0d080002e4b8c0998cb1116d8710781b7225c304b880/scipp-25.12.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-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/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a2/1c/769552a9d840065137272ebe86ffbb0bc92b0f1e0a68ee5266a225f8cd7b/sqlalchemy-2.0.45-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/ca/270d463f6c34f539bb55acdab14099c092d3be28c8af64d61399aa07610c/spglib-2.6.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/d7/6d/b8b78b5b80f3c3ab3f7fa90faa195ec3401f6d884b60221260fd4d51864c/sqlalchemy-2.0.48-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - 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/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-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/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/49/7230b1d56aeaee0eefd346a70f582463f11fb7036d2d020bcf68053bd994/uv-0.9.22-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/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/35/684f641de4de2b20db7d2163c735b2bb211e3b3c84c241706d6448e5e868/uv-0.10.9-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-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/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b2/0d/71ceabc14c146ba8ee3804ca7b3d42b1664c8440439de5214d366fec7d3a/yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl + - pypi: ./ win-64: - - 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-2026.1.4-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/gsl-2.8-h5b8d9c4_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/icu-78.1-h637d24d_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.3-hac47afa_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h52bdfb6_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.1-default_h4379cf1_1003.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda - 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/libsqlite-3.51.1-hf5d6505_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_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.1-h3cfd58e_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.1-h779ef1b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.8-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.2.1-he453025_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.0-h725018a_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.14-h0159041_2_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-hd094cb3_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.7.0-h80d1838_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-h3155e25_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.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/win-64/vc-14.3-h41ae7f8_34.conda @@ -1868,96 +2096,109 @@ environments: - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/f2/27cdf04c9851712d6c1b99df6821a6623c3c9e55956d4b1e318c337b5a48/aiohttp-3.13.3-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/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-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/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fe/1f/a853b73d386521fd44b7f67ded6b17b7b2367067d9106a5c4b44f9a34274/charset_normalizer-3.4.5-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f2/a8/aaac7ff12ddf5d68a39e13a423a8490426f5f661384f5ad8d9062761bd8e/debugpy-1.8.19-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/33/fae9a52a6cb97efd21176303dfef44e487b56e3473c1329e019d5682d158/diffpy_pdffit2-1.5.2-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/ee/c9ca3f81d69ec6c99ca41e10432a58c60fef3a6e63cce6df7e796f90fe22/easydiffraction-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/ce/f5a4c42c117f8113ce04048053c128d17426751a508f26398110c993a074/fonttools-4.62.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/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/dd/9d/412d75eb7b9c0aa1e939b419a66c7d61471aa387919d4be32893921564af/gemmi-0.7.4-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/5e/62402bf021183bc6122cb01b8f1be17cac67545713fb30f888f59357a782/gemmi-0.7.5-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1d/d5/c339b3b4bc8198b7caa4f2bd9fd685ac9f29795816d8db112da3d04175bb/greenlet-3.3.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/3a/efb2cf697fbccdf75b24e2c18025e7dfa54c4f31fab75c51d0fe79942cef/greenlet-3.3.2-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/86/92/162cfaee4ccf370465c5af1ce36a9eacec1becb552f2033bb3584e6f640a/ipython-9.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c0/8cc378aad5ccfcc37adeda27d3864f20ea808ad5b63572f5b986a42a649c/jupyter_dark_detect-0.1.0-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/af/c3/acced767eecc11a70c65c45295db5396c4f0c1937874937d5a76d7b177b6/jupyterlab-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/0d/2d240e7098e0cafba4d25e9530e7596b1bb1bd4476e41b10346bcaaa36d6/jupytext-1.18.1-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/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.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/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl @@ -1965,75 +2206,85 @@ environments: - 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/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-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/89/5e/406b7d578926b68790e390d83a1165a9bfc2d95612a1a9c1c4d5c72ea815/msgspec-0.20.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/3d/2e/cf2ffeb386ac3763526151163ad7da9f1b586aac96d2b4f7de1eaebf0c61/narwhals-2.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-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/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - 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/12/e5/838eb1004fb9b6f0383c47c0d902eb698fda052e8e64ca48c70a2144a48c/nbstripout-0.8.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/39/dfdef4cfbfcf7c80cd144b1b2e262c2e785f18b152eeebe14ecd70ee7455/ncrystal-4.2.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/51/e13a37a8d924feefb444d7eb42094750ba1bba756cbb8c1f9a523414c4fb/ncrystal_core-4.2.12-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/78/98/20fdd6825d1876fc54bb098509e736073d20f569b033f2f23bf74aa1df9c/ncrystal_python-4.2.12-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/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/76/1d/edccf27adedb754db7c4511d5eac8b83f004ae948fe2d3509e8b78097d4c/numpy-2.4.3-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/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1f/67/af63f83cd6ca603a00fe8530c10a60f0879265b8be00b5930e8e78c5b30b/pandas-3.0.1-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/05/bb/39e6768529454cc2b57e1e2fa0a0a18ff64397a16303270e215a3e03285f/pathspec-1.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-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/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.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/34/68/d9317542e3f2b180c4306e3f45d3c922d7e86d8ce39f941bb9e2e9d8599e/psutil-7.2.1-cp37-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-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/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-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/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/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/50/74/c655a6eda0fd188d490c14142a0f0380655ac7099604e1fbf8fa1a97f0a1/python_engineio-4.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/d2/2ccc2b69a187b80fda3152745670cfba936704f296a9fa54c6c8ac694d12/python_socketio-5.16.0-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/a6/a1/409c1651c9f874d598c10f51ff586c416625601df4bca315d08baec4c3e3/pywinpty-3.0.2-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/c3/3e75075c7f71735f22b66fab0481f2c98e3a4d58cba55cb50ba29114bcf6/pywinpty-3.0.3-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl @@ -2044,191 +2295,214 @@ environments: - 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/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/f1/d0/22ec7036ba0b0a35bccb7f25ab407382ed34af0b111475eb301c16f8a2e5/scipy-1.16.3-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/1b/5a/f2f2e5eda25579f754acd83399c522ee03d6acbe001dfe53c8a1ec928b44/send2trash-2.0.0-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/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/75/6a3786de6645ac2ccd94fbf83c59cc6b929bfa3a89cb62c8cb3be4de0606/scipp-25.12.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-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/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bc/fa/09d0a11fe9f15c7fa5c7f0dd26be3d235b0c0cbf2f9544f43bc42efc8a24/sqlalchemy-2.0.45-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/a8/d89e1bde525baba10eb8d0be79a5bbaf56c59a47b32bb954866d96a228e3/spglib-2.6.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/58/d5/dd767277f6feef12d05651538f280277e661698f617fa4d086cce6055416/sqlalchemy-2.0.48-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - 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/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-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/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/32/49/9e3e19ba756c4a5e6acb4ea74336d3035f7959254fbb05f5eb77bff067ed/uv-0.9.22-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/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/e9/adf7a12136573937d12ac189569e2e90e7fad18b458192083df6986f3013/uv-0.10.9-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-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/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/93/22/b85eca6fa2ad9491af48c973e4c8cf6b103a73dbb271fe3346949449fca0/yarl-1.23.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl + - pypi: ./ py313-dev: channels: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple + options: + pypi-prerelease-mode: if-necessary-or-explicit packages: 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/linux-64/bzip2-1.0.8-hda65f42_8.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gsl-2.8-hbf7d49c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-default_hbd61a6d_105.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250512.1-cxx17_hba17884_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_16.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/libexpat-2.7.4-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.1-h0c1763c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.2.1-he2c55a7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.11-hc97d973_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.7.0-he4ff34a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.12-hc97d973_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-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/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-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/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/12/da/91a52516e9d5aea87d32d1523f9cdcf7a35a3b298e6be05d6509ba3cfab2/coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/af/3b86dbd18d8dab5646f5b7c7db5bd9c43108e093864032aabd35d41b127d/diffpy_pdffit2-1.5.2.tar.gz - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/ee/c9ca3f81d69ec6c99ca41e10432a58c60fef3a6e63cce6df7e796f90fe22/easydiffraction-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.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/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/bc/60d93477b653eeb1ddf5f9ec34be689b79234d82dbdded269ac0252715b8/fonttools-4.62.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/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/ed/34/a6536afaeee07fa351e2087bf7b5b1522aa703bc1f6e29d53c27a722ac33/gemmi-0.7.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/8c/db8e79c4c744ebae1dcf25f7dbcc5d7df912cdbcdf7221e761479e8bd04b/gemmi-0.7.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/86/92/162cfaee4ccf370465c5af1ce36a9eacec1becb552f2033bb3584e6f640a/ipython-9.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c0/8cc378aad5ccfcc37adeda27d3864f20ea808ad5b63572f5b986a42a649c/jupyter_dark_detect-0.1.0-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/af/c3/acced767eecc11a70c65c45295db5396c4f0c1937874937d5a76d7b177b6/jupyterlab-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/0d/2d240e7098e0cafba4d25e9530e7596b1bb1bd4476e41b10346bcaaa36d6/jupytext-1.18.1-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/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.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/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/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/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl @@ -2236,75 +2510,85 @@ environments: - 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/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-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/28/83/36557b04cfdc317ed8a525c4993b23e43a8fbcddaddd78619112ca07138c/msgspec-0.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/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/3d/2e/cf2ffeb386ac3763526151163ad7da9f1b586aac96d2b4f7de1eaebf0c61/narwhals-2.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-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/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - 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/12/e5/838eb1004fb9b6f0383c47c0d902eb698fda052e8e64ca48c70a2144a48c/nbstripout-0.8.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/39/dfdef4cfbfcf7c80cd144b1b2e262c2e785f18b152eeebe14ecd70ee7455/ncrystal-4.2.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e3/8b/1f02771d91ceafec996cef7f92f6a24010fedc47fd9404f8e11772d8501c/ncrystal_core-4.2.12-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/78/98/20fdd6825d1876fc54bb098509e736073d20f569b033f2f23bf74aa1df9c/ncrystal_python-4.2.12-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/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-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/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-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/05/bb/39e6768529454cc2b57e1e2fa0a0a18ff64397a16303270e215a3e03285f/pathspec-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-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/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.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/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_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/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-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/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/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/50/74/c655a6eda0fd188d490c14142a0f0380655ac7099604e1fbf8fa1a97f0a1/python_engineio-4.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/d2/2ccc2b69a187b80fda3152745670cfba936704f296a9fa54c6c8ac694d12/python_socketio-5.16.0-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/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -2315,181 +2599,203 @@ environments: - 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/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1b/5a/f2f2e5eda25579f754acd83399c522ee03d6acbe001dfe53c8a1ec928b44/send2trash-2.0.0-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/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/98/aa2d4b9d28969cc7f62409f9f9fc5b5a853af651255eba03e9bee8546dd9/scipp-25.12.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-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/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0e/50/80a8d080ac7d3d321e5e5d420c9a522b0aa770ec7013ea91f9a8b7d36e4a/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/53/01/1c0485ae02e645bc517bf5d5a6ca674f62c97e247890b954cbfe85c64dae/spglib-2.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - 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/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-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/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/15/63fb7a6908db2f03716c4a50aea7e27a7440fe6a09854282c401139afaf7/uv-0.9.22-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/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/34/b104c413079874493eed7bf11838b47b697cf1f0ed7e9de374ea37b4e4e0/uv-0.10.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-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/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: ./ osx-64: - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.6-hb5e19a0_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/gsl-2.8-hc707ee6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20250512.1-cxx17_hfc00f1c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.2-h14c5de8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20260107.1-cxx17_h7ed6875_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.8-h3d58e20_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.3-heffb93a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-h750e83c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_15.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_15.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_15.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/libexpat-2.7.4-h991f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.2-h11316ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-hf3981d6_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.51.1-hb99441e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.52.0-h77d7759_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-21.1.8-h472b3d1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.2.1-h5523da6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.0-h230baf5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.11-h17c18a5_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.7.0-hf6efa0e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.12-h894a449_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-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/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-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/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/eb/bb3ff420acdaf9bcaf94c510f42df11974bc3fc475ef50d619366f33fda3/diffpy_pdffit2-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/ee/c9ca3f81d69ec6c99ca41e10432a58c60fef3a6e63cce6df7e796f90fe22/easydiffraction-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/dc/c409c8ceec0d3119e9ab0b7b1a2e3c76d1f4d66e4a9db5c59e6b7652e7df/fonttools-4.62.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/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/3e/e7/b88b72919c910d3233065680cbc74a2a9d00ed65a06b100751d5b78e08e1/gemmi-0.7.4-cp313-cp313-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c4/80/fd758344a72ca7b5e1c5bbdc1d263f3b215d3897941b5f450380445ca0a9/gemmi-0.7.5-cp313-cp313-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl - - pypi: https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/86/92/162cfaee4ccf370465c5af1ce36a9eacec1becb552f2033bb3584e6f640a/ipython-9.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c0/8cc378aad5ccfcc37adeda27d3864f20ea808ad5b63572f5b986a42a649c/jupyter_dark_detect-0.1.0-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/af/c3/acced767eecc11a70c65c45295db5396c4f0c1937874937d5a76d7b177b6/jupyterlab-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/0d/2d240e7098e0cafba4d25e9530e7596b1bb1bd4476e41b10346bcaaa36d6/jupytext-1.18.1-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/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.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/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl @@ -2497,75 +2803,85 @@ environments: - 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/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-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/8a/d1/b902d38b6e5ba3bdddbec469bba388d647f960aeed7b5b3623a8debe8a76/msgspec-0.20.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/3d/2e/cf2ffeb386ac3763526151163ad7da9f1b586aac96d2b4f7de1eaebf0c61/narwhals-2.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-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/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - 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/12/e5/838eb1004fb9b6f0383c47c0d902eb698fda052e8e64ca48c70a2144a48c/nbstripout-0.8.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/39/dfdef4cfbfcf7c80cd144b1b2e262c2e785f18b152eeebe14ecd70ee7455/ncrystal-4.2.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/ee/0d9d9218d2081e56828194f83d0eac6292b7182708fd07a62756c66f7194/ncrystal_core-4.2.12-py3-none-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/78/98/20fdd6825d1876fc54bb098509e736073d20f569b033f2f23bf74aa1df9c/ncrystal_python-4.2.12-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/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-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/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/0b/48/aad6ec4f8d007534c091e9a7172b3ec1b1ee6d99a9cbb936b5eab6c6cf58/pandas-3.0.1-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/05/bb/39e6768529454cc2b57e1e2fa0a0a18ff64397a16303270e215a3e03285f/pathspec-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-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/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.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/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-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/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-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/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/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/50/74/c655a6eda0fd188d490c14142a0f0380655ac7099604e1fbf8fa1a97f0a1/python_engineio-4.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/d2/2ccc2b69a187b80fda3152745670cfba936704f296a9fa54c6c8ac694d12/python_socketio-5.16.0-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/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -2576,180 +2892,202 @@ environments: - 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/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1b/5a/f2f2e5eda25579f754acd83399c522ee03d6acbe001dfe53c8a1ec928b44/send2trash-2.0.0-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/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/22/75e119e0a200914f88f121cd956e1eb7f72c8ace4b63171f52ba0334d142/scipp-25.12.0-cp313-cp313-macosx_11_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-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/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1b/a5/174d33068d4383df4be9ee1ea9251f17820a622f3be744ca2ab7334818ee/spglib-2.6.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - 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/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-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/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5e/68/bb76c97c284ce7fb8efa868994c2510588faa7075e60d8865d1373e54b7b/uv-0.9.22-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/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/34/2e5cd576d312eb1131b615f49ee95ff6efb740965324843617adae729cf2/uv-0.10.9-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-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/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/b6/8925d68af039b835ae876db5838e82e76ec87b9782ecc97e192b809c4831/yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: ./ osx-arm64: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gsl-2.8-h8d0574d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20250512.1-cxx17_hd41c47c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.8-hf598326_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.3-haf25636_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_16.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_16.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_16.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/libexpat-2.7.4-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.51.1-h1b79a29_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-21.1.8-h4a912ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.2.1-h5230ea7_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.0-h5503f6c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.11-hfc2f54d_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.7.0-hbfc8e16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.12-h20e6be0_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-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/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-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/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/28/d050c2716c74c6fce9ace360e727e6f86b68212fb6b0ea57c005ebe574ea/diffpy_pdffit2-1.5.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/ee/c9ca3f81d69ec6c99ca41e10432a58c60fef3a6e63cce6df7e796f90fe22/easydiffraction-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/c7/985c1670aa6d82ef270f04cde11394c168f2002700353bd2bde405e59b8f/fonttools-4.62.0-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/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/19/5b/0976c1af0dd59a6850e9ea3b6c6d28f3dff0651c694a6a6192a2933e8feb/gemmi-0.7.4-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/9c/1236dd7d22ed48527286b613c84e3376ea731b65e6734b6e6a0b4d03744c/gemmi-0.7.5-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/86/92/162cfaee4ccf370465c5af1ce36a9eacec1becb552f2033bb3584e6f640a/ipython-9.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c0/8cc378aad5ccfcc37adeda27d3864f20ea808ad5b63572f5b986a42a649c/jupyter_dark_detect-0.1.0-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/af/c3/acced767eecc11a70c65c45295db5396c4f0c1937874937d5a76d7b177b6/jupyterlab-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/0d/2d240e7098e0cafba4d25e9530e7596b1bb1bd4476e41b10346bcaaa36d6/jupytext-1.18.1-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/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.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/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl @@ -2757,75 +3095,85 @@ environments: - 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/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-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/57/b6/eff0305961a1d9447ec2b02f8c73c8946f22564d302a504185b730c9a761/msgspec-0.20.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/3d/2e/cf2ffeb386ac3763526151163ad7da9f1b586aac96d2b4f7de1eaebf0c61/narwhals-2.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-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/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - 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/12/e5/838eb1004fb9b6f0383c47c0d902eb698fda052e8e64ca48c70a2144a48c/nbstripout-0.8.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/39/dfdef4cfbfcf7c80cd144b1b2e262c2e785f18b152eeebe14ecd70ee7455/ncrystal-4.2.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/8d/2b26572e909238bb114d50fb0d1b6b54eb6dafa2d83a7264f18146796b0d/ncrystal_core-4.2.12-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/78/98/20fdd6825d1876fc54bb098509e736073d20f569b033f2f23bf74aa1df9c/ncrystal_python-4.2.12-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/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-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/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a8/14/5990826f779f79148ae9d3a2c39593dc04d61d5d90541e71b5749f35af95/pandas-3.0.1-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/05/bb/39e6768529454cc2b57e1e2fa0a0a18ff64397a16303270e215a3e03285f/pathspec-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-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/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.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/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-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/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-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/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/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/50/74/c655a6eda0fd188d490c14142a0f0380655ac7099604e1fbf8fa1a97f0a1/python_engineio-4.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/d2/2ccc2b69a187b80fda3152745670cfba936704f296a9fa54c6c8ac694d12/python_socketio-5.16.0-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/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -2836,76 +3184,85 @@ environments: - 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/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/1b/5a/f2f2e5eda25579f754acd83399c522ee03d6acbe001dfe53c8a1ec928b44/send2trash-2.0.0-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/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/5c/bc0ca94bff65fe0d206a369b54625f8ec7852dfd1d835174692026f34df9/scipp-25.12.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-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/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/47/634fe8323c6c2bfa86e10eb41ebfe410db5e6231aa1727a31ce4f002480f/spglib-2.6.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - 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/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-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/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/49/7230b1d56aeaee0eefd346a70f582463f11fb7036d2d020bcf68053bd994/uv-0.9.22-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/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/35/684f641de4de2b20db7d2163c735b2bb211e3b3c84c241706d6448e5e868/uv-0.10.9-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-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/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: ./ win-64: - - 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-2026.1.4-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/gsl-2.8-h5b8d9c4_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/icu-78.1-h637d24d_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.3-hac47afa_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h52bdfb6_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.1-default_h4379cf1_1003.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda - 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/libsqlite-3.51.1-hf5d6505_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_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.1-h3cfd58e_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.1-h779ef1b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.8-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.2.1-he453025_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.0-h725018a_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.11-h09917c8_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.7.0-h80d1838_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.12-h09917c8_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-hd094cb3_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-h3155e25_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.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/win-64/vc-14.3-h41ae7f8_34.conda @@ -2914,96 +3271,108 @@ environments: - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-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/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-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/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/45/0b/0768b4231d5a044da8f75e097a8714ae1041246bb765d6b5563bab456735/coverage-7.13.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/4e/931480b9552c7d0feebe40c73725dd7703dcc578ba9efc14fe0e6d31cfd1/debugpy-1.8.19-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1f/0c/6826cb2151628c59cca66ca6089ff910ab3ccd62b0524c2b398dc145ee52/diffpy_pdffit2-1.5.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/ee/c9ca3f81d69ec6c99ca41e10432a58c60fef3a6e63cce6df7e796f90fe22/easydiffraction-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/7a/e25245a30457595740041dba9d0ea8ec1b2517f2f1a6a741f15eba1a4edc/fonttools-4.62.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/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/19/f7/1a03aa6b06d26dbd1231b3ac907f8fdfcf02c0b27fc5f4c31493ad6ecdad/gemmi-0.7.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/ab/7d7463cda94f8b68b969ea97aaad679655a0e436efd6a643e528a8de114e/gemmi-0.7.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/86/92/162cfaee4ccf370465c5af1ce36a9eacec1becb552f2033bb3584e6f640a/ipython-9.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c0/8cc378aad5ccfcc37adeda27d3864f20ea808ad5b63572f5b986a42a649c/jupyter_dark_detect-0.1.0-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/af/c3/acced767eecc11a70c65c45295db5396c4f0c1937874937d5a76d7b177b6/jupyterlab-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/0d/2d240e7098e0cafba4d25e9530e7596b1bb1bd4476e41b10346bcaaa36d6/jupytext-1.18.1-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/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.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/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl @@ -3011,74 +3380,84 @@ environments: - 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/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-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/f1/25/5e8080fe0117f799b1b68008dc29a65862077296b92550632de015128579/msgspec-0.20.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/3d/2e/cf2ffeb386ac3763526151163ad7da9f1b586aac96d2b4f7de1eaebf0c61/narwhals-2.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-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/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - 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/12/e5/838eb1004fb9b6f0383c47c0d902eb698fda052e8e64ca48c70a2144a48c/nbstripout-0.8.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/39/dfdef4cfbfcf7c80cd144b1b2e262c2e785f18b152eeebe14ecd70ee7455/ncrystal-4.2.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/51/e13a37a8d924feefb444d7eb42094750ba1bba756cbb8c1f9a523414c4fb/ncrystal_core-4.2.12-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/78/98/20fdd6825d1876fc54bb098509e736073d20f569b033f2f23bf74aa1df9c/ncrystal_python-4.2.12-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/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-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/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/d6/7d/216a1588b65a7aa5f4535570418a599d943c85afb1d95b0876fc00aa1468/pandas-3.0.1-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/05/bb/39e6768529454cc2b57e1e2fa0a0a18ff64397a16303270e215a3e03285f/pathspec-1.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-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/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.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/34/68/d9317542e3f2b180c4306e3f45d3c922d7e86d8ce39f941bb9e2e9d8599e/psutil-7.2.1-cp37-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-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/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-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/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/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/50/74/c655a6eda0fd188d490c14142a0f0380655ac7099604e1fbf8fa1a97f0a1/python_engineio-4.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/d2/2ccc2b69a187b80fda3152745670cfba936704f296a9fa54c6c8ac694d12/python_socketio-5.16.0-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/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl @@ -3089,72 +3468,75 @@ environments: - 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/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/1b/5a/f2f2e5eda25579f754acd83399c522ee03d6acbe001dfe53c8a1ec928b44/send2trash-2.0.0-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/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/d1/b3cd2733a96a36c54c36889b2cfdd0331c1e5b57fa1757485a22d0ec3142/scipp-25.12.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-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/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c0/c5/d17113020b2d43073412aeca09b60d2009442420372123b8d49cc253f8b8/sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/56/a31e8d3c9e8d21100b83bbe1c1f3f7c94db317393a229e193461e5e6d2a4/spglib-2.6.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - 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/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-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/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/32/49/9e3e19ba756c4a5e6acb4ea74336d3035f7959254fbb05f5eb77bff067ed/uv-0.9.22-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/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/e9/adf7a12136573937d12ac189569e2e90e7fad18b458192083df6986f3013/uv-0.10.9-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-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/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl + - pypi: ./ packages: -- conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 - md5: d7c89558ba9fa0495403155b64376d81 - license: None - purls: [] - size: 2562 - timestamp: 1578324546067 -- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - build_number: 16 - sha256: fbe2c5e56a653bebb982eda4876a9178aedfc2b545f25d0ce9c4c0b508253d22 - md5: 73aaf86a425cc6e73fcf236a5a46396d - depends: - - _libgcc_mutex 0.1 conda_forge +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + build_number: 20 + sha256: 1dd3fffd892081df9726d7eb7e0dea6198962ba775bd88842135a4ddb4deb3c9 + md5: a9f577daf3de00bca7c3c76c0ecbd1de + depends: + - __glibc >=2.17,<3.0.a0 - libgomp >=7.5.0 constrains: - - openmp_impl 9999 + - openmp_impl <0.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 23621 - timestamp: 1650670423406 + size: 28948 + timestamp: 1770939786096 - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda build_number: 7 sha256: 30006902a9274de8abdad5a9f02ef7c8bb3d69a503486af0c1faee30b023e5b7 @@ -3334,6 +3716,18 @@ packages: - frozenlist>=1.1.0 - typing-extensions>=4.2 ; python_full_version < '3.13' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + name: annotated-doc + version: 0.0.4 + sha256: 571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl + name: annotated-types + version: 0.7.0 + sha256: 1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 + requires_dist: + - typing-extensions>=4.0.0 ; python_full_version < '3.9' + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl name: anyio version: 4.12.1 @@ -3417,6 +3811,26 @@ packages: requires_dist: - setuptools - flake8 ; extra == 'qa' +- pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + name: ase + version: 3.27.0 + sha256: 058c48ea504fe7fbbe7c932f778415243ef2df45b1ab869866f24efcc17f0538 + requires_dist: + - numpy>=1.21.6 + - scipy>=1.8.1 + - matplotlib>=3.5.2 + - sphinx ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-gallery ; extra == 'docs' + - pillow ; extra == 'docs' + - pytest>=7.4.0 ; extra == 'test' + - pytest-xdist>=3.2.0 ; extra == 'test' + - spglib>=1.9 ; extra == 'spglib' + - mypy ; extra == 'lint' + - ruff ; extra == 'lint' + - types-docutils ; extra == 'lint' + - types-pymysql ; extra == 'lint' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl name: asteval version: 1.0.8 @@ -3441,13 +3855,13 @@ packages: - 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 +- pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl name: async-lru - version: 2.0.5 - sha256: ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943 + version: 2.2.0 + sha256: e2c1cf731eba202b59c5feedaef14ffd9d02ad0037fcda64938699f2c380eafe requires_dist: - typing-extensions>=4.0.0 ; python_full_version < '3.11' - requires_python: '>=3.9' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl name: attrs version: 25.4.0 @@ -3461,10 +3875,10 @@ packages: - 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 +- pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl name: babel - version: 2.17.0 - sha256: 4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2 + version: 2.18.0 + sha256: e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35 requires_dist: - pytz>=2015.7 ; python_full_version < '3.9' - tzdata ; sys_platform == 'win32' and extra == 'dev' @@ -3476,17 +3890,17 @@ packages: - pytz ; extra == 'dev' - setuptools ; extra == 'dev' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl +- pypi: https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl name: backrefs - version: '6.1' - sha256: e82bba3875ee4430f4de4b6db19429a27275d95a5f3773c57e9e18abc23fd2b7 + version: '6.2' + sha256: 08aa7fae530c6b2361d7bdcbda1a7c454e330cc9dbcd03f5c23205e430e5c3be requires_dist: - regex ; extra == 'extras' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl +- pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl name: backrefs - version: '6.1' - sha256: 4c9d3dc1e2e558965202c012304f33d4e0e477e1c103663fd2c3cc9bb18b0d05 + version: '6.2' + sha256: 12df81596ab511f783b7d87c043ce26bc5b0288cf3bb03610fe76b8189282b2b requires_dist: - regex ; extra == 'extras' requires_python: '>=3.9' @@ -3521,12 +3935,12 @@ packages: version: 1.9.0 sha256: ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl name: build - version: 1.3.0 - sha256: 7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4 + version: 1.4.0 + sha256: 6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596 requires_dist: - - packaging>=19.1 + - packaging>=24.0 - pyproject-hooks - colorama ; os_name == 'nt' - importlib-metadata>=4.6 ; python_full_version < '3.10.2' @@ -3564,40 +3978,40 @@ packages: - sphinx ; extra == 'dev' - versioningit ; extra == 'dev' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda - sha256: c30daba32ddebbb7ded490f0e371eae90f51e72db620554089103b4a6934b0d5 - md5: 51a19bba1b8ebfb60df25cde030b7ebc +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6 + md5: d2ffd7602c02f2b316fd921d39876885 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 license: bzip2-1.0.6 license_family: BSD purls: [] - size: 260341 - timestamp: 1757437258798 -- conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - sha256: 8f50b58efb29c710f3cecf2027a8d7325ba769ab10c746eff75cea3ac050b10c - md5: 97c4b3bd8a90722104798175a1bdddbf + size: 260182 + timestamp: 1771350215188 +- conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda + sha256: 9f242f13537ef1ce195f93f0cc162965d6cc79da578568d6d8e50f70dd025c42 + md5: 4173ac3b19ec0a4f400b4f782910368b depends: - __osx >=10.13 license: bzip2-1.0.6 license_family: BSD purls: [] - size: 132607 - timestamp: 1757437730085 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda - sha256: b456200636bd5fecb2bec63f7e0985ad2097cf1b83d60ce0b6968dffa6d02aa1 - md5: 58fd217444c2a5701a44244faf518206 + size: 133427 + timestamp: 1771350680709 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + sha256: 540fe54be35fac0c17feefbdc3e29725cce05d7367ffedfaaa1bdda234b019df + md5: 620b85a3f45526a8bc4d23fd78fc22f0 depends: - __osx >=11.0 license: bzip2-1.0.6 license_family: BSD purls: [] - size: 125061 - timestamp: 1757437486465 -- conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda - sha256: d882712855624641f48aa9dc3f5feea2ed6b4e6004585d3616386a18186fe692 - md5: 1077e9333c41ff0be8edd1a5ec0ddace + size: 124834 + timestamp: 1771350416561 +- conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + sha256: 76dfb71df5e8d1c4eded2dbb5ba15bb8fb2e2b0fe42d94145d5eed4c75c35902 + md5: 4cb8e6b48f67de0b018719cdf1136306 depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 @@ -3605,8 +4019,8 @@ packages: license: bzip2-1.0.6 license_family: BSD purls: [] - size: 55977 - timestamp: 1757437738856 + size: 56115 + timestamp: 1771350256444 - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda sha256: cc9accf72fa028d31c2a038460787751127317dcfa991f8d1f1babf216bb454e md5: 920bb03579f15389b9e512095ad995b7 @@ -3638,28 +4052,28 @@ packages: purls: [] size: 180327 timestamp: 1765215064054 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-h4c7d964_0.conda - sha256: 4ddcb01be03f85d3db9d881407fb13a673372f1b9fac9c836ea441893390e049 - md5: 84d389c9eee640dda3d26fc5335c67d8 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda + sha256: 37950019c59b99585cee5d30dbc2cc9696ed4e11f5742606a4db1621ed8f94d6 + md5: f001e6e220355b7f87403a4d0e5bf1ca depends: - __win license: ISC purls: [] - size: 147139 - timestamp: 1767500904211 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-hbd8a1cb_0.conda - sha256: b5974ec9b50e3c514a382335efa81ed02b05906849827a34061c496f4defa0b2 - md5: bddacf101bb4dd0e51811cb69c7790e2 + size: 147734 + timestamp: 1772006322223 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + sha256: 67cc7101b36421c5913a1687ef1b99f85b5d6868da3abbf6ec1a4181e79782fc + md5: 4492fd26db29495f0ba23f146cd5638d depends: - __unix license: ISC purls: [] - size: 146519 - timestamp: 1767500828366 -- pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + size: 147413 + timestamp: 1772006283803 +- pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl name: certifi - version: 2026.1.4 - sha256: 9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c + version: 2026.2.25 + sha256: 027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa 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 @@ -3722,35 +4136,35 @@ packages: version: 3.5.0 sha256: a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 requires_python: '>=3.10' -- 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/03/f4/44d3b830a20e89ff82a3134912d9a1cf6084d64f3b95dcad40f74449a654/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: charset-normalizer - version: 3.4.4 - sha256: 5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016 + version: 3.4.5 + sha256: 5bcb3227c3d9aaf73eaaab1db7ccd80a8995c509ee9941e2aae060ca6e4e5d81 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 +- pypi: https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl name: charset-normalizer - version: 3.4.4 - sha256: 840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381 + version: 3.4.5 + sha256: 610f72c0ee565dfb8ae1241b666119582fdbfe7c0975c175be719f940e110694 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 +- pypi: https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: charset-normalizer - version: 3.4.4 - sha256: e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794 + version: 3.4.5 + sha256: 0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819 requires_python: '>=3.7' -- 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/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl name: charset-normalizer - version: 3.4.4 - sha256: b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14 + version: 3.4.5 + sha256: e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7 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 +- pypi: https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl name: charset-normalizer - version: 3.4.4 - sha256: 6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8 + version: 3.4.5 + sha256: ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23 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 +- pypi: https://files.pythonhosted.org/packages/fe/1f/a853b73d386521fd44b7f67ded6b17b7b2367067d9106a5c4b44f9a34274/charset_normalizer-3.4.5-cp311-cp311-win_amd64.whl name: charset-normalizer - version: 3.4.4 - sha256: a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894 + version: 3.4.5 + sha256: f8102ae93c0bc863b1d41ea0f4499c20a83229f52ed870850892df555187154a requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl name: click @@ -3976,59 +4390,59 @@ packages: - pytest-xdist ; extra == 'test-no-images' - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/12/da/91a52516e9d5aea87d32d1523f9cdcf7a35a3b298e6be05d6509ba3cfab2/coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl name: coverage - version: 7.13.1 - sha256: fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992 + version: 7.13.4 + sha256: 2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: coverage - version: 7.13.1 - sha256: a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a + version: 7.13.4 + sha256: 8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl name: coverage - version: 7.13.1 - sha256: 1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b + version: 7.13.4 + sha256: 3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/45/0b/0768b4231d5a044da8f75e097a8714ae1041246bb765d6b5563bab456735/coverage-7.13.1-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl name: coverage - version: 7.13.1 - sha256: 5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500 + version: 7.13.4 + sha256: d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl name: coverage - version: 7.13.1 - sha256: cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78 + version: 7.13.4 + sha256: b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl name: coverage - version: 7.13.1 - sha256: 1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88 + version: 7.13.4 + sha256: e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: coverage - version: 7.13.1 - sha256: 4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3 + version: 7.13.4 + sha256: b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl name: coverage - version: 7.13.1 - sha256: bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee + version: 7.13.4 + sha256: 19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' @@ -4041,6 +4455,13 @@ packages: - scipy - pycifstar - matplotlib +- pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl + name: cyclebane + version: 24.10.0 + sha256: 902dd318667e4a222afc270cc5bc72c67d5d6047d2e0e1c36018885fb80f5e5d + requires_dist: + - networkx + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl name: cycler version: 0.12.1 @@ -4061,20 +4482,52 @@ packages: requires_dist: - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'macos-listener' requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + name: dask + version: 2026.1.2 + sha256: 46a0cf3b8d87f78a3d2e6b145aea4418a6d6d606fe6a16c79bd8ca2bb862bc91 + requires_dist: + - click>=8.1 + - cloudpickle>=3.0.0 + - fsspec>=2021.9.0 + - packaging>=20.0 + - partd>=1.4.0 + - pyyaml>=5.3.1 + - toolz>=0.12.0 + - importlib-metadata>=4.13.0 ; python_full_version < '3.12' + - numpy>=1.24 ; extra == 'array' + - dask[array] ; extra == 'dataframe' + - pandas>=2.0 ; extra == 'dataframe' + - pyarrow>=16.0 ; extra == 'dataframe' + - distributed>=2026.1.2,<2026.1.3 ; extra == 'distributed' + - bokeh>=3.1.0 ; extra == 'diagnostics' + - jinja2>=2.10.3 ; extra == 'diagnostics' + - dask[array,dataframe,diagnostics,distributed] ; extra == 'complete' + - pyarrow>=16.0 ; extra == 'complete' + - lz4>=4.3.2 ; extra == 'complete' + - pandas[test] ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-rerunfailures ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - pre-commit ; extra == 'test' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl name: debugpy - version: 1.8.19 - sha256: 360ffd231a780abbc414ba0f005dad409e71c78637efe8f2bd75837132a41d38 + version: 1.8.20 + sha256: eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/f2/a8/aaac7ff12ddf5d68a39e13a423a8490426f5f661384f5ad8d9062761bd8e/debugpy-1.8.19-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl name: debugpy - version: 1.8.19 - sha256: 14035cbdbb1fe4b642babcdcb5935c2da3b1067ac211c5c5a8fdc0bb31adbcaa + version: 1.8.20 + sha256: 1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/fb/4e/931480b9552c7d0feebe40c73725dd7703dcc578ba9efc14fe0e6d31cfd1/debugpy-1.8.19-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl name: debugpy - version: 1.8.19 - sha256: c30639998a9f9cd9699b4b621942c0179a6527f083c72351f95c6ab1728d5b73 + version: 1.8.20 + sha256: 5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7 requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl name: decorator @@ -4086,10 +4539,10 @@ packages: version: 0.7.1 sha256: a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' -- pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl name: dfo-ls - version: '1.6' - sha256: 416edce5537237fa417bd27aef5ba91201e856f3daae52ffd44cfacb10f5b771 + version: 1.6.2 + sha256: 961d15e7194f3868e9e48da45010cb6d24d85491007fbee4e38a9f3ab8050cef requires_dist: - setuptools - numpy @@ -4157,27 +4610,54 @@ packages: - numpy - pycifrw requires_python: '>=3.11,<3.14' -- pypi: https://files.pythonhosted.org/packages/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl name: diffpy-utils - version: 3.6.1 - sha256: b7917926f35a84db81d2950ce44f243d7d4c7494a96a18dc673ce555cde0d2d2 + version: 3.7.1 + sha256: 8bf33eb3e228bf6a18242e4cc01cf4b7bff307fbef90f745bf7d680c7ed4d3b7 requires_dist: - numpy - xraydb - scipy - requires_python: '>=3.11,<3.14' -- pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl + requires_python: '>=3.11,<3.15' +- pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl name: dill - version: 0.4.0 - sha256: 44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049 + version: 0.4.1 + sha256: 1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d requires_dist: - objgraph>=1.7.2 ; extra == 'graph' - gprof2dot>=2022.7.29 ; extra == 'profile' - requires_python: '>=3.8' + requires_python: '>=3.9' - 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/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl + name: dnspython + version: 2.8.0 + sha256: 01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af + requires_dist: + - black>=25.1.0 ; extra == 'dev' + - coverage>=7.0 ; extra == 'dev' + - flake8>=7 ; extra == 'dev' + - hypercorn>=0.17.0 ; extra == 'dev' + - mypy>=1.17 ; extra == 'dev' + - pylint>=3 ; extra == 'dev' + - pytest-cov>=6.2.0 ; extra == 'dev' + - pytest>=8.4 ; extra == 'dev' + - quart-trio>=0.12.0 ; extra == 'dev' + - sphinx-rtd-theme>=3.0.0 ; extra == 'dev' + - sphinx>=8.2.0 ; extra == 'dev' + - twine>=6.1.0 ; extra == 'dev' + - wheel>=0.45.0 ; extra == 'dev' + - cryptography>=45 ; extra == 'dnssec' + - h2>=4.2.0 ; extra == 'doh' + - httpcore>=1.0.0 ; extra == 'doh' + - httpx>=0.28.0 ; extra == 'doh' + - aioquic>=1.2.0 ; extra == 'doq' + - idna>=3.10 ; extra == 'idna' + - trio>=0.30 ; extra == 'trio' + - wmi>=1.5.1 ; sys_platform == 'win32' and extra == 'wmi' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl name: docformatter version: 1.7.7 @@ -4187,10 +4667,10 @@ packages: - tomli>=2.0.0,<3.0.0 ; python_full_version < '3.11' and extra == 'tomli' - untokenize>=0.1.1,<0.2.0 requires_python: '>=3.9,<4.0' -- pypi: https://files.pythonhosted.org/packages/82/ee/c9ca3f81d69ec6c99ca41e10432a58c60fef3a6e63cce6df7e796f90fe22/easydiffraction-0.10.0-py3-none-any.whl +- pypi: ./ name: easydiffraction - version: 0.10.0 - sha256: eef4593f36b9cacf6ded23aa2c22b6b3c317d97b5791c4625c4a66ca457a8e3d + version: 0.10.2+devdirty78 + sha256: 735c06d04ba73c012b4d00d6562d8f55a629fd23dfa89532d2818e52d493013d requires_dist: - asciichartpy - asteval @@ -4200,9 +4680,10 @@ packages: - dfo-ls - diffpy-pdffit2 - diffpy-utils + - essdiffraction - gemmi - lmfit - - numpy<2.4 + - numpy - pooch - rich - scipy @@ -4217,12 +4698,11 @@ packages: - docformatter ; extra == 'all' - interrogate ; extra == 'all' - jinja2 ; extra == 'all' - - jupyter-dark-detect ; extra == 'all' - jupyterquiz ; extra == 'all' - jupytext ; extra == 'all' - mike ; extra == 'all' - mkdocs ; extra == 'all' - - mkdocs-autorefs<1.3.0 ; extra == 'all' + - mkdocs-autorefs ; extra == 'all' - mkdocs-jupyter ; extra == 'all' - mkdocs-markdownextradata-plugin ; extra == 'all' - mkdocs-material ; extra == 'all' @@ -4262,7 +4742,7 @@ packages: - versioningit ; extra == 'dev' - mike ; extra == 'docs' - mkdocs ; extra == 'docs' - - mkdocs-autorefs<1.3.0 ; extra == 'docs' + - mkdocs-autorefs ; extra == 'docs' - mkdocs-jupyter ; extra == 'docs' - mkdocs-markdownextradata-plugin ; extra == 'docs' - mkdocs-material ; extra == 'docs' @@ -4270,11 +4750,74 @@ packages: - mkdocstrings-python ; extra == 'docs' - pyyaml ; extra == 'docs' - darkdetect ; extra == 'visualization' - - jupyter-dark-detect ; extra == 'visualization' - pandas ; extra == 'visualization' - plotly ; extra == 'visualization' - py3dmol ; extra == 'visualization' requires_python: '>=3.11,<3.14' +- pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + name: email-validator + version: 2.3.0 + sha256: 80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4 + requires_dist: + - dnspython>=2.0.0 + - idna>=2.0.0 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl + name: essdiffraction + version: 26.3.0 + sha256: 3e3063d8af817ece183258b35579491d7edf85c4ca95fa3f1cef7877ad24365a + requires_dist: + - dask>=2022.1.0 + - essreduce>=26.2.1 + - graphviz + - numpy>=2 + - plopp>=26.2.0 + - pythreejs>=2.4.1 + - sciline>=25.4.1 + - scipp>=25.11.0 + - scippneutron>=25.2.0 + - scippnexus>=23.12.0 + - tof>=25.12.0 + - ncrystal[cif]>=4.1.0 + - spglib!=2.7 + - pandas>=2.1.2 ; extra == 'test' + - pooch>=1.5 ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - ipywidgets>=8.1.7 ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + name: essreduce + version: 26.3.1 + sha256: 0f629a52ad1793904cc41f6298229321f249f87e972b35f0b0fcfad8f280a78b + requires_dist: + - sciline>=25.11.0 + - scipp>=25.4.0 + - scippneutron>=25.11.1 + - scippnexus>=25.6.0 + - graphviz>=0.20 ; extra == 'test' + - ipywidgets>=8.1 ; extra == 'test' + - matplotlib>=3.10.7 ; extra == 'test' + - numba>=0.63 ; extra == 'test' + - pooch>=1.9.0 ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - scipy>=1.14 ; extra == 'test' + - tof>=25.12.0 ; extra == 'test' + - autodoc-pydantic ; extra == 'docs' + - graphviz>=0.20 ; extra == 'docs' + - ipykernel ; extra == 'docs' + - ipython!=8.7.0 ; extra == 'docs' + - ipywidgets>=8.1 ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbsphinx ; extra == 'docs' + - numba>=0.63 ; extra == 'docs' + - plopp ; extra == 'docs' + - pydata-sphinx-theme>=0.14 ; extra == 'docs' + - sphinx>=7 ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-design ; extra == 'docs' + - tof>=25.12.0 ; extra == 'docs' + requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl name: execnet version: 2.1.2 @@ -4311,15 +4854,15 @@ packages: - pytest-benchmark ; extra == 'devel' - pytest-cache ; extra == 'devel' - validictory ; extra == 'devel' -- pypi: https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl name: filelock - version: 3.20.2 - sha256: fbba7237d6ea277175a32c54bb71ef814a8546d8601269e1bfc388de333974e8 + version: 3.25.1 + sha256: 18972df45473c4aa2c7921b609ee9ca4925910cc3a0fb226c96b92fc224ef7bf requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/77/ce/f5a4c42c117f8113ce04048053c128d17426751a508f26398110c993a074/fonttools-4.62.0-cp311-cp311-win_amd64.whl name: fonttools - version: 4.61.1 - sha256: fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7 + version: 4.62.0 + sha256: 4da779e8f342a32856075ddb193b2a024ad900bc04ecb744014c32409ae871ed requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4350,10 +4893,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/82/c7/985c1670aa6d82ef270f04cde11394c168f2002700353bd2bde405e59b8f/fonttools-4.62.0-cp313-cp313-macosx_10_13_universal2.whl name: fonttools - version: 4.61.1 - sha256: 21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b + version: 4.62.0 + sha256: 274c8b8a87e439faf565d3bcd3f9f9e31bca7740755776a4a90a4bfeaa722efa requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4384,10 +4927,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl +- pypi: https://files.pythonhosted.org/packages/8a/d7/8e4845993ee233c2023d11babe9b3dae7d30333da1d792eeccebcb77baab/fonttools-4.62.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: fonttools - version: 4.61.1 - sha256: 8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c + version: 4.62.0 + sha256: 591220d5333264b1df0d3285adbdfe2af4f6a45bbf9ca2b485f97c9f577c49ff requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4418,10 +4961,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/c0/7a/9aeec114bc9fc00d757a41f092f7107863d372e684a5b5724c043654477c/fonttools-4.62.0-cp311-cp311-macosx_10_9_x86_64.whl name: fonttools - version: 4.61.1 - sha256: dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e + version: 4.62.0 + sha256: 153afc3012ff8761b1733e8fbe5d98623409774c44ffd88fbcb780e240c11d13 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4452,10 +4995,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl +- pypi: https://files.pythonhosted.org/packages/c1/dc/c409c8ceec0d3119e9ab0b7b1a2e3c76d1f4d66e4a9db5c59e6b7652e7df/fonttools-4.62.0-cp313-cp313-macosx_10_13_x86_64.whl name: fonttools - version: 4.61.1 - sha256: c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09 + version: 4.62.0 + sha256: 93e27131a5a0ae82aaadcffe309b1bae195f6711689722af026862bede05c07c requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4486,10 +5029,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/e4/33/63d79ca41020dd460b51f1e0f58ad1ff0a36b7bcbdf8f3971d52836581e9/fonttools-4.62.0-cp311-cp311-macosx_10_9_universal2.whl name: fonttools - version: 4.61.1 - sha256: 75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9 + version: 4.62.0 + sha256: 196cafef9aeec5258425bd31a4e9a414b2ee0d1557bca184d7923d3d3bcd90f9 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4520,10 +5063,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.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/f5/7a/e25245a30457595740041dba9d0ea8ec1b2517f2f1a6a741f15eba1a4edc/fonttools-4.62.0-cp313-cp313-win_amd64.whl name: fonttools - version: 4.61.1 - sha256: 64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5 + version: 4.62.0 + sha256: 6826a5aa53fb6def8a66bf423939745f415546c4e92478a7c531b8b6282b6c3b requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4554,10 +5097,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/fb/bc/60d93477b653eeb1ddf5f9ec34be689b79234d82dbdded269ac0252715b8/fonttools-4.62.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: fonttools - version: 4.61.1 - sha256: 5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37 + version: 4.62.0 + sha256: 106aec9226f9498fc5345125ff7200842c01eda273ae038f5049b0916907acee requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4635,46 +5178,154 @@ packages: version: 1.8.0 sha256: 17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/19/5b/0976c1af0dd59a6850e9ea3b6c6d28f3dff0651c694a6a6192a2933e8feb/gemmi-0.7.4-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + name: fsspec + version: 2026.2.0 + sha256: 98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437 + requires_dist: + - adlfs ; extra == 'abfs' + - adlfs ; extra == 'adl' + - pyarrow>=1 ; extra == 'arrow' + - dask ; extra == 'dask' + - distributed ; extra == 'dask' + - pre-commit ; extra == 'dev' + - ruff>=0.5 ; extra == 'dev' + - numpydoc ; extra == 'doc' + - sphinx ; extra == 'doc' + - sphinx-design ; extra == 'doc' + - sphinx-rtd-theme ; extra == 'doc' + - yarl ; extra == 'doc' + - dropbox ; extra == 'dropbox' + - dropboxdrivefs ; extra == 'dropbox' + - requests ; extra == 'dropbox' + - adlfs ; extra == 'full' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'full' + - dask ; extra == 'full' + - distributed ; extra == 'full' + - dropbox ; extra == 'full' + - dropboxdrivefs ; extra == 'full' + - fusepy ; extra == 'full' + - gcsfs>2024.2.0 ; extra == 'full' + - libarchive-c ; extra == 'full' + - ocifs ; extra == 'full' + - panel ; extra == 'full' + - paramiko ; extra == 'full' + - pyarrow>=1 ; extra == 'full' + - pygit2 ; extra == 'full' + - requests ; extra == 'full' + - s3fs>2024.2.0 ; extra == 'full' + - smbprotocol ; extra == 'full' + - tqdm ; extra == 'full' + - fusepy ; extra == 'fuse' + - gcsfs>2024.2.0 ; extra == 'gcs' + - pygit2 ; extra == 'git' + - requests ; extra == 'github' + - gcsfs ; extra == 'gs' + - panel ; extra == 'gui' + - pyarrow>=1 ; extra == 'hdfs' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'http' + - libarchive-c ; extra == 'libarchive' + - ocifs ; extra == 'oci' + - s3fs>2024.2.0 ; extra == 's3' + - paramiko ; extra == 'sftp' + - smbprotocol ; extra == 'smb' + - paramiko ; extra == 'ssh' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'test' + - numpy ; extra == 'test' + - pytest ; extra == 'test' + - pytest-asyncio!=0.22.0 ; extra == 'test' + - pytest-benchmark ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-recording ; extra == 'test' + - pytest-rerunfailures ; extra == 'test' + - requests ; extra == 'test' + - aiobotocore>=2.5.4,<3.0.0 ; extra == 'test-downstream' + - dask[dataframe,test] ; extra == 'test-downstream' + - moto[server]>4,<5 ; extra == 'test-downstream' + - pytest-timeout ; extra == 'test-downstream' + - xarray ; extra == 'test-downstream' + - adlfs ; extra == 'test-full' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'test-full' + - backports-zstd ; python_full_version < '3.14' and extra == 'test-full' + - cloudpickle ; extra == 'test-full' + - dask ; extra == 'test-full' + - distributed ; extra == 'test-full' + - dropbox ; extra == 'test-full' + - dropboxdrivefs ; extra == 'test-full' + - fastparquet ; extra == 'test-full' + - fusepy ; extra == 'test-full' + - gcsfs ; extra == 'test-full' + - jinja2 ; extra == 'test-full' + - kerchunk ; extra == 'test-full' + - libarchive-c ; extra == 'test-full' + - lz4 ; extra == 'test-full' + - notebook ; extra == 'test-full' + - numpy ; extra == 'test-full' + - ocifs ; extra == 'test-full' + - pandas<3.0.0 ; extra == 'test-full' + - panel ; extra == 'test-full' + - paramiko ; extra == 'test-full' + - pyarrow ; extra == 'test-full' + - pyarrow>=1 ; extra == 'test-full' + - pyftpdlib ; extra == 'test-full' + - pygit2 ; extra == 'test-full' + - pytest ; extra == 'test-full' + - pytest-asyncio!=0.22.0 ; extra == 'test-full' + - pytest-benchmark ; extra == 'test-full' + - pytest-cov ; extra == 'test-full' + - pytest-mock ; extra == 'test-full' + - pytest-recording ; extra == 'test-full' + - pytest-rerunfailures ; extra == 'test-full' + - python-snappy ; extra == 'test-full' + - requests ; extra == 'test-full' + - smbprotocol ; extra == 'test-full' + - tqdm ; extra == 'test-full' + - urllib3 ; extra == 'test-full' + - zarr ; extra == 'test-full' + - zstandard ; python_full_version < '3.14' and extra == 'test-full' + - tqdm ; extra == 'tqdm' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/42/15/26cac702cdf6281ddeb185d5912ce14e555e277c6e4caeb1d36966e43822/gemmi-0.7.5-cp311-cp311-macosx_11_0_arm64.whl name: gemmi - version: 0.7.4 - sha256: d1757e5210fb4244190af782a175787cdd3baa3128f1157cd43d3ccbd3133a60 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/19/f7/1a03aa6b06d26dbd1231b3ac907f8fdfcf02c0b27fc5f4c31493ad6ecdad/gemmi-0.7.4-cp313-cp313-win_amd64.whl + version: 0.7.5 + sha256: 4db34eaa3d3fc102afea7a156330862cbeb82f557444c079403d4412e326c527 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/48/eb/46e443fc70b4aabe6e775521ff476aefb051db9acabb16a5cb51f04e3e2b/gemmi-0.7.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: gemmi - version: 0.7.4 - sha256: ce51f071a69d72c5c242be17e3cd31fbfe7e4a31c86b532c44ea083761389274 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/28/bc/e943898c25121f36625ab4913b8e24d0bdd054a17e380d19924066102574/gemmi-0.7.4-cp311-cp311-macosx_10_14_x86_64.whl + version: 0.7.5 + sha256: 895c63c7bcf30cffba97cf12c89dc3905f4645f838c17009b4534459a6c53a1e + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/7f/79/b13830a65bf9fc85474a984604f094cc18817dc93a784f4c567a2dc05169/gemmi-0.7.5-cp311-cp311-macosx_10_14_x86_64.whl name: gemmi - version: 0.7.4 - sha256: 006e3251a1cc70e050460e5b4bd8ab30225feb32dace39a6354806ce77e61e5a - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/3e/e7/b88b72919c910d3233065680cbc74a2a9d00ed65a06b100751d5b78e08e1/gemmi-0.7.4-cp313-cp313-macosx_10_14_x86_64.whl + version: 0.7.5 + sha256: e134fd33f34bf9f2ffacd9e0207aeac6329dde818f62340e7390217a25ee8e2d + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a3/8c/db8e79c4c744ebae1dcf25f7dbcc5d7df912cdbcdf7221e761479e8bd04b/gemmi-0.7.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: gemmi - version: 0.7.4 - sha256: 4088c03764d8b5c0202b43e7def0fa53909b93e13b2fea33473953cd64969e2a - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/46/3e/51e7914c8a640548d1b980140b1bd1419c169bee300a556cfd7f4175444d/gemmi-0.7.4-cp311-cp311-macosx_11_0_arm64.whl + version: 0.7.5 + sha256: 750b4d9751aaf1460ac4f0f45308ddced25f47bcf7a30355eb3b1f779f03952a + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b9/5e/62402bf021183bc6122cb01b8f1be17cac67545713fb30f888f59357a782/gemmi-0.7.5-cp311-cp311-win_amd64.whl name: gemmi - version: 0.7.4 - sha256: ee61dd8579fbe19d658806ca42013e780846a407597777db83abbeb68da7100e - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/96/ae/41aff180c36dd3c8f0a84faf38ac8683f8dca99250abcfbc3ed19897290b/gemmi-0.7.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + version: 0.7.5 + sha256: 06cb44f4e3657b7e3a2b23cd40b67a8e7b5d00bfb92ea94cb4060bd47ba50df6 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c1/9c/1236dd7d22ed48527286b613c84e3376ea731b65e6734b6e6a0b4d03744c/gemmi-0.7.5-cp313-cp313-macosx_11_0_arm64.whl name: gemmi - version: 0.7.4 - sha256: 7ff3f09305f9f7e8c6a4332c8882f3b909b97efc912cd7b376651f4b2e095d42 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/dd/9d/412d75eb7b9c0aa1e939b419a66c7d61471aa387919d4be32893921564af/gemmi-0.7.4-cp311-cp311-win_amd64.whl + version: 0.7.5 + sha256: c7d8b08c33fe6ba375223306149092440c69cbfbd55c3d3e3436e5fb315a225d + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c4/80/fd758344a72ca7b5e1c5bbdc1d263f3b215d3897941b5f450380445ca0a9/gemmi-0.7.5-cp313-cp313-macosx_10_14_x86_64.whl name: gemmi - version: 0.7.4 - sha256: c802b5dc495e53e055c056d5f647dd28f6c0c815d14a778d0cf607db8691c9f3 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/ed/34/a6536afaeee07fa351e2087bf7b5b1522aa703bc1f6e29d53c27a722ac33/gemmi-0.7.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + version: 0.7.5 + sha256: ef9b6ada1c00c6ba7c7a5b9e938cc3b45d83e775c23d12bf63b6882d5f3cdd6b + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ee/ab/7d7463cda94f8b68b969ea97aaad679655a0e436efd6a643e528a8de114e/gemmi-0.7.5-cp313-cp313-win_amd64.whl name: gemmi - version: 0.7.4 - sha256: aaa1b1a613649db9f9d6c67df6662c8357c3459112842dd7a77203552b795a02 - requires_python: '>=3.8' + version: 0.7.5 + sha256: ad1f72ffa24adbfaf259e11471f6f071a668667f6ca846051f3bfea024fd337d + requires_python: '>=3.9' - 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 @@ -4685,10 +5336,30 @@ packages: - markdown ; extra == 'dev' - flake8 ; extra == 'dev' - wheel ; extra == 'dev' -- pypi: https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl +- pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + name: graphviz + version: '0.21' + sha256: 54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42 + requires_dist: + - build ; extra == 'dev' + - wheel ; extra == 'dev' + - twine ; extra == 'dev' + - flake8 ; extra == 'dev' + - flake8-pyproject ; extra == 'dev' + - pep8-naming ; extra == 'dev' + - tox>=3 ; extra == 'dev' + - pytest>=7,<8.1 ; extra == 'test' + - pytest-mock>=3 ; extra == 'test' + - pytest-cov ; extra == 'test' + - coverage ; extra == 'test' + - sphinx>=5,<7 ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx-rtd-theme>=0.2.5 ; extra == 'docs' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl name: greenlet - version: 3.3.0 - sha256: a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739 + version: 3.3.2 + sha256: 8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358 requires_dist: - sphinx ; extra == 'docs' - furo ; extra == 'docs' @@ -4696,10 +5367,10 @@ packages: - psutil ; extra == 'test' - setuptools ; extra == 'test' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/1d/d5/c339b3b4bc8198b7caa4f2bd9fd685ac9f29795816d8db112da3d04175bb/greenlet-3.3.0-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl name: greenlet - version: 3.3.0 - sha256: 7652ee180d16d447a683c04e4c5f6441bae7ba7b17ffd9f6b3aff4605e9e6f71 + version: 3.3.2 + sha256: b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab requires_dist: - sphinx ; extra == 'docs' - furo ; extra == 'docs' @@ -4707,10 +5378,10 @@ packages: - psutil ; extra == 'test' - setuptools ; extra == 'test' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/1f/cb/48e964c452ca2b92175a9b2dca037a553036cb053ba69e284650ce755f13/greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl +- pypi: https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl name: greenlet - version: 3.3.0 - sha256: e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e + version: 3.3.2 + sha256: a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124 requires_dist: - sphinx ; extra == 'docs' - furo ; extra == 'docs' @@ -4718,10 +5389,10 @@ packages: - psutil ; extra == 'test' - setuptools ; extra == 'test' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl name: greenlet - version: 3.3.0 - sha256: 9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38 + version: 3.3.2 + sha256: aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4 requires_dist: - sphinx ; extra == 'docs' - furo ; extra == 'docs' @@ -4729,10 +5400,10 @@ packages: - psutil ; extra == 'test' - setuptools ; extra == 'test' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/dc/a6/e959a127b630a58e23529972dbc868c107f9d583b5a9f878fb858c46bc1a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/f1/3a/efb2cf697fbccdf75b24e2c18025e7dfa54c4f31fab75c51d0fe79942cef/greenlet-3.3.2-cp311-cp311-win_amd64.whl name: greenlet - version: 3.3.0 - sha256: 6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948 + version: 3.3.2 + sha256: 1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5 requires_dist: - sphinx ; extra == 'docs' - furo ; extra == 'docs' @@ -4740,10 +5411,10 @@ packages: - psutil ; extra == 'test' - setuptools ; extra == 'test' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl name: greenlet - version: 3.3.0 - sha256: 087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527 + version: 3.3.2 + sha256: c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86 requires_dist: - sphinx ; extra == 'docs' - furo ; extra == 'docs' @@ -4751,12 +5422,11 @@ packages: - psutil ; extra == 'test' - setuptools ; extra == 'test' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl - name: griffe - version: 1.15.0 - sha256: 6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3 +- pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + name: griffelib + version: 2.0.0 + sha256: 01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f requires_dist: - - colorama>=0.4 - pip>=24.0 ; extra == 'pypi' - platformdirs>=4.2 ; extra == 'pypi' - wheel>=0.42 ; extra == 'pypi' @@ -4817,59 +5487,59 @@ packages: version: 0.16.0 sha256: 63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/23/95/499b4e56452ef8b6c95a271af0dde08dac4ddb70515a75f346d4f400579b/h5py-3.15.1-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl name: h5py - version: 3.15.1 - sha256: 550e51131376889656feec4aff2170efc054a7fe79eb1da3bb92e1625d1ac878 + version: 3.16.0 + sha256: 370a845f432c2c9619db8eed334d1e610c6015796122b0e57aa46312c22617d9 requires_dist: - numpy>=1.21.2 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl name: h5py - version: 3.15.1 - sha256: ab2219dbc6fcdb6932f76b548e2b16f34a1f52b7666e998157a4dfc02e2c4123 + version: 3.16.0 + sha256: fb1720028d99040792bb2fb31facb8da44a6f29df7697e0b84f0d79aff2e9bd3 requires_dist: - numpy>=1.21.2 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/41/fd/8349b48b15b47768042cff06ad6e1c229f0a4bd89225bf6b6894fea27e6d/h5py-3.15.1-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl name: h5py - version: 3.15.1 - sha256: 5aaa330bcbf2830150c50897ea5dcbed30b5b6d56897289846ac5b9e529ec243 + version: 3.16.0 + sha256: 42108e93326c50c2810025aade9eac9d6827524cdccc7d4b75a546e5ab308edb requires_dist: - numpy>=1.21.2 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/ba/95/a825894f3e45cbac7554c4e97314ce886b233a20033787eda755ca8fecc7/h5py-3.16.0-cp311-cp311-macosx_10_9_x86_64.whl name: h5py - version: 3.15.1 - sha256: c8440fd8bee9500c235ecb7aa1917a0389a2adb80c209fa1cc485bd70e0d94a5 + version: 3.16.0 + sha256: 719439d14b83f74eeb080e9650a6c7aa6d0d9ea0ca7f804347b05fac6fbf18af requires_dist: - 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 +- pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl name: h5py - version: 3.15.1 - sha256: 5b849ba619a066196169763c33f9f0f02e381156d61c03e000bb0100f9950faf + version: 3.16.0 + sha256: 9300ad32dea9dfc5171f94d5f6948e159ed93e4701280b0f508773b3f582f402 requires_dist: - numpy>=1.21.2 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/c1/b0/1c628e26a0b95858f54aba17e1599e7f6cd241727596cc2580b72cb0a9bf/h5py-3.15.1-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl name: h5py - version: 3.15.1 - sha256: c970fb80001fffabb0109eaf95116c8e7c0d3ca2de854e0901e8a04c1f098509 + version: 3.16.0 + sha256: c3f0a0e136f2e95dd0b67146abb6668af4f1a69c81ef8651a2d316e8e01de447 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 +- pypi: https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl name: h5py - version: 3.15.1 - sha256: 121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8 + version: 3.16.0 + sha256: 18f2bbcd545e6991412253b98727374c356d67caa920e68dc79eab36bf5fedad 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 +- pypi: https://files.pythonhosted.org/packages/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl name: h5py - version: 3.15.1 - sha256: dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52 + version: 3.16.0 + sha256: 17d1f1630f92ad74494a9a7392ab25982ce2b469fc62da6074c0ce48366a2999 requires_dist: - numpy>=1.21.2 requires_python: '>=3.10' @@ -4903,57 +5573,45 @@ packages: - socksio==1.* ; extra == 'socks' - zstandard>=0.18.0 ; extra == 'zstd' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - sha256: 71e750d509f5fa3421087ba88ef9a7b9be11c53174af3aa4d06aff4c18b38e8e - md5: 8b189310083baabfb622af68fd9d3ae3 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + sha256: 142a722072fa96cf16ff98eaaf641f54ab84744af81754c292cb81e0881c0329 + md5: 186a18e3ba246eccfc7cff00cd19a870 depends: - __glibc >=2.17,<3.0.a0 - - libgcc-ng >=12 - - libstdcxx-ng >=12 + - libgcc >=14 + - libstdcxx >=14 license: MIT license_family: MIT purls: [] - size: 12129203 - timestamp: 1720853576813 -- conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - sha256: 2e64307532f482a0929412976c8450c719d558ba20c0962832132fd0d07ba7a7 - md5: d68d48a3060eb5abdc1cdc8e2a3a5966 + size: 12728445 + timestamp: 1767969922681 +- conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.2-h14c5de8_0.conda + sha256: f3066beae7fe3002f09c8a412cdf1819f49a2c9a485f720ec11664330cf9f1fe + md5: 30334add4de016489b731c6662511684 depends: - __osx >=10.13 license: MIT license_family: MIT purls: [] - size: 11761697 - timestamp: 1720853679409 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - sha256: 9ba12c93406f3df5ab0a43db8a4b4ef67a5871dfd401010fbe29b218b2cbe620 - md5: 5eb22c1d7b3fc4abb50d92d621583137 + size: 12263724 + timestamp: 1767970604977 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + sha256: 24bc62335106c30fecbcc1dba62c5eba06d18b90ea1061abd111af7b9c89c2d7 + md5: 114e6bfe7c5ad2525eb3597acdbf2300 depends: - __osx >=11.0 license: MIT license_family: MIT purls: [] - size: 11857802 - timestamp: 1720853997952 -- conda: https://conda.anaconda.org/conda-forge/win-64/icu-78.1-h637d24d_0.conda - sha256: bee083d5a0f05c380fcec1f30a71ef5518b23563aeb0a21f6b60b792645f9689 - md5: cb8048bed35ef01431184d6a88e46b3e - depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT - purls: [] - size: 13849749 - timestamp: 1766299627069 -- pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + size: 12389400 + timestamp: 1772209104304 +- pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl name: identify - version: 2.6.15 - sha256: 1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757 + version: 2.6.17 + sha256: be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0 requires_dist: - ukkonen ; extra == 'license' - requires_python: '>=3.9' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl name: idna version: '3.11' @@ -4990,27 +5648,6 @@ packages: - pytest-mypy>=1.0.1 ; extra == 'type' - mypy<1.19 ; platform_python_implementation == 'PyPy' and extra == 'type' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl - name: importlib-resources - version: 6.5.2 - sha256: 789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec - requires_dist: - - zipp>=3.1.0 ; python_full_version < '3.10' - - pytest>=6,!=8.1.* ; extra == 'test' - - zipp>=3.17 ; extra == 'test' - - jaraco-test>=5.4 ; extra == 'test' - - sphinx>=3.5 ; extra == 'doc' - - jaraco-packaging>=9.3 ; extra == 'doc' - - rst-linker>=1.9 ; extra == 'doc' - - furo ; extra == 'doc' - - sphinx-lint ; extra == 'doc' - - jaraco-tidelift>=1.4 ; extra == 'doc' - - pytest-checkdocs>=2.4 ; extra == 'check' - - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' - - pytest-cov ; extra == 'cover' - - pytest-enabler>=2.2 ; extra == 'enabler' - - pytest-mypy ; extra == 'type' - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl name: iniconfig version: 2.3.0 @@ -5044,6 +5681,21 @@ packages: - pytest-mock ; extra == 'tests' - coverage[toml] ; extra == 'tests' requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl + name: ipydatawidgets + version: 4.3.5 + sha256: d590cdb7c364f2f6ab346f20b9d2dd661d27a834ef7845bc9d7113118f05ec87 + requires_dist: + - ipywidgets>=7.0.0 + - numpy + - traittypes>=0.2.0 + - sphinx ; extra == 'docs' + - recommonmark ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - pytest>=4 ; extra == 'test' + - pytest-cov ; extra == 'test' + - nbval>=0.9.2 ; extra == 'test' + requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl name: ipykernel version: 6.31.0 @@ -5084,10 +5736,10 @@ packages: - pytest-timeout ; extra == 'test' - pytest>=7.0,<9 ; extra == 'test' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/86/92/162cfaee4ccf370465c5af1ce36a9eacec1becb552f2033bb3584e6f640a/ipython-9.9.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl name: ipython - version: 9.9.0 - sha256: b457fe9165df2b84e8ec909a97abcf2ed88f565970efba16b1f7229c283d252b + version: 9.10.0 + sha256: c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d requires_dist: - colorama>=0.4.4 ; sys_platform == 'win32' - decorator>=4.3.2 @@ -5130,6 +5782,52 @@ packages: - ipython[doc,matplotlib,terminal,test,test-extra] ; extra == 'all' - argcomplete>=3.0 ; extra == 'all' requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl + name: ipython + version: 9.11.0 + sha256: 6922d5bcf944c6e525a76a0a304451b60a2b6f875e86656d8bc2dfda5d710e19 + requires_dist: + - colorama>=0.4.4 ; sys_platform == 'win32' + - decorator>=5.1.0 + - ipython-pygments-lexers>=1.0.0 + - jedi>=0.18.2 + - matplotlib-inline>=0.1.6 + - pexpect>4.6 ; sys_platform != 'emscripten' and sys_platform != 'win32' + - prompt-toolkit>=3.0.41,<3.1.0 + - pygments>=2.14.0 + - stack-data>=0.6.0 + - traitlets>=5.13.0 + - black ; extra == 'black' + - docrepr ; extra == 'doc' + - exceptiongroup ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - ipykernel ; extra == 'doc' + - ipython[matplotlib,test] ; extra == 'doc' + - setuptools>=80.0 ; extra == 'doc' + - sphinx-toml==0.0.4 ; extra == 'doc' + - sphinx-rtd-theme>=0.1.8 ; extra == 'doc' + - sphinx>=8.0 ; extra == 'doc' + - typing-extensions ; extra == 'doc' + - pytest>=7.0.0 ; extra == 'test' + - pytest-asyncio>=1.0.0 ; extra == 'test' + - testpath>=0.2 ; extra == 'test' + - packaging>=23.0.0 ; extra == 'test' + - setuptools>=80.0 ; 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>6.30 ; extra == 'test-extra' + - numpy>=2.0 ; extra == 'test-extra' + - pandas>2.1 ; extra == 'test-extra' + - trio>=0.22.0 ; extra == 'test-extra' + - matplotlib>3.9 ; extra == 'matplotlib' + - ipython[doc,matplotlib,terminal,test,test-extra] ; extra == 'all' + - argcomplete>=3.0 ; extra == 'all' + - types-decorator ; extra == 'all' + requires_python: '>=3.12' - 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 @@ -5137,6 +5835,22 @@ packages: requires_dist: - pygments requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + name: ipywidgets + version: 8.1.8 + sha256: ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e + requires_dist: + - comm>=0.1.3 + - ipython>=6.1.0 + - traitlets>=4.3.1 + - widgetsnbextension~=4.0.14 + - jupyterlab-widgets~=3.0.15 + - jsonschema ; extra == 'test' + - ipykernel ; extra == 'test' + - pytest>=3.6.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytz ; extra == 'test' + requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl name: isoduration version: 20.11.0 @@ -5202,15 +5916,15 @@ packages: 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 +- pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl name: jsonschema - version: 4.25.1 - sha256: 3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63 + version: 4.26.0 + sha256: d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce requires_dist: - attrs>=22.2.0 - jsonschema-specifications>=2023.3.6 - referencing>=0.28.4 - - rpds-py>=0.7.1 + - rpds-py>=0.25.0 - fqdn ; extra == 'format' - idna ; extra == 'format' - isoduration ; extra == 'format' @@ -5228,7 +5942,7 @@ packages: - rfc3987-syntax>=1.1.0 ; extra == 'format-nongpl' - uri-template ; extra == 'format-nongpl' - webcolors>=24.6.0 ; extra == 'format-nongpl' - requires_python: '>=3.9' + requires_python: '>=3.10' - 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 @@ -5236,10 +5950,10 @@ packages: requires_dist: - referencing>=0.31.0 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl name: jupyter-client - version: 8.7.0 - sha256: 3671a94fd25e62f5f2f554f5e95389c2294d89822378a5f2dd24353e1494a9e0 + version: 8.8.0 + sha256: f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a requires_dist: - jupyter-core>=5.1 - python-dateutil>=2.8.2 @@ -5253,10 +5967,12 @@ packages: - sphinx>=4 ; extra == 'docs' - sphinxcontrib-github-alt ; extra == 'docs' - sphinxcontrib-spelling ; extra == 'docs' + - orjson ; extra == 'orjson' - anyio ; extra == 'test' - coverage ; extra == 'test' - ipykernel>=6.14 ; extra == 'test' - - mypy ; extra == 'test' + - msgpack ; extra == 'test' + - mypy ; platform_python_implementation != 'PyPy' and extra == 'test' - paramiko ; sys_platform == 'win32' and extra == 'test' - pre-commit ; extra == 'test' - pytest ; extra == 'test' @@ -5283,13 +5999,6 @@ packages: - pytest-timeout ; extra == 'test' - pytest<9 ; extra == 'test' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/76/c0/8cc378aad5ccfcc37adeda27d3864f20ea808ad5b63572f5b986a42a649c/jupyter_dark_detect-0.1.0-py3-none-any.whl - name: jupyter-dark-detect - version: 0.1.0 - sha256: 198a7fa1270c6ce1ac2fd056cea9300e018bb8d04211de4c946cbe9e03e7a89d - requires_dist: - - ipython>=7.0.0 - requires_python: '>=3.7' - 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 @@ -5373,10 +6082,10 @@ packages: - 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 +- pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl name: jupyter-server-terminals - version: 0.5.3 - sha256: 41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa + version: 0.5.4 + sha256: 55be353fc74a80bc7f3b20e6be50a55a61cd525626f578dcb66a5708e2007d14 requires_dist: - pywinpty>=2.0.3 ; os_name == 'nt' - terminado>=0.8.3 @@ -5397,10 +6106,10 @@ packages: - pytest-timeout ; extra == 'test' - pytest>=7.0 ; extra == 'test' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/af/c3/acced767eecc11a70c65c45295db5396c4f0c1937874937d5a76d7b177b6/jupyterlab-4.5.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl name: jupyterlab - version: 4.5.1 - sha256: 31b059de96de0754ff1f2ce6279774b6aab8c34d7082e9752db58207c99bd514 + version: 4.5.5 + sha256: a35694a40a8e7f2e82f387472af24e61b22adcce87b5a8ab97a5d9c486202a6d requires_dist: - async-lru>=1.0.0 - httpx>=0.25.0,<1 @@ -5501,14 +6210,19 @@ packages: - 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 +- pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + name: jupyterlab-widgets + version: 3.0.16 + sha256: 45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl name: jupyterquiz - version: 2.9.6.2 - sha256: f60f358c809d06a38c423c804740c1113c8840e130227aef50694a9201474bef -- pypi: https://files.pythonhosted.org/packages/bd/0d/2d240e7098e0cafba4d25e9530e7596b1bb1bd4476e41b10346bcaaa36d6/jupytext-1.18.1-py3-none-any.whl + version: 2.9.6.4 + sha256: f8c4418f6c827454523fc882a30d744b585cb58ac1ae277769c3059d04fc272b +- pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl name: jupytext - version: 1.18.1 - sha256: 24f999400726a1c658beae55e15fdd2a6255ab1a418697864cd779874e6011ab + version: 1.19.1 + sha256: d8975035155d034bdfde5c0c37891425314b7ea8d3a6c4b5d18c294348714cd9 requires_dist: - markdown-it-py>=1.0 - mdit-py-plugins @@ -5524,6 +6238,7 @@ packages: - isort ; extra == 'dev' - jupyter-fs[fs]>=1.0 ; extra == 'dev' - jupyter-server!=2.11 ; extra == 'dev' + - marimo>=0.17.6,<=0.19.4 ; extra == 'dev' - nbconvert ; extra == 'dev' - pre-commit ; extra == 'dev' - pytest ; extra == 'dev' @@ -5558,6 +6273,7 @@ packages: - isort ; extra == 'test-external' - jupyter-fs[fs]>=1.0 ; extra == 'test-external' - jupyter-server!=2.11 ; extra == 'test-external' + - marimo>=0.17.6,<=0.19.4 ; extra == 'test-external' - nbconvert ; extra == 'test-external' - pre-commit ; extra == 'test-external' - pytest ; extra == 'test-external' @@ -5581,45 +6297,45 @@ packages: - 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 +- pypi: https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl name: kiwisolver - version: 1.4.9 - sha256: 1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f + version: 1.5.0 + sha256: 0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac requires_python: '>=3.10' -- 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/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl name: kiwisolver - version: 1.4.9 - sha256: 2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543 + version: 1.5.0 + sha256: 3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: kiwisolver - version: 1.4.9 - sha256: be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2 + version: 1.5.0 + sha256: 332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3 requires_python: '>=3.10' -- 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/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: kiwisolver - version: 1.4.9 - sha256: dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61 + version: 1.5.0 + sha256: 2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27 requires_python: '>=3.10' -- 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/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl name: kiwisolver - version: 1.4.9 - sha256: dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d + version: 1.5.0 + sha256: dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd requires_python: '>=3.10' -- 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/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl name: kiwisolver - version: 1.4.9 - sha256: 39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089 + version: 1.5.0 + sha256: beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b requires_python: '>=3.10' -- 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/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl name: kiwisolver - version: 1.4.9 - sha256: efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2 + version: 1.5.0 + sha256: 0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276 requires_python: '>=3.10' -- 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/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl name: kiwisolver - version: 1.4.9 - sha256: b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098 + version: 1.5.0 + sha256: c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3 requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl name: lark @@ -5631,62 +6347,75 @@ packages: - 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.45-default_hbd61a6d_105.conda - sha256: 1027bd8aa0d5144e954e426ab6218fd5c14e54a98f571985675468b339c808ca - md5: 3ec0aa5037d39b06554109a01e6fb0c6 +- pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + name: lazy-loader + version: '0.5' + sha256: ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005 + requires_dist: + - packaging + - pytest>=8.0 ; extra == 'test' + - pytest-cov>=5.0 ; extra == 'test' + - coverage[toml]>=7.2 ; extra == 'test' + - pre-commit==4.3.0 ; extra == 'lint' + - changelist==0.5 ; extra == 'dev' + - spin==0.15 ; extra == 'dev' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + sha256: 565941ac1f8b0d2f2e8f02827cbca648f4d18cd461afc31f15604cd291b5c5f3 + md5: 12bd9a3f089ee6c9266a37dab82afabd depends: - __glibc >=2.17,<3.0.a0 - zstd >=1.5.7,<1.6.0a0 constrains: - - binutils_impl_linux-64 2.45 + - binutils_impl_linux-64 2.45.1 license: GPL-3.0-only license_family: GPL purls: [] - size: 730831 - timestamp: 1766513089214 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250512.1-cxx17_hba17884_0.conda - sha256: dcd1429a1782864c452057a6c5bc1860f2b637dc20a2b7e6eacd57395bbceff8 - md5: 83b160d4da3e1e847bf044997621ed63 + size: 725507 + timestamp: 1770267139900 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda + sha256: a7a4481a4d217a3eadea0ec489826a69070fcc3153f00443aa491ed21527d239 + md5: 6f7b4302263347698fd24565fbf11310 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 + - libgcc >=14 + - libstdcxx >=14 constrains: - - libabseil-static =20250512.1=cxx17* - - abseil-cpp =20250512.1 + - libabseil-static =20260107.1=cxx17* + - abseil-cpp =20260107.1 license: Apache-2.0 license_family: Apache purls: [] - size: 1310612 - timestamp: 1750194198254 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20250512.1-cxx17_hfc00f1c_0.conda - sha256: a878efebf62f039a1f1733c1e150a75a99c7029ece24e34efdf23d56256585b1 - md5: ddf1acaed2276c7eb9d3c76b49699a11 + size: 1384817 + timestamp: 1770863194876 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20260107.1-cxx17_h7ed6875_0.conda + sha256: 2b4ff36082ddfbacc47ac6e11d4dd9f3403cd109ce8d7f0fbee0cdd47cdef013 + md5: 317f40d7bd7bf6d54b56d4a5b5f5085d depends: - __osx >=10.13 - - libcxx >=18 + - libcxx >=19 constrains: - - abseil-cpp =20250512.1 - - libabseil-static =20250512.1=cxx17* + - libabseil-static =20260107.1=cxx17* + - abseil-cpp =20260107.1 license: Apache-2.0 license_family: Apache purls: [] - size: 1162435 - timestamp: 1750194293086 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20250512.1-cxx17_hd41c47c_0.conda - sha256: 7f0ee9ae7fa2cf7ac92b0acf8047c8bac965389e48be61bf1d463e057af2ea6a - md5: 360dbb413ee2c170a0a684a33c4fc6b8 + size: 1217836 + timestamp: 1770863510112 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda + sha256: 756611fbb8d2957a5b4635d9772bd8432cb6ddac05580a6284cca6fdc9b07fca + md5: bb65152e0d7c7178c0f1ee25692c9fd1 depends: - __osx >=11.0 - - libcxx >=18 + - libcxx >=19 constrains: - - libabseil-static =20250512.1=cxx17* - - abseil-cpp =20250512.1 + - abseil-cpp =20260107.1 + - libabseil-static =20260107.1=cxx17* license: Apache-2.0 license_family: Apache purls: [] - size: 1174081 - timestamp: 1750194620012 + size: 1229639 + timestamp: 1770863511331 - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda build_number: 5 sha256: 18c72545080b86739352482ba14ba2c4815e19e26a7417ca21a95b76ec8da24c @@ -5916,26 +6645,26 @@ packages: purls: [] size: 68079 timestamp: 1765819124349 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.8-h3d58e20_0.conda - sha256: cbd8e821e97436d8fc126c24b50df838b05ba4c80494fbb93ccaf2e3b2d109fb - md5: 9f8a60a77ecafb7966ca961c94f33bd1 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda + sha256: fa002b43752fe5860e588435525195324fe250287105ebd472ac138e97de45e6 + md5: 836389b6b9ae58f3fbcf7cafebd5c7f2 depends: - - __osx >=10.13 + - __osx >=11.0 license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 569777 - timestamp: 1765919624323 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.8-hf598326_0.conda - sha256: 82e228975fd491bcf1071ecd0a6ec2a0fcc5f57eb0bd1d52cb13a18d57c67786 - md5: 780f0251b757564e062187044232c2b7 + size: 570141 + timestamp: 1772001147762 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda + sha256: ce1049fa6fda9cf08ff1c50fb39573b5b0ea6958375d8ea7ccd8456ab81a0bcb + md5: e9c56daea841013e7774b5cd46f41564 depends: - __osx >=11.0 license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 569118 - timestamp: 1765919724254 + size: 568910 + timestamp: 1772001095642 - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda sha256: 1cd6048169fa0395af74ed5d8f1716e22c19a81a8a36f934c110ca3ad4dd27b4 md5: 172bf1cd1ff8629f2b1179945ed45055 @@ -5962,91 +6691,91 @@ packages: purls: [] size: 107458 timestamp: 1702146414478 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda - sha256: 1e1b08f6211629cbc2efe7a5bca5953f8f6b3cae0eeb04ca4dacee1bd4e2db2f - md5: 8b09ae86839581147ef2e5c5e229d164 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda + sha256: d78f1d3bea8c031d2f032b760f36676d87929b18146351c4464c66b0869df3f5 + md5: e7f7ce06ec24cfcfb9e36d28cf82ba57 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 constrains: - - expat 2.7.3.* + - expat 2.7.4.* license: MIT license_family: MIT purls: [] - size: 76643 - timestamp: 1763549731408 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.3-heffb93a_0.conda - sha256: d11b3a6ce5b2e832f430fd112084533a01220597221bee16d6c7dc3947dffba6 - md5: 222e0732a1d0780a622926265bee14ef + size: 76798 + timestamp: 1771259418166 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.4-h991f03e_0.conda + sha256: 8d9d79b2de7d6f335692391f5281607221bf5d040e6724dad4c4d77cd603ce43 + md5: a684eb8a19b2aa68fde0267df172a1e3 depends: - __osx >=10.13 constrains: - - expat 2.7.3.* + - expat 2.7.4.* license: MIT license_family: MIT purls: [] - size: 74058 - timestamp: 1763549886493 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.3-haf25636_0.conda - sha256: fce22610ecc95e6d149e42a42fbc3cc9d9179bd4eb6232639a60f06e080eec98 - md5: b79875dbb5b1db9a4a22a4520f918e1a + size: 74578 + timestamp: 1771260142624 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda + sha256: 03887d8080d6a8fe02d75b80929271b39697ecca7628f0657d7afaea87761edf + md5: a92e310ae8dfc206ff449f362fc4217f depends: - __osx >=11.0 constrains: - - expat 2.7.3.* + - expat 2.7.4.* license: MIT license_family: MIT purls: [] - size: 67800 - timestamp: 1763549994166 -- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.3-hac47afa_0.conda - sha256: 844ab708594bdfbd7b35e1a67c379861bcd180d6efe57b654f482ae2f7f5c21e - md5: 8c9e4f1a0e688eef2e95711178061a0f + size: 68199 + timestamp: 1771260020767 +- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda + sha256: b31f6fb629c4e17885aaf2082fb30384156d16b48b264e454de4a06a313b533d + md5: 1c1ced969021592407f16ada4573586d depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: - - expat 2.7.3.* + - expat 2.7.4.* license: MIT license_family: MIT purls: [] - size: 70137 - timestamp: 1763550049107 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda - sha256: 25cbdfa65580cfab1b8d15ee90b4c9f1e0d72128f1661449c9a999d341377d54 - md5: 35f29eec58405aaf55e01cb470d8c26a + size: 70323 + timestamp: 1771259521393 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 + md5: a360c33a5abe61c07959e449fa1453eb depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 57821 - timestamp: 1760295480630 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-h750e83c_0.conda - sha256: 277dc89950f5d97f1683f26e362d6dca3c2efa16cb2f6fdb73d109effa1cd3d0 - md5: d214916b24c625bcc459b245d509f22e + size: 58592 + timestamp: 1769456073053 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda + sha256: 951958d1792238006fdc6fce7f71f1b559534743b26cc1333497d46e5903a2d6 + md5: 66a0dc7464927d0853b590b6f53ba3ea depends: - __osx >=10.13 license: MIT license_family: MIT purls: [] - size: 52573 - timestamp: 1760295626449 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda - sha256: 9b8acdf42df61b7bfe8bdc545c016c29e61985e79748c64ad66df47dbc2e295f - md5: 411ff7cd5d1472bba0f55c0faf04453b + size: 53583 + timestamp: 1769456300951 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + sha256: 6686a26466a527585e6a75cc2a242bf4a3d97d6d6c86424a441677917f28bec7 + md5: 43c04d9cb46ef176bb2a4c77e324d599 depends: - __osx >=11.0 license: MIT license_family: MIT purls: [] - size: 40251 - timestamp: 1760295839166 -- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h52bdfb6_0.conda - sha256: ddff25aaa4f0aa535413f5d831b04073789522890a4d8626366e43ecde1534a3 - md5: ba4ad812d2afc22b9a34ce8327a0930f + size: 40979 + timestamp: 1769456747661 +- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + sha256: 59d01f2dfa8b77491b5888a5ab88ff4e1574c9359f7e229da254cdfe27ddc190 + md5: 720b39f5ec0610457b725eb3f396219a depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 @@ -6054,97 +6783,97 @@ packages: license: MIT license_family: MIT purls: [] - size: 44866 - timestamp: 1760295760649 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_16.conda - sha256: 6eed58051c2e12b804d53ceff5994a350c61baf117ec83f5f10c953a3f311451 - md5: 6d0363467e6ed84f11435eb309f2ff06 + size: 45831 + timestamp: 1769456418774 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda + sha256: faf7d2017b4d718951e3a59d081eb09759152f93038479b768e3d612688f83f5 + md5: 0aa00f03f9e39fb9876085dee11a85d4 depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 constrains: - - libgcc-ng ==15.2.0=*_16 - - libgomp 15.2.0 he0feb66_16 + - libgcc-ng ==15.2.0=*_18 + - libgomp 15.2.0 he0feb66_18 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 1042798 - timestamp: 1765256792743 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_15.conda - sha256: e04b115ae32f8cbf95905971856ff557b296511735f4e1587b88abf519ff6fb8 - md5: c816665789d1e47cdfd6da8a81e1af64 + size: 1041788 + timestamp: 1771378212382 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_18.conda + sha256: 83366f11615ab234aa1e0797393f9e07b78124b5a24c4a9f8af0113d02df818e + md5: 9a5cb96e43f5c2296690186e15b3296f depends: - _openmp_mutex constrains: - - libgomp 15.2.0 15 - - libgcc-ng ==15.2.0=*_15 + - libgcc-ng ==15.2.0=*_18 + - libgomp 15.2.0 18 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 422960 - timestamp: 1764839601296 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_16.conda - sha256: 646c91dbc422fe92a5f8a3a5409c9aac66549f4ce8f8d1cab7c2aa5db789bb69 - md5: 8b216bac0de7a9d60f3ddeba2515545c + size: 423025 + timestamp: 1771378225170 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda + sha256: 1d9c4f35586adb71bcd23e31b68b7f3e4c4ab89914c26bed5f2859290be5560e + md5: 92df6107310b1fff92c4cc84f0de247b depends: - _openmp_mutex constrains: - - libgcc-ng ==15.2.0=*_16 - - libgomp 15.2.0 16 + - libgcc-ng ==15.2.0=*_18 + - libgomp 15.2.0 18 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 402197 - timestamp: 1765258985740 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_16.conda - sha256: 5f07f9317f596a201cc6e095e5fc92621afca64829785e483738d935f8cab361 - md5: 5a68259fac2da8f2ee6f7bfe49c9eb8b + size: 401974 + timestamp: 1771378877463 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda + sha256: e318a711400f536c81123e753d4c797a821021fb38970cebfb3f454126016893 + md5: d5e96b1ed75ca01906b3d2469b4ce493 depends: - - libgcc 15.2.0 he0feb66_16 + - libgcc 15.2.0 he0feb66_18 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 27256 - timestamp: 1765256804124 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_16.conda - sha256: 8a7b01e1ee1c462ad243524d76099e7174ebdd94ff045fe3e9b1e58db196463b - md5: 40d9b534410403c821ff64f00d0adc22 + size: 27526 + timestamp: 1771378224552 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda + sha256: d2c9fad338fd85e4487424865da8e74006ab2e2475bd788f624d7a39b2a72aee + md5: 9063115da5bc35fdc3e1002e69b9ef6e depends: - - libgfortran5 15.2.0 h68bc16d_16 + - libgfortran5 15.2.0 h68bc16d_18 constrains: - - libgfortran-ng ==15.2.0=*_16 + - libgfortran-ng ==15.2.0=*_18 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 27215 - timestamp: 1765256845586 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_15.conda - sha256: 7bb4d51348e8f7c1a565df95f4fc2a2021229d42300aab8366eda0ea1af90587 - md5: a089323fefeeaba2ae60e1ccebf86ddc + size: 27523 + timestamp: 1771378269450 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_18.conda + sha256: fb06c2a2ef06716a0f2a6550f5d13cdd1d89365993068512b7ae3c34e6e665d9 + md5: 34a9f67498721abcfef00178bcf4b190 depends: - - libgfortran5 15.2.0 hd16e46c_15 + - libgfortran5 15.2.0 hd16e46c_18 constrains: - - libgfortran-ng ==15.2.0=*_15 + - libgfortran-ng ==15.2.0=*_18 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 139002 - timestamp: 1764839892631 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_16.conda - sha256: 68a6c1384d209f8654112c4c57c68c540540dd8e09e17dd1facf6cf3467798b5 - md5: 11e09edf0dde4c288508501fe621bab4 + size: 139761 + timestamp: 1771378423828 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda + sha256: 63f89087c3f0c8621c5c89ecceec1e56e5e1c84f65fc9c5feca33a07c570a836 + md5: 26981599908ed2205366e8fc91b37fc6 depends: - - libgfortran5 15.2.0 hdae7583_16 + - libgfortran5 15.2.0 hdae7583_18 constrains: - - libgfortran-ng ==15.2.0=*_16 + - libgfortran-ng ==15.2.0=*_18 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 138630 - timestamp: 1765259217400 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_16.conda - sha256: d0e974ebc937c67ae37f07a28edace978e01dc0f44ee02f29ab8a16004b8148b - md5: 39183d4e0c05609fd65f130633194e37 + size: 138973 + timestamp: 1771379054939 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda + sha256: 539b57cf50ec85509a94ba9949b7e30717839e4d694bc94f30d41c9d34de2d12 + md5: 646855f357199a12f02a87382d429b75 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=15.2.0 @@ -6153,11 +6882,11 @@ packages: license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 2480559 - timestamp: 1765256819588 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_15.conda - sha256: 456385a7d3357d5fdfc8e11bf18dcdf71753c4016c440f92a2486057524dd59a - md5: c2a6149bf7f82774a0118b9efef966dd + size: 2482475 + timestamp: 1771378241063 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda + sha256: ddaf9dcf008c031b10987991aa78643e03c24a534ad420925cbd5851b31faa11 + md5: ca52daf58cea766656266c8771d8be81 depends: - libgcc >=15.2.0 constrains: @@ -6165,11 +6894,11 @@ packages: license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 1061950 - timestamp: 1764839609607 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_16.conda - sha256: 9fb7f4ff219e3fb5decbd0ee90a950f4078c90a86f5d8d61ca608c913062f9b0 - md5: 265a9d03461da24884ecc8eb58396d57 + size: 1062274 + timestamp: 1771378232014 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda + sha256: 91033978ba25e6a60fb86843cf7e1f7dc8ad513f9689f991c9ddabfaf0361e7e + md5: c4a6f7989cffb0544bfd9207b6789971 depends: - libgcc >=15.2.0 constrains: @@ -6177,21 +6906,21 @@ packages: license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 598291 - timestamp: 1765258993165 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_16.conda - sha256: 5b3e5e4e9270ecfcd48f47e3a68f037f5ab0f529ccb223e8e5d5ac75a58fc687 - md5: 26c46f90d0e727e95c6c9498a33a09f3 + size: 598634 + timestamp: 1771378886363 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + sha256: 21337ab58e5e0649d869ab168d4e609b033509de22521de1bfed0c031bfc5110 + md5: 239c5e9546c38a1e884d69effcf4c882 depends: - __glibc >=2.17,<3.0.a0 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 603284 - timestamp: 1765256703881 -- conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.1-default_h4379cf1_1003.conda - sha256: 2d534c09f92966b885acb3f4a838f7055cea043165a03079a539b06c54e20a49 - md5: d1699ce4fe195a9f61264a1c29b87035 + size: 603262 + timestamp: 1771378117851 +- conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda + sha256: 8cdf11333a81085468d9aa536ebb155abd74adc293576f6013fc0c85a7a90da3 + md5: 3b576f6860f838f950c570f4433b086e depends: - libwinpthread >=12.0.0.r4.gg4f2fc60ca - libxml2 @@ -6202,8 +6931,8 @@ packages: license: BSD-3-Clause license_family: BSD purls: [] - size: 2412642 - timestamp: 1765090345611 + size: 2411241 + timestamp: 1765104337762 - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda sha256: 0dcdb1a5f01863ac4e8ba006a8b0dc1a02d2221ec3319b5915a1863254d7efa7 md5: 64571d1dd6cdcfa25d0664a5950fdaa2 @@ -6215,96 +6944,96 @@ packages: purls: [] size: 696926 timestamp: 1754909290005 -- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda - sha256: f2591c0069447bbe28d4d696b7fcb0c5bd0b4ac582769b89addbcf26fb3430d8 - md5: 1a580f7796c7bf6393fddb8bbbde58dc +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda + sha256: 755c55ebab181d678c12e49cced893598f2bab22d582fbbf4d8b83c18be207eb + md5: c7c83eecbb72d88b940c249af56c8b17 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 constrains: - - xz 5.8.1.* + - xz 5.8.2.* license: 0BSD purls: [] - size: 112894 - timestamp: 1749230047870 -- conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda - sha256: 7e22fd1bdb8bf4c2be93de2d4e718db5c548aa082af47a7430eb23192de6bb36 - md5: 8468beea04b9065b9807fc8b9cdc5894 + size: 113207 + timestamp: 1768752626120 +- conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.2-h11316ed_0.conda + sha256: 7ab3c98abd3b5d5ec72faa8d9f5d4b50dcee4970ed05339bc381861199dabb41 + md5: 688a0c3d57fa118b9c97bf7e471ab46c depends: - __osx >=10.13 constrains: - - xz 5.8.1.* + - xz 5.8.2.* license: 0BSD purls: [] - size: 104826 - timestamp: 1749230155443 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda - sha256: 0cb92a9e026e7bd4842f410a5c5c665c89b2eb97794ffddba519a626b8ce7285 - md5: d6df911d4564d77c4374b02552cb17d1 + size: 105482 + timestamp: 1768753411348 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda + sha256: 7bfc7ffb2d6a9629357a70d4eadeadb6f88fa26ebc28f606b1c1e5e5ed99dc7e + md5: 009f0d956d7bfb00de86901d16e486c7 depends: - __osx >=11.0 constrains: - - xz 5.8.1.* + - xz 5.8.2.* license: 0BSD purls: [] - size: 92286 - timestamp: 1749230283517 -- conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda - sha256: 55764956eb9179b98de7cc0e55696f2eff8f7b83fc3ebff5e696ca358bca28cc - md5: c15148b2e18da456f5108ccb5e411446 + size: 92242 + timestamp: 1768752982486 +- conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda + sha256: f25bf293f550c8ed2e0c7145eb404324611cfccff37660869d97abf526eb957c + md5: ba0bfd4c3cf73f299ffe46ff0eaeb8e3 depends: - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 constrains: - - xz 5.8.1.* + - xz 5.8.2.* license: 0BSD purls: [] - size: 104935 - timestamp: 1749230611612 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda - sha256: 3aa92d4074d4063f2a162cd8ecb45dccac93e543e565c01a787e16a43501f7ee - md5: c7e925f37e3b40d893459e625f6a53f1 + size: 106169 + timestamp: 1768752763559 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + sha256: fe171ed5cf5959993d43ff72de7596e8ac2853e9021dec0344e583734f1e0843 + md5: 2c21e66f50753a083cbe6b80f38268fa depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: BSD-2-Clause license_family: BSD purls: [] - size: 91183 - timestamp: 1748393666725 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda - sha256: 98299c73c7a93cd4f5ff8bb7f43cd80389f08b5a27a296d806bdef7841cc9b9e - md5: 18b81186a6adb43f000ad19ed7b70381 + size: 92400 + timestamp: 1769482286018 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-hf3981d6_1.conda + sha256: 1096c740109386607938ab9f09a7e9bca06d86770a284777586d6c378b8fb3fd + md5: ec88ba8a245855935b871a7324373105 depends: - __osx >=10.13 license: BSD-2-Clause license_family: BSD purls: [] - size: 77667 - timestamp: 1748393757154 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda - sha256: 0a1875fc1642324ebd6c4ac864604f3f18f57fbcf558a8264f6ced028a3c75b2 - md5: 85ccccb47823dd9f7a99d2c7f530342f + size: 79899 + timestamp: 1769482558610 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda + sha256: 1089c7f15d5b62c622625ec6700732ece83be8b705da8c6607f4dabb0c4bd6d2 + md5: 57c4be259f5e0b99a5983799a228ae55 depends: - __osx >=11.0 license: BSD-2-Clause license_family: BSD purls: [] - size: 71829 - timestamp: 1748393749336 -- conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda - sha256: fc529fc82c7caf51202cc5cec5bb1c2e8d90edbac6d0a4602c966366efe3c7bf - md5: 74860100b2029e2523cf480804c76b9b + size: 73690 + timestamp: 1769482560514 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda + sha256: 40dcd0b9522a6e0af72a9db0ced619176e7cfdb114855c7a64f278e73f8a7514 + md5: e4a9fc2bba3b022dad998c78856afe47 depends: - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 license: BSD-2-Clause license_family: BSD purls: [] - size: 88657 - timestamp: 1723861474602 + size: 89411 + timestamp: 1769482314283 - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda sha256: a4a7dab8db4dc81c736e9a9b42bdfd97b087816e029e221380511960ac46c690 md5: b499ce4b026493a13774bcf0f4c33849 @@ -6395,9 +7124,9 @@ packages: purls: [] size: 6268795 timestamp: 1763117623665 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_3.conda - sha256: dcc626c7103503d1dfc0371687ad553cb948b8ed0249c2a721147bdeb8db4a73 - md5: a18a7f471c517062ee71b843ef95eb8a +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda + sha256: ebbbc089b70bcde87c4121a083c724330f02a690fb9d7c6cd18c30f1b12504fa + md5: a6f6d3a31bb29e48d37ce65de54e2df0 depends: - __osx >=11.0 - libgfortran @@ -6408,73 +7137,65 @@ packages: license: BSD-3-Clause license_family: BSD purls: [] - size: 4285762 - timestamp: 1761749506256 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.1-h0c1763c_1.conda - sha256: 5ef162b2a1390d1495a759734afe2312a358a58441cf8f378be651903646f3b7 - md5: ad1fd565aff83b543d726382c0ab0af2 + size: 4284132 + timestamp: 1768547079205 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda + sha256: d716847b7deca293d2e49ed1c8ab9e4b9e04b9d780aea49a97c26925b28a7993 + md5: fd893f6a3002a635b5e50ceb9dd2c0f4 depends: - __glibc >=2.17,<3.0.a0 + - icu >=78.2,<79.0a0 - libgcc >=14 - libzlib >=1.3.1,<2.0a0 license: blessing purls: [] - size: 940686 - timestamp: 1766319628770 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.51.1-hb99441e_1.conda - sha256: d62dcce2ecf555864db393fa0fdc0492f9f644c0435f516d0ba4c5f9f934234b - md5: ec7a2bad1b422f3966e4776442adb05c + size: 951405 + timestamp: 1772818874251 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.52.0-h77d7759_0.conda + sha256: f500d1cd50cfcd288d02b8fc3c3b7ecf8de6fec7b86e57ea058def02908e4231 + md5: d553eb96758e038b04027b30fe314b2d depends: - - __osx >=10.13 + - __osx >=11.0 - libzlib >=1.3.1,<2.0a0 license: blessing purls: [] - size: 987257 - timestamp: 1766319833399 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.51.1-h1b79a29_1.conda - sha256: f2c3cbf2ca7d697098964a748fbf19d6e4adcefa23844ec49f0166f1d36af83c - md5: 8c3951797658e10b610929c3e57e9ad9 + size: 996526 + timestamp: 1772819669038 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda + sha256: beb0fd5594d6d7c7cd42c992b6bb4d66cbb39d6c94a8234f15956da99a04306c + md5: f6233a3fddc35a2ec9f617f79d6f3d71 depends: - __osx >=11.0 + - icu >=78.2,<79.0a0 - libzlib >=1.3.1,<2.0a0 license: blessing purls: [] - size: 905861 - timestamp: 1766319901587 -- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.51.1-hf5d6505_1.conda - sha256: d6d86715a1afe11f626b7509935e9d2e14a4946632c0ac474526e20fc6c55f99 - md5: be65be5f758709fc01b01626152e96b0 + size: 918420 + timestamp: 1772819478684 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_0.conda + sha256: 5fccf1e4e4062f8b9a554abf4f9735a98e70f82e2865d0bfdb47b9de94887583 + md5: 8830689d537fda55f990620680934bb1 depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: blessing purls: [] - size: 1292859 - timestamp: 1766319616777 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_16.conda - sha256: 813427918316a00c904723f1dfc3da1bbc1974c5cfe1ed1e704c6f4e0798cbc6 - md5: 68f68355000ec3f1d6f26ea13e8f525f + size: 1297302 + timestamp: 1772818899033 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda + sha256: 78668020064fdaa27e9ab65cd2997e2c837b564ab26ce3bf0e58a2ce1a525c6e + md5: 1b08cd684f34175e4514474793d44bcb depends: - __glibc >=2.17,<3.0.a0 - - libgcc 15.2.0 he0feb66_16 + - libgcc 15.2.0 he0feb66_18 constrains: - - libstdcxx-ng ==15.2.0=*_16 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 5856456 - timestamp: 1765256838573 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_16.conda - sha256: 81f2f246c7533b41c5e0c274172d607829019621c4a0823b5c0b4a8c7028ee84 - md5: 1b3152694d236cf233b76b8c56bf0eae - depends: - - libstdcxx 15.2.0 h934c35e_16 + - libstdcxx-ng ==15.2.0=*_18 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 27300 - timestamp: 1765256885128 + size: 5852330 + timestamp: 1771378262446 - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda sha256: 1a7539cfa7df00714e8943e18de0b06cceef6778e420a5ee3a2a145773758aee md5: db409b7c1720428638e7c0d509d3e1b5 @@ -6538,41 +7259,42 @@ packages: purls: [] size: 100393 timestamp: 1702724383534 -- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.1-h779ef1b_1.conda - sha256: 8b47d5fb00a6ccc0f495d16787ab5f37a434d51965584d6000966252efecf56d - md5: 68dc154b8d415176c07b6995bd3a65d9 +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda + sha256: f905eb7046987c336122121759e7f09144729f6898f48cd06df2a945b86998d8 + md5: 1007e1bfe181a2aee214779ee7f13d30 depends: - - icu >=78.1,<79.0a0 - libiconv >=1.18,<2.0a0 - - liblzma >=5.8.1,<6.0a0 - - libxml2-16 2.15.1 h3cfd58e_1 + - liblzma >=5.8.2,<6.0a0 + - libxml2-16 2.15.2 h692994f_0 - libzlib >=1.3.1,<2.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 + constrains: + - icu <0.0a0 license: MIT license_family: MIT purls: [] - size: 43387 - timestamp: 1766327259710 -- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.1-h3cfd58e_1.conda - sha256: a857e941156b7f462063e34e086d212c6ccbc1521ebdf75b9ed66bd90add57dc - md5: 07d73826fde28e7dbaec52a3297d7d26 + size: 43681 + timestamp: 1772704748950 +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda + sha256: b8c71b3b609c7cfe17f3f2a47c75394d7b30acfb8b34ad7a049ea8757b4d33df + md5: e365238134188e42ed36ee996159d482 depends: - - icu >=78.1,<79.0a0 - libiconv >=1.18,<2.0a0 - - liblzma >=5.8.1,<6.0a0 + - liblzma >=5.8.2,<6.0a0 - libzlib >=1.3.1,<2.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: - - libxml2 2.15.1 + - libxml2 2.15.2 + - icu <0.0a0 license: MIT license_family: MIT purls: [] - size: 518964 - timestamp: 1766327232819 + size: 520078 + timestamp: 1772704728534 - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 md5: edb0dca6bc32e4f4789199455a1dbeb8 @@ -6624,47 +7346,47 @@ packages: purls: [] size: 55476 timestamp: 1727963768015 -- conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-21.1.8-h472b3d1_0.conda - sha256: 2a41885f44cbc1546ff26369924b981efa37a29d20dc5445b64539ba240739e6 - md5: e2d811e9f464dd67398b4ce1f9c7c872 +- conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda + sha256: b63df4e592b3362e7d13e3d1cf8e55ce932ff4f17611c8514b5d36368ec2094c + md5: 3921780bab286f2439ba483c22b90345 depends: - - __osx >=10.13 + - __osx >=11.0 constrains: - - openmp 21.1.8|21.1.8.* + - openmp 22.1.0|22.1.0.* - intel-openmp <0.0a0 license: Apache-2.0 WITH LLVM-exception license_family: APACHE purls: [] - size: 311405 - timestamp: 1765965194247 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-21.1.8-h4a912ad_0.conda - sha256: 56bcd20a0a44ddd143b6ce605700fdf876bcf5c509adc50bf27e76673407a070 - md5: 206ad2df1b5550526e386087bef543c7 + size: 311938 + timestamp: 1772024731611 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda + sha256: 0daeedb3872ad0fdd6f0d7e7165c63488e8a315d7057907434145fba0c1e7b3d + md5: ff0820b5588b20be3b858552ecf8ffae depends: - __osx >=11.0 constrains: - - openmp 21.1.8|21.1.8.* + - openmp 22.1.0|22.1.0.* - intel-openmp <0.0a0 license: Apache-2.0 WITH LLVM-exception license_family: APACHE purls: [] - size: 285974 - timestamp: 1765964756583 -- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.8-h4fa8253_0.conda - sha256: 145c4370abe870f10987efa9fc15a8383f1dab09abbc9ad4ff15a55d45658f7b - md5: 0d8b425ac862bcf17e4b28802c9351cb + size: 285558 + timestamp: 1772028716784 +- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda + sha256: bb55a3736380759d338f87aac68df4fd7d845ae090b94400525f5d21a55eea31 + md5: e5505e0b7d6ef5c19d5c0c1884a2f494 depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: + - openmp 22.1.0|22.1.0.* - intel-openmp <0.0a0 - - openmp 21.1.8|21.1.8.* license: Apache-2.0 WITH LLVM-exception license_family: APACHE purls: [] - size: 347566 - timestamp: 1765964942856 + size: 347404 + timestamp: 1772025050288 - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl name: lmfit version: 1.3.4 @@ -6700,6 +7422,11 @@ packages: - pytest-cov ; extra == 'test' - lmfit[dev,doc,test] ; extra == 'all' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl + name: locket + version: 1.0.0 + sha256: b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*' - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl name: mando version: 0.7.1 @@ -6709,17 +7436,17 @@ packages: - argparse ; python_full_version < '2.7' - funcsigs ; python_full_version < '3.3' - rst2ansi ; extra == 'restructuredtext' -- pypi: https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl name: markdown - version: '3.10' - sha256: b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c + version: 3.10.2 + sha256: e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36 requires_dist: - 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' + - mkdocstrings[python]>=0.28.3 ; extra == 'docs' - mkdocs-gen-files ; extra == 'docs' - mkdocs-section-index ; extra == 'docs' - mkdocs-literate-nav ; extra == 'docs' @@ -6985,19 +7712,19 @@ packages: version: 1.3.4 sha256: 70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307 requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl name: mike - version: 2.1.3 - sha256: d90c64077e84f06272437b464735130d380703a76a5738b152932884c60c062a + version: 2.1.4 + sha256: 39933e992e155dd70f2297e749a0ed78d8fd7942bc33a3666195d177758a280e requires_dist: - - importlib-metadata - - importlib-resources - jinja2>=2.7 - mkdocs>=1.0 - pyparsing>=3.0 - pyyaml>=5.1 - pyyaml-env-tag - verspec + - importlib-metadata ; python_full_version < '3.10' + - importlib-resources ; python_full_version < '3.10' - coverage ; extra == 'dev' - flake8-quotes ; extra == 'dev' - flake8>=3.0 ; extra == 'dev' @@ -7049,25 +7776,25 @@ packages: - 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 +- pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl name: mkdocs-autorefs - version: 1.2.0 - sha256: d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f + version: 1.4.4 + sha256: 834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089 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 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl name: mkdocs-get-deps - version: 0.2.0 - sha256: 2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134 + version: 0.2.1 + sha256: 07d6076298715dfcb8232e7dec083d09015b4e65482ce7f6743cb07cd1da847e 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' + requires_python: '>=3.9' - 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 @@ -7088,10 +7815,10 @@ packages: - mkdocs - pyyaml requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl name: mkdocs-material - version: 9.7.1 - sha256: 3f6100937d7d731f87f1e3e3b021c97f7239666b9ba1151ab476cabb96c60d5c + version: 9.7.4 + sha256: 6549ad95e4d130ed5099759dfa76ea34c593eefdb9c18c97273605518e99cfbf requires_dist: - babel>=2.10 - backrefs>=5.7.post1 @@ -7104,13 +7831,13 @@ packages: - pygments>=2.16 - pymdown-extensions>=10.2 - requests>=2.30 - - 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' + - mkdocs-git-committers-plugin-2>=1.1 ; extra == 'git' + - mkdocs-git-revision-date-localized-plugin>=1.2.4 ; extra == 'git' + - cairosvg>=2.6 ; extra == 'imaging' + - pillow>=10.2 ; 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 @@ -7124,34 +7851,31 @@ packages: requires_dist: - mkdocs requires_python: '>=3.5' -- pypi: https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl name: mkdocstrings - version: 0.27.0 - sha256: 6ceaa7ea830770959b55a16203ac63da24badd71325b96af950e59fd37366332 + version: 1.0.3 + sha256: 0d66d18430c2201dc7fe85134277382baaa15e6b30979f3f3bdbabd6dbdb6046 requires_dist: - - click>=7.0 - - jinja2>=2.11.1 + - jinja2>=3.1 - markdown>=3.6 - markupsafe>=1.1 - - mkdocs>=1.4 - - mkdocs-autorefs>=1.2 - - platformdirs>=2.2 + - mkdocs>=1.6 + - mkdocs-autorefs>=1.4 - 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 + - mkdocstrings-python>=1.16.2 ; extra == 'python' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl name: mkdocstrings-python - version: 1.13.0 - sha256: b88bbb207bab4086434743849f8e796788b373bd32e7bfefbf8560ac45d88f97 + version: 2.0.3 + sha256: 0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12 requires_dist: - - mkdocstrings>=0.26 - - mkdocs-autorefs>=1.2 - - griffe>=0.49 - requires_python: '>=3.9' + - mkdocstrings>=0.30 + - mkdocs-autorefs>=1.4 + - griffelib>=2.0 + - typing-extensions>=4.0 ; python_full_version < '3.11' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda sha256: b2b4c84b95210760e4d12319416c60ab66e03674ccdcbd14aeb59f82ebb1318d md5: fd05d1e894497b012d05a804232254ed @@ -7173,6 +7897,15 @@ packages: requires_dist: - jinja2 - matplotlib +- pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl + name: mpltoolbox + version: 26.2.0 + sha256: cd2668db4216fc4d7c2ba37974961aa61445f1517527b645b6082930e35ba7f0 + requires_dist: + - matplotlib + - ipympl ; extra == 'test' + - pytest>=8.0 ; extra == 'test' + requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl name: mpmath version: 1.3.0 @@ -7298,68 +8031,68 @@ packages: - tomli-w ; extra == 'toml' - pyyaml ; extra == 'yaml' requires_python: '>=3.9' -- 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/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: multidict - version: 6.7.0 - sha256: 14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721 + version: 6.7.1 + sha256: 439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl name: multidict - version: 6.7.0 - sha256: 95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81 + version: 6.7.1 + sha256: 935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl name: multidict - version: 6.7.0 - sha256: 30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390 + version: 6.7.1 + sha256: 844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- 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/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: multidict - version: 6.7.0 - sha256: 4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6 + version: 6.7.1 + sha256: 9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- 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/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl name: multidict - version: 6.7.0 - sha256: 7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159 + version: 6.7.1 + sha256: 960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- 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/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl name: multidict - version: 6.7.0 - sha256: 3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd + version: 6.7.1 + sha256: 84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- 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/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl name: multidict - version: 6.7.0 - sha256: 9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32 + version: 6.7.1 + sha256: bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- 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/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl name: multidict - version: 6.7.0 - sha256: 9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca + version: 6.7.1 + sha256: f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/3d/2e/cf2ffeb386ac3763526151163ad7da9f1b586aac96d2b4f7de1eaebf0c61/narwhals-2.15.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl name: narwhals - version: 2.15.0 - sha256: cbfe21ca19d260d9fd67f995ec75c44592d1f106933b03ddd375df7ac841f9d6 + version: 2.17.0 + sha256: 2ac5307b7c2b275a7d66eeda906b8605e3d7a760951e188dcfff86e8ebe083dd requires_dist: - - cudf>=24.10.0 ; extra == 'cudf' + - cudf-cu12>=24.10.0 ; extra == 'cudf' - dask[dataframe]>=2024.8 ; extra == 'dask' - duckdb>=1.1 ; extra == 'duckdb' - ibis-framework>=6.0.0 ; extra == 'ibis' @@ -7372,6 +8105,8 @@ packages: - pyarrow>=13.0.0 ; extra == 'pyarrow' - pyspark>=3.5.0 ; extra == 'pyspark' - pyspark[connect]>=3.5.0 ; extra == 'pyspark-connect' + - duckdb>=1.1 ; extra == 'sql' + - sqlparse ; extra == 'sql' - sqlframe>=3.22.0,!=3.39.3 ; extra == 'sqlframe' requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl @@ -7412,10 +8147,10 @@ packages: - testpath ; extra == 'test' - xmltodict ; extra == 'test' requires_python: '>=3.10.0' -- pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl name: nbconvert - version: 7.16.6 - sha256: 1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b + version: 7.17.0 + sha256: 4f99a63b337b9a23504347afdab24a11faa7d86b405e5c8f9881cd313336d518 requires_dist: - beautifulsoup4 - bleach[css]!=5.0.0 @@ -7433,6 +8168,7 @@ packages: - pygments>=2.4.1 - traitlets>=5.1 - flaky ; extra == 'all' + - intersphinx-registry ; extra == 'all' - ipykernel ; extra == 'all' - ipython ; extra == 'all' - ipywidgets>=7.5 ; extra == 'all' @@ -7442,15 +8178,16 @@ packages: - pydata-sphinx-theme ; extra == 'all' - pyqtwebengine>=5.15 ; extra == 'all' - pytest>=7 ; extra == 'all' - - sphinx==5.0.2 ; extra == 'all' + - sphinx>=5.0.2 ; extra == 'all' - sphinxcontrib-spelling ; extra == 'all' - tornado>=6.1 ; extra == 'all' + - intersphinx-registry ; extra == 'docs' - 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' + - sphinx>=5.0.2 ; extra == 'docs' - sphinxcontrib-spelling ; extra == 'docs' - pyqtwebengine>=5.15 ; extra == 'qtpdf' - pyqtwebengine>=5.15 ; extra == 'qtpng' @@ -7460,7 +8197,7 @@ packages: - ipywidgets>=7.5 ; extra == 'test' - pytest>=7 ; extra == 'test' - playwright ; extra == 'webpdf' - requires_python: '>=3.8' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl name: nbformat version: 5.10.4 @@ -7510,12 +8247,71 @@ packages: - pyupgrade ; extra == 'toolchain' - ruff ; extra == 'toolchain' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/12/e5/838eb1004fb9b6f0383c47c0d902eb698fda052e8e64ca48c70a2144a48c/nbstripout-0.8.2-py2.py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl name: nbstripout - version: 0.8.2 - sha256: 5f06f9138cb64daed3e91c5359ff0fff85bab4d0db7d72723be1da6f51b890ae + version: 0.9.1 + sha256: ca027ee45742ee77e4f8e9080254f9a707f1161ba11367b82fdf4a29892c759e requires_dist: - nbformat + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/fc/39/dfdef4cfbfcf7c80cd144b1b2e262c2e785f18b152eeebe14ecd70ee7455/ncrystal-4.2.12-py3-none-any.whl + name: ncrystal + version: 4.2.12 + sha256: 45c414c786cab7e64cf6c69c89e8d49da08f83833b7e27f382366bd5fe7b8582 + requires_dist: + - ncrystal-core==4.2.12 + - ncrystal-python==4.2.12 + - spglib>=2.1.0 ; extra == 'composer' + - ase>=3.23.0 ; extra == 'cif' + - gemmi>=0.6.1 ; extra == 'cif' + - spglib>=2.1.0 ; extra == 'cif' + - endf-parserpy>=0.14.3 ; extra == 'endf' + - matplotlib>=3.6.0 ; extra == 'plot' + - ase>=3.23.0 ; extra == 'all' + - endf-parserpy>=0.14.3 ; extra == 'all' + - gemmi>=0.6.1 ; extra == 'all' + - matplotlib>=3.6.0 ; extra == 'all' + - spglib>=2.1.0 ; extra == 'all' + - pyyaml>=6.0.0 ; extra == 'devel' + - ase>=3.23.0 ; extra == 'devel' + - cppcheck ; extra == 'devel' + - endf-parserpy>=0.14.3 ; extra == 'devel' + - gemmi>=0.6.1 ; extra == 'devel' + - matplotlib>=3.6.0 ; extra == 'devel' + - mpmath>=1.3.0 ; extra == 'devel' + - numpy>=1.22 ; extra == 'devel' + - pybind11>=2.11.0 ; extra == 'devel' + - ruff>=0.8.1 ; extra == 'devel' + - simple-build-system>=1.6.0 ; extra == 'devel' + - spglib>=2.1.0 ; extra == 'devel' + - tomli>=2.0.0 ; python_full_version < '3.11' and extra == 'devel' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/55/8d/2b26572e909238bb114d50fb0d1b6b54eb6dafa2d83a7264f18146796b0d/ncrystal_core-4.2.12-py3-none-macosx_11_0_arm64.whl + name: ncrystal-core + version: 4.2.12 + sha256: d47a1dd3c3348bdc0b7cb8df19ad4c51eeb4befce1279f56a67ca1d773e5d84c + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/7b/51/e13a37a8d924feefb444d7eb42094750ba1bba756cbb8c1f9a523414c4fb/ncrystal_core-4.2.12-py3-none-win_amd64.whl + name: ncrystal-core + version: 4.2.12 + sha256: a04c835c20f5ef5e1869282e921e0eae7665d99c48d27dbe4cb78c0cda0a52cc + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/ab/ee/0d9d9218d2081e56828194f83d0eac6292b7182708fd07a62756c66f7194/ncrystal_core-4.2.12-py3-none-macosx_10_9_x86_64.whl + name: ncrystal-core + version: 4.2.12 + sha256: 8ccf39338f5745334e88036f3df885008294ad1228e5b3ffc6ff4bfd1b01dd99 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/e3/8b/1f02771d91ceafec996cef7f92f6a24010fedc47fd9404f8e11772d8501c/ncrystal_core-4.2.12-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: ncrystal-core + version: 4.2.12 + sha256: dd622af09c422973c7effc5bbb6f64d7ad5dc020010564362f6c9eebf99f00da + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/78/98/20fdd6825d1876fc54bb098509e736073d20f569b033f2f23bf74aa1df9c/ncrystal_python-4.2.12-py3-none-any.whl + name: ncrystal-python + version: 4.2.12 + sha256: 60bb715d9d74bae031de25858d33665a4c407a5ecaa4c5626cb42bcf182400bb + requires_dist: + - numpy>=1.22 requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 @@ -7550,92 +8346,135 @@ packages: version: 1.6.0 sha256: 87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c requires_python: '>=3.5' +- pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl + name: networkx + version: 3.6.1 + sha256: d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762 + requires_dist: + - asv ; extra == 'benchmarking' + - virtualenv ; extra == 'benchmarking' + - numpy>=1.25 ; extra == 'default' + - scipy>=1.11.2 ; extra == 'default' + - matplotlib>=3.8 ; extra == 'default' + - pandas>=2.0 ; extra == 'default' + - pre-commit>=4.1 ; extra == 'developer' + - mypy>=1.15 ; extra == 'developer' + - sphinx>=8.0 ; extra == 'doc' + - pydata-sphinx-theme>=0.16 ; extra == 'doc' + - sphinx-gallery>=0.18 ; extra == 'doc' + - numpydoc>=1.8.0 ; extra == 'doc' + - pillow>=10 ; extra == 'doc' + - texext>=0.6.7 ; extra == 'doc' + - myst-nb>=1.1 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - osmnx>=2.0.0 ; extra == 'example' + - momepy>=0.7.2 ; extra == 'example' + - contextily>=1.6 ; extra == 'example' + - seaborn>=0.13 ; extra == 'example' + - cairocffi>=1.7 ; extra == 'example' + - igraph>=0.11 ; extra == 'example' + - scikit-learn>=1.5 ; extra == 'example' + - iplotx>=0.9.0 ; extra == 'example' + - lxml>=4.6 ; extra == 'extra' + - pygraphviz>=1.14 ; extra == 'extra' + - pydot>=3.0.1 ; extra == 'extra' + - sympy>=1.10 ; extra == 'extra' + - build>=0.10 ; extra == 'release' + - twine>=4.0 ; extra == 'release' + - wheel>=0.40 ; extra == 'release' + - changelist==0.5 ; extra == 'release' + - pytest>=7.2 ; extra == 'test' + - pytest-cov>=4.0 ; extra == 'test' + - pytest-xdist>=3.0 ; extra == 'test' + - pytest-mpl ; extra == 'test-extras' + - pytest-randomly ; extra == 'test-extras' + requires_python: '>=3.11,!=3.14.1' - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl name: nodeenv version: 1.10.0 sha256: 5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*' -- conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.2.1-he2c55a7_1.conda - sha256: 6516f99fe400181ebe27cba29180ca0c7425c15d7392f74220a028ad0e0064a2 - md5: d8005b3a90515c952b51026f6b7d005d +- conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.7.0-he4ff34a_0.conda + sha256: 4791285a34195615e22a94dc62cbd43181548b34eb34e6cb1dcf8f469476a32e + md5: ba562095149fde99c700365d90e6d632 depends: - __glibc >=2.28,<3.0.a0 - libstdcxx >=14 - libgcc >=14 - - zstd >=1.5.7,<1.6.0a0 - - c-ares >=1.34.6,<2.0a0 - - libuv >=1.51.0,<2.0a0 - - libsqlite >=3.51.1,<4.0a0 - libnghttp2 >=1.67.0,<2.0a0 - - openssl >=3.5.4,<4.0a0 - - libabseil >=20250512.1,<20250513.0a0 - - libabseil * cxx17* - - libzlib >=1.3.1,<2.0a0 + - c-ares >=1.34.6,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 - libbrotlicommon >=1.2.0,<1.3.0a0 - libbrotlienc >=1.2.0,<1.3.0a0 - libbrotlidec >=1.2.0,<1.3.0a0 - - icu >=75.1,<76.0a0 + - libuv >=1.51.0,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - libabseil >=20260107.1,<20260108.0a0 + - libabseil * cxx17* + - icu >=78.2,<79.0a0 + - libzlib >=1.3.1,<2.0a0 + - libsqlite >=3.51.2,<4.0a0 license: MIT license_family: MIT purls: [] - size: 17246248 - timestamp: 1765444698486 -- conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.2.1-h5523da6_1.conda - sha256: 25ade898cb9e6f26622cc563dab89810f59e898e37ec4ffabd079f9f9a068998 - md5: 18ce8107e5d71b65aaa585c238a9e90d + size: 18875002 + timestamp: 1772300115686 +- conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.7.0-hf6efa0e_0.conda + sha256: a23a3b536f1ccd3c10a2cbdf8c638b601c51f6e9bbf1e6c708610cf5b1df3d0f + md5: 448185eb18358e4b5fe2a157cc7e415f depends: - - __osx >=11.0 - libcxx >=19 - - libsqlite >=3.51.1,<4.0a0 - - libabseil >=20250512.1,<20250513.0a0 - - libabseil * cxx17* - - zstd >=1.5.7,<1.6.0a0 + - __osx >=10.15 + - openssl >=3.5.5,<4.0a0 + - c-ares >=1.34.6,<2.0a0 + - libuv >=1.51.0,<2.0a0 - libbrotlicommon >=1.2.0,<1.3.0a0 - libbrotlienc >=1.2.0,<1.3.0a0 - libbrotlidec >=1.2.0,<1.3.0a0 + - libabseil >=20260107.1,<20260108.0a0 + - libabseil * cxx17* - libnghttp2 >=1.67.0,<2.0a0 - - openssl >=3.5.4,<4.0a0 - - libuv >=1.51.0,<2.0a0 - - c-ares >=1.34.6,<2.0a0 - - icu >=75.1,<76.0a0 + - icu >=78.2,<79.0a0 + - libsqlite >=3.51.2,<4.0a0 - libzlib >=1.3.1,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 license: MIT license_family: MIT purls: [] - size: 16923801 - timestamp: 1765444650323 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.2.1-h5230ea7_1.conda - sha256: acb4a33a096fa89d0ec0eea5d5f19988594d4e5c8d482ac60d2b0365d16dd984 - md5: 0b6dfe96bcfb469afe82885b3fecbd56 + size: 18342335 + timestamp: 1772299132495 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.7.0-hbfc8e16_0.conda + sha256: 6398a45d2bb943585540d98a9b1b3bed1fb48fabd327b56eba2ece00e3dd202f + md5: 22a4a51f30590f274bb3f831d911c09a depends: - __osx >=11.0 - libcxx >=19 - - libsqlite >=3.51.1,<4.0a0 + - libsqlite >=3.51.2,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 - libbrotlicommon >=1.2.0,<1.3.0a0 - libbrotlienc >=1.2.0,<1.3.0a0 - libbrotlidec >=1.2.0,<1.3.0a0 - - openssl >=3.5.4,<4.0a0 - - c-ares >=1.34.6,<2.0a0 - - icu >=75.1,<76.0a0 - - zstd >=1.5.7,<1.6.0a0 - - libabseil >=20250512.1,<20250513.0a0 + - libuv >=1.51.0,<2.0a0 + - libabseil >=20260107.1,<20260108.0a0 - libabseil * cxx17* + - icu >=78.2,<79.0a0 + - c-ares >=1.34.6,<2.0a0 + - openssl >=3.5.5,<4.0a0 - libnghttp2 >=1.67.0,<2.0a0 - - libuv >=1.51.0,<2.0a0 - libzlib >=1.3.1,<2.0a0 license: MIT license_family: MIT purls: [] - size: 16202237 - timestamp: 1765482731453 -- conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.2.1-he453025_1.conda - sha256: 9742d28cf4a171dc9898bfb3c8512858f1ed46aa3cbc26d8839003d879564beb - md5: 461d47b472740c68ec0771c8b759868b + size: 17351648 + timestamp: 1772299122966 +- conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.7.0-h80d1838_0.conda + sha256: 0ea0ddad32366396d1beda7ce93ddd3d9f705286c1a4f99f05ec0049183c1e97 + md5: 61b62d3be12c9edbe34f202d51891927 license: MIT license_family: MIT purls: [] - size: 30449097 - timestamp: 1765444649904 + size: 31254428 + timestamp: 1772299132945 - 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 @@ -7647,49 +8486,49 @@ packages: - pytest-jupyter ; extra == 'test' - pytest-tornasync ; extra == 'test' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/2b/09/3c4abbc1dcd8010bf1a611d174c7aa689fc505585ec806111b4406f6f1b1/numpy-2.4.3-cp311-cp311-macosx_14_0_x86_64.whl name: numpy - version: 2.3.5 - sha256: 00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b + version: 2.4.3 + sha256: 23b46bb6d8ecb68b58c09944483c135ae5f0e9b8d8858ece5e4ead783771d2a9 requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl name: numpy - version: 2.3.5 - sha256: acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218 + version: 2.4.3 + sha256: bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/76/1d/edccf27adedb754db7c4511d5eac8b83f004ae948fe2d3509e8b78097d4c/numpy-2.4.3-cp311-cp311-win_amd64.whl name: numpy - version: 2.3.5 - sha256: de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10 + version: 2.4.3 + sha256: 77e76d932c49a75617c6d13464e41203cd410956614d0a0e999b25e9e8d27eec requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/78/51/9f5d7a41f0b51649ddf2f2320595e15e122a40610b233d51928dd6c92353/numpy-2.4.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: numpy - version: 2.3.5 - sha256: 8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4 + version: 2.4.3 + sha256: 715de7f82e192e8cae5a507a347d97ad17598f8e026152ca97233e3666daaa71 requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: numpy - version: 2.3.5 - sha256: aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188 + version: 2.4.3 + sha256: d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl name: numpy - version: 2.3.5 - sha256: a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c + version: 2.4.3 + sha256: 7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl name: numpy - version: 2.3.5 - sha256: d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff + version: 2.4.3 + sha256: 0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/ef/27/d26c85cbcd86b26e4f125b0668e7a7c0542d19dd7d23ee12e87b550e95b5/numpy-2.4.3-cp311-cp311-macosx_14_0_arm64.whl name: numpy - version: 2.3.5 - sha256: 11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017 + version: 2.4.3 + sha256: a1988292870c7cb9d0ebb4cc96b4d447513a9644801de54606dc7aabf2b7d920 requires_python: '>=3.11' -- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda - sha256: a47271202f4518a484956968335b2521409c8173e123ab381e775c358c67fe6d - md5: 9ee58d5c534af06558933af3c845a780 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda + sha256: 44c877f8af015332a5d12f5ff0fb20ca32f896526a7d0cdb30c769df1144fb5c + md5: f61eb8cd60ff9057122a3d338b99c00f depends: - __glibc >=2.17,<3.0.a0 - ca-certificates @@ -7697,33 +8536,33 @@ packages: license: Apache-2.0 license_family: Apache purls: [] - size: 3165399 - timestamp: 1762839186699 -- conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.0-h230baf5_0.conda - sha256: 36fe9fb316be22fcfb46d5fa3e2e85eec5ef84f908b7745f68f768917235b2d5 - md5: 3f50cdf9a97d0280655758b735781096 + size: 3164551 + timestamp: 1769555830639 +- conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda + sha256: e02e5639b0e4d6d4fcf0f3b082642844fb5a37316f5b0a1126c6271347462e90 + md5: 30bb8d08b99b9a7600d39efb3559fff0 depends: - __osx >=10.13 - ca-certificates license: Apache-2.0 license_family: Apache purls: [] - size: 2778996 - timestamp: 1762840724922 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.0-h5503f6c_0.conda - sha256: ebe93dafcc09e099782fe3907485d4e1671296bc14f8c383cb6f3dfebb773988 - md5: b34dc4172653c13dcf453862f251af2b + size: 2777136 + timestamp: 1769557662405 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda + sha256: 361f5c5e60052abc12bdd1b50d7a1a43e6a6653aab99a2263bf2288d709dcf67 + md5: f4f6ad63f98f64191c3e77c5f5f29d76 depends: - __osx >=11.0 - ca-certificates license: Apache-2.0 license_family: Apache purls: [] - size: 3108371 - timestamp: 1762839712322 -- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.0-h725018a_0.conda - sha256: 6d72d6f766293d4f2aa60c28c244c8efed6946c430814175f959ffe8cab899b3 - md5: 84f8fb4afd1157f59098f618cd2437e4 + size: 3104268 + timestamp: 1769556384749 +- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda + sha256: 53a5ad2e5553b8157a91bb8aa375f78c5958f77cb80e9d2ce59471ea8e5c0bd6 + md5: eb585509b815415bc964b2c7e11c7eb3 depends: - ca-certificates - ucrt >=10.0.20348.0 @@ -7732,8 +8571,8 @@ packages: license: Apache-2.0 license_family: Apache purls: [] - size: 9440812 - timestamp: 1762841722179 + size: 9343023 + timestamp: 1769557547888 - pypi: https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl name: overrides version: 7.7.0 @@ -7741,10 +8580,10 @@ packages: 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 +- pypi: https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl name: packaging - version: '25.0' - sha256: 29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 + version: '26.0' + sha256: b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529 requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl name: paginate @@ -7754,754 +8593,758 @@ packages: - 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 +- pypi: https://files.pythonhosted.org/packages/0b/48/aad6ec4f8d007534c091e9a7172b3ec1b1ee6d99a9cbb936b5eab6c6cf58/pandas-3.0.1-cp313-cp313-macosx_10_13_x86_64.whl name: pandas - version: 2.3.3 - sha256: 318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac + version: 3.0.1 + sha256: 5272627187b5d9c20e55d27caf5f2cd23e286aba25cadf73c8590e432e2b7262 requires_dist: - - numpy>=1.22.4 ; python_full_version < '3.11' - - numpy>=1.23.2 ; python_full_version == '3.11.*' - - numpy>=1.26.0 ; python_full_version >= '3.12' + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' - python-dateutil>=2.8.2 - - pytz>=2020.1 - - tzdata>=2022.7 - - hypothesis>=6.46.1 ; extra == 'test' - - pytest>=7.3.2 ; extra == 'test' - - pytest-xdist>=2.2.0 ; extra == 'test' - - pyarrow>=10.0.1 ; extra == 'pyarrow' - - bottleneck>=1.3.6 ; extra == 'performance' - - numba>=0.56.4 ; extra == 'performance' - - numexpr>=2.8.4 ; extra == 'performance' - - scipy>=1.10.0 ; extra == 'computation' - - xarray>=2022.12.0 ; extra == 'computation' - - fsspec>=2022.11.0 ; extra == 'fss' - - s3fs>=2022.11.0 ; extra == 'aws' - - gcsfs>=2022.11.0 ; extra == 'gcp' - - pandas-gbq>=0.19.0 ; extra == 'gcp' + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' - odfpy>=1.4.1 ; extra == 'excel' - - openpyxl>=3.1.0 ; extra == 'excel' - - python-calamine>=0.1.7 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' - pyxlsb>=1.0.10 ; extra == 'excel' - xlrd>=2.0.1 ; extra == 'excel' - - xlsxwriter>=3.0.5 ; extra == 'excel' - - pyarrow>=10.0.1 ; extra == 'parquet' - - pyarrow>=10.0.1 ; extra == 'feather' - - tables>=3.8.0 ; extra == 'hdf5' - - pyreadstat>=1.2.0 ; extra == 'spss' - - sqlalchemy>=2.0.0 ; extra == 'postgresql' - - psycopg2>=2.9.6 ; extra == 'postgresql' - - adbc-driver-postgresql>=0.8.0 ; extra == 'postgresql' - - sqlalchemy>=2.0.0 ; extra == 'mysql' - - pymysql>=1.0.2 ; extra == 'mysql' - - sqlalchemy>=2.0.0 ; extra == 'sql-other' - - adbc-driver-postgresql>=0.8.0 ; extra == 'sql-other' - - adbc-driver-sqlite>=0.8.0 ; extra == 'sql-other' - - beautifulsoup4>=4.11.2 ; extra == 'html' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' - html5lib>=1.1 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'xml' - - matplotlib>=3.6.3 ; extra == 'plot' - - jinja2>=3.1.2 ; extra == 'output-formatting' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' - tabulate>=0.9.0 ; extra == 'output-formatting' - pyqt5>=5.15.9 ; extra == 'clipboard' - - qtpy>=2.3.0 ; extra == 'clipboard' - - zstandard>=0.19.0 ; extra == 'compression' - - dataframe-api-compat>=0.1.7 ; extra == 'consortium-standard' - - adbc-driver-postgresql>=0.8.0 ; extra == 'all' - - adbc-driver-sqlite>=0.8.0 ; extra == 'all' - - beautifulsoup4>=4.11.2 ; extra == 'all' - - bottleneck>=1.3.6 ; extra == 'all' - - dataframe-api-compat>=0.1.7 ; extra == 'all' - - fastparquet>=2022.12.0 ; extra == 'all' - - fsspec>=2022.11.0 ; extra == 'all' - - gcsfs>=2022.11.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' - html5lib>=1.1 ; extra == 'all' - - hypothesis>=6.46.1 ; extra == 'all' - - jinja2>=3.1.2 ; extra == 'all' - - lxml>=4.9.2 ; extra == 'all' - - matplotlib>=3.6.3 ; extra == 'all' - - numba>=0.56.4 ; extra == 'all' - - numexpr>=2.8.4 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' - odfpy>=1.4.1 ; extra == 'all' - - openpyxl>=3.1.0 ; extra == 'all' - - pandas-gbq>=0.19.0 ; extra == 'all' - - psycopg2>=2.9.6 ; extra == 'all' - - pyarrow>=10.0.1 ; extra == 'all' - - pymysql>=1.0.2 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' - pyqt5>=5.15.9 ; extra == 'all' - - pyreadstat>=1.2.0 ; extra == 'all' - - pytest>=7.3.2 ; extra == 'all' - - pytest-xdist>=2.2.0 ; extra == 'all' - - python-calamine>=0.1.7 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' - pyxlsb>=1.0.10 ; extra == 'all' - - qtpy>=2.3.0 ; extra == 'all' - - scipy>=1.10.0 ; extra == 'all' - - s3fs>=2022.11.0 ; extra == 'all' - - sqlalchemy>=2.0.0 ; extra == 'all' - - tables>=3.8.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' - tabulate>=0.9.0 ; extra == 'all' - - xarray>=2022.12.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' - xlrd>=2.0.1 ; extra == 'all' - - xlsxwriter>=3.0.5 ; extra == 'all' - - zstandard>=0.19.0 ; extra == 'all' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/1f/67/af63f83cd6ca603a00fe8530c10a60f0879265b8be00b5930e8e78c5b30b/pandas-3.0.1-cp311-cp311-win_amd64.whl name: pandas - version: 2.3.3 - sha256: bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8 + version: 3.0.1 + sha256: 84f0904a69e7365f79a0c77d3cdfccbfb05bf87847e3a51a41e1426b0edb9c79 requires_dist: - - numpy>=1.22.4 ; python_full_version < '3.11' - - numpy>=1.23.2 ; python_full_version == '3.11.*' - - numpy>=1.26.0 ; python_full_version >= '3.12' + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' - python-dateutil>=2.8.2 - - pytz>=2020.1 - - tzdata>=2022.7 - - hypothesis>=6.46.1 ; extra == 'test' - - pytest>=7.3.2 ; extra == 'test' - - pytest-xdist>=2.2.0 ; extra == 'test' - - pyarrow>=10.0.1 ; extra == 'pyarrow' - - bottleneck>=1.3.6 ; extra == 'performance' - - numba>=0.56.4 ; extra == 'performance' - - numexpr>=2.8.4 ; extra == 'performance' - - scipy>=1.10.0 ; extra == 'computation' - - xarray>=2022.12.0 ; extra == 'computation' - - fsspec>=2022.11.0 ; extra == 'fss' - - s3fs>=2022.11.0 ; extra == 'aws' - - gcsfs>=2022.11.0 ; extra == 'gcp' - - pandas-gbq>=0.19.0 ; extra == 'gcp' + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' - odfpy>=1.4.1 ; extra == 'excel' - - openpyxl>=3.1.0 ; extra == 'excel' - - python-calamine>=0.1.7 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' - pyxlsb>=1.0.10 ; extra == 'excel' - xlrd>=2.0.1 ; extra == 'excel' - - xlsxwriter>=3.0.5 ; extra == 'excel' - - pyarrow>=10.0.1 ; extra == 'parquet' - - pyarrow>=10.0.1 ; extra == 'feather' - - tables>=3.8.0 ; extra == 'hdf5' - - pyreadstat>=1.2.0 ; extra == 'spss' - - sqlalchemy>=2.0.0 ; extra == 'postgresql' - - psycopg2>=2.9.6 ; extra == 'postgresql' - - adbc-driver-postgresql>=0.8.0 ; extra == 'postgresql' - - sqlalchemy>=2.0.0 ; extra == 'mysql' - - pymysql>=1.0.2 ; extra == 'mysql' - - sqlalchemy>=2.0.0 ; extra == 'sql-other' - - adbc-driver-postgresql>=0.8.0 ; extra == 'sql-other' - - adbc-driver-sqlite>=0.8.0 ; extra == 'sql-other' - - beautifulsoup4>=4.11.2 ; extra == 'html' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' - html5lib>=1.1 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'xml' - - matplotlib>=3.6.3 ; extra == 'plot' - - jinja2>=3.1.2 ; extra == 'output-formatting' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' - tabulate>=0.9.0 ; extra == 'output-formatting' - pyqt5>=5.15.9 ; extra == 'clipboard' - - qtpy>=2.3.0 ; extra == 'clipboard' - - zstandard>=0.19.0 ; extra == 'compression' - - dataframe-api-compat>=0.1.7 ; extra == 'consortium-standard' - - adbc-driver-postgresql>=0.8.0 ; extra == 'all' - - adbc-driver-sqlite>=0.8.0 ; extra == 'all' - - beautifulsoup4>=4.11.2 ; extra == 'all' - - bottleneck>=1.3.6 ; extra == 'all' - - dataframe-api-compat>=0.1.7 ; extra == 'all' - - fastparquet>=2022.12.0 ; extra == 'all' - - fsspec>=2022.11.0 ; extra == 'all' - - gcsfs>=2022.11.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' - html5lib>=1.1 ; extra == 'all' - - hypothesis>=6.46.1 ; extra == 'all' - - jinja2>=3.1.2 ; extra == 'all' - - lxml>=4.9.2 ; extra == 'all' - - matplotlib>=3.6.3 ; extra == 'all' - - numba>=0.56.4 ; extra == 'all' - - numexpr>=2.8.4 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' - odfpy>=1.4.1 ; extra == 'all' - - openpyxl>=3.1.0 ; extra == 'all' - - pandas-gbq>=0.19.0 ; extra == 'all' - - psycopg2>=2.9.6 ; extra == 'all' - - pyarrow>=10.0.1 ; extra == 'all' - - pymysql>=1.0.2 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' - pyqt5>=5.15.9 ; extra == 'all' - - pyreadstat>=1.2.0 ; extra == 'all' - - pytest>=7.3.2 ; extra == 'all' - - pytest-xdist>=2.2.0 ; extra == 'all' - - python-calamine>=0.1.7 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' - pyxlsb>=1.0.10 ; extra == 'all' - - qtpy>=2.3.0 ; extra == 'all' - - scipy>=1.10.0 ; extra == 'all' - - s3fs>=2022.11.0 ; extra == 'all' - - sqlalchemy>=2.0.0 ; extra == 'all' - - tables>=3.8.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' - tabulate>=0.9.0 ; extra == 'all' - - xarray>=2022.12.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' - xlrd>=2.0.1 ; extra == 'all' - - xlsxwriter>=3.0.5 ; extra == 'all' - - zstandard>=0.19.0 ; extra == 'all' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/2e/7c/870c7e7daec2a6c7ff2ac9e33b23317230d4e4e954b35112759ea4a924a7/pandas-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl name: pandas - version: 2.3.3 - sha256: f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee + version: 3.0.1 + sha256: 830994d7e1f31dd7e790045235605ab61cff6c94defc774547e8b7fdfbff3dc7 requires_dist: - - numpy>=1.22.4 ; python_full_version < '3.11' - - numpy>=1.23.2 ; python_full_version == '3.11.*' - - numpy>=1.26.0 ; python_full_version >= '3.12' + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' - python-dateutil>=2.8.2 - - pytz>=2020.1 - - tzdata>=2022.7 - - hypothesis>=6.46.1 ; extra == 'test' - - pytest>=7.3.2 ; extra == 'test' - - pytest-xdist>=2.2.0 ; extra == 'test' - - pyarrow>=10.0.1 ; extra == 'pyarrow' - - bottleneck>=1.3.6 ; extra == 'performance' - - numba>=0.56.4 ; extra == 'performance' - - numexpr>=2.8.4 ; extra == 'performance' - - scipy>=1.10.0 ; extra == 'computation' - - xarray>=2022.12.0 ; extra == 'computation' - - fsspec>=2022.11.0 ; extra == 'fss' - - s3fs>=2022.11.0 ; extra == 'aws' - - gcsfs>=2022.11.0 ; extra == 'gcp' - - pandas-gbq>=0.19.0 ; extra == 'gcp' + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' - odfpy>=1.4.1 ; extra == 'excel' - - openpyxl>=3.1.0 ; extra == 'excel' - - python-calamine>=0.1.7 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' - pyxlsb>=1.0.10 ; extra == 'excel' - xlrd>=2.0.1 ; extra == 'excel' - - xlsxwriter>=3.0.5 ; extra == 'excel' - - pyarrow>=10.0.1 ; extra == 'parquet' - - pyarrow>=10.0.1 ; extra == 'feather' - - tables>=3.8.0 ; extra == 'hdf5' - - pyreadstat>=1.2.0 ; extra == 'spss' - - sqlalchemy>=2.0.0 ; extra == 'postgresql' - - psycopg2>=2.9.6 ; extra == 'postgresql' - - adbc-driver-postgresql>=0.8.0 ; extra == 'postgresql' - - sqlalchemy>=2.0.0 ; extra == 'mysql' - - pymysql>=1.0.2 ; extra == 'mysql' - - sqlalchemy>=2.0.0 ; extra == 'sql-other' - - adbc-driver-postgresql>=0.8.0 ; extra == 'sql-other' - - adbc-driver-sqlite>=0.8.0 ; extra == 'sql-other' - - beautifulsoup4>=4.11.2 ; extra == 'html' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' - html5lib>=1.1 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'xml' - - matplotlib>=3.6.3 ; extra == 'plot' - - jinja2>=3.1.2 ; extra == 'output-formatting' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' - tabulate>=0.9.0 ; extra == 'output-formatting' - pyqt5>=5.15.9 ; extra == 'clipboard' - - qtpy>=2.3.0 ; extra == 'clipboard' - - zstandard>=0.19.0 ; extra == 'compression' - - dataframe-api-compat>=0.1.7 ; extra == 'consortium-standard' - - adbc-driver-postgresql>=0.8.0 ; extra == 'all' - - adbc-driver-sqlite>=0.8.0 ; extra == 'all' - - beautifulsoup4>=4.11.2 ; extra == 'all' - - bottleneck>=1.3.6 ; extra == 'all' - - dataframe-api-compat>=0.1.7 ; extra == 'all' - - fastparquet>=2022.12.0 ; extra == 'all' - - fsspec>=2022.11.0 ; extra == 'all' - - gcsfs>=2022.11.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' - html5lib>=1.1 ; extra == 'all' - - hypothesis>=6.46.1 ; extra == 'all' - - jinja2>=3.1.2 ; extra == 'all' - - lxml>=4.9.2 ; extra == 'all' - - matplotlib>=3.6.3 ; extra == 'all' - - numba>=0.56.4 ; extra == 'all' - - numexpr>=2.8.4 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' - odfpy>=1.4.1 ; extra == 'all' - - openpyxl>=3.1.0 ; extra == 'all' - - pandas-gbq>=0.19.0 ; extra == 'all' - - psycopg2>=2.9.6 ; extra == 'all' - - pyarrow>=10.0.1 ; extra == 'all' - - pymysql>=1.0.2 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' - pyqt5>=5.15.9 ; extra == 'all' - - pyreadstat>=1.2.0 ; extra == 'all' - - pytest>=7.3.2 ; extra == 'all' - - pytest-xdist>=2.2.0 ; extra == 'all' - - python-calamine>=0.1.7 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' - pyxlsb>=1.0.10 ; extra == 'all' - - qtpy>=2.3.0 ; extra == 'all' - - scipy>=1.10.0 ; extra == 'all' - - s3fs>=2022.11.0 ; extra == 'all' - - sqlalchemy>=2.0.0 ; extra == 'all' - - tables>=3.8.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' - tabulate>=0.9.0 ; extra == 'all' - - xarray>=2022.12.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' - xlrd>=2.0.1 ; extra == 'all' - - xlsxwriter>=3.0.5 ; extra == 'all' - - zstandard>=0.19.0 ; extra == 'all' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/a8/14/5990826f779f79148ae9d3a2c39593dc04d61d5d90541e71b5749f35af95/pandas-3.0.1-cp313-cp313-macosx_11_0_arm64.whl name: pandas - version: 2.3.3 - sha256: f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c + version: 3.0.1 + sha256: 661e0f665932af88c7877f31da0dc743fe9c8f2524bdffe23d24fdcb67ef9d56 requires_dist: - - numpy>=1.22.4 ; python_full_version < '3.11' - - numpy>=1.23.2 ; python_full_version == '3.11.*' - - numpy>=1.26.0 ; python_full_version >= '3.12' + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' - python-dateutil>=2.8.2 - - pytz>=2020.1 - - tzdata>=2022.7 - - hypothesis>=6.46.1 ; extra == 'test' - - pytest>=7.3.2 ; extra == 'test' - - pytest-xdist>=2.2.0 ; extra == 'test' - - pyarrow>=10.0.1 ; extra == 'pyarrow' - - bottleneck>=1.3.6 ; extra == 'performance' - - numba>=0.56.4 ; extra == 'performance' - - numexpr>=2.8.4 ; extra == 'performance' - - scipy>=1.10.0 ; extra == 'computation' - - xarray>=2022.12.0 ; extra == 'computation' - - fsspec>=2022.11.0 ; extra == 'fss' - - s3fs>=2022.11.0 ; extra == 'aws' - - gcsfs>=2022.11.0 ; extra == 'gcp' - - pandas-gbq>=0.19.0 ; extra == 'gcp' + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' - odfpy>=1.4.1 ; extra == 'excel' - - openpyxl>=3.1.0 ; extra == 'excel' - - python-calamine>=0.1.7 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' - pyxlsb>=1.0.10 ; extra == 'excel' - xlrd>=2.0.1 ; extra == 'excel' - - xlsxwriter>=3.0.5 ; extra == 'excel' - - pyarrow>=10.0.1 ; extra == 'parquet' - - pyarrow>=10.0.1 ; extra == 'feather' - - tables>=3.8.0 ; extra == 'hdf5' - - pyreadstat>=1.2.0 ; extra == 'spss' - - sqlalchemy>=2.0.0 ; extra == 'postgresql' - - psycopg2>=2.9.6 ; extra == 'postgresql' - - adbc-driver-postgresql>=0.8.0 ; extra == 'postgresql' - - sqlalchemy>=2.0.0 ; extra == 'mysql' - - pymysql>=1.0.2 ; extra == 'mysql' - - sqlalchemy>=2.0.0 ; extra == 'sql-other' - - adbc-driver-postgresql>=0.8.0 ; extra == 'sql-other' - - adbc-driver-sqlite>=0.8.0 ; extra == 'sql-other' - - beautifulsoup4>=4.11.2 ; extra == 'html' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' - html5lib>=1.1 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'xml' - - matplotlib>=3.6.3 ; extra == 'plot' - - jinja2>=3.1.2 ; extra == 'output-formatting' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' - tabulate>=0.9.0 ; extra == 'output-formatting' - pyqt5>=5.15.9 ; extra == 'clipboard' - - qtpy>=2.3.0 ; extra == 'clipboard' - - zstandard>=0.19.0 ; extra == 'compression' - - dataframe-api-compat>=0.1.7 ; extra == 'consortium-standard' - - adbc-driver-postgresql>=0.8.0 ; extra == 'all' - - adbc-driver-sqlite>=0.8.0 ; extra == 'all' - - beautifulsoup4>=4.11.2 ; extra == 'all' - - bottleneck>=1.3.6 ; extra == 'all' - - dataframe-api-compat>=0.1.7 ; extra == 'all' - - fastparquet>=2022.12.0 ; extra == 'all' - - fsspec>=2022.11.0 ; extra == 'all' - - gcsfs>=2022.11.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' - html5lib>=1.1 ; extra == 'all' - - hypothesis>=6.46.1 ; extra == 'all' - - jinja2>=3.1.2 ; extra == 'all' - - lxml>=4.9.2 ; extra == 'all' - - matplotlib>=3.6.3 ; extra == 'all' - - numba>=0.56.4 ; extra == 'all' - - numexpr>=2.8.4 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' - odfpy>=1.4.1 ; extra == 'all' - - openpyxl>=3.1.0 ; extra == 'all' - - pandas-gbq>=0.19.0 ; extra == 'all' - - psycopg2>=2.9.6 ; extra == 'all' - - pyarrow>=10.0.1 ; extra == 'all' - - pymysql>=1.0.2 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' - pyqt5>=5.15.9 ; extra == 'all' - - pyreadstat>=1.2.0 ; extra == 'all' - - pytest>=7.3.2 ; extra == 'all' - - pytest-xdist>=2.2.0 ; extra == 'all' - - python-calamine>=0.1.7 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' - pyxlsb>=1.0.10 ; extra == 'all' - - qtpy>=2.3.0 ; extra == 'all' - - scipy>=1.10.0 ; extra == 'all' - - s3fs>=2022.11.0 ; extra == 'all' - - sqlalchemy>=2.0.0 ; extra == 'all' - - tables>=3.8.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' - tabulate>=0.9.0 ; extra == 'all' - - xarray>=2022.12.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' - xlrd>=2.0.1 ; extra == 'all' - - xlsxwriter>=3.0.5 ; extra == 'all' - - zstandard>=0.19.0 ; extra == 'all' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/c1/27/90683c7122febeefe84a56f2cde86a9f05f68d53885cebcc473298dfc33e/pandas-3.0.1-cp311-cp311-macosx_11_0_arm64.whl name: pandas - version: 2.3.3 - sha256: 8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45 + version: 3.0.1 + sha256: 24ba315ba3d6e5806063ac6eb717504e499ce30bd8c236d8693a5fd3f084c796 requires_dist: - - numpy>=1.22.4 ; python_full_version < '3.11' - - numpy>=1.23.2 ; python_full_version == '3.11.*' - - numpy>=1.26.0 ; python_full_version >= '3.12' + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' - python-dateutil>=2.8.2 - - pytz>=2020.1 - - tzdata>=2022.7 - - hypothesis>=6.46.1 ; extra == 'test' - - pytest>=7.3.2 ; extra == 'test' - - pytest-xdist>=2.2.0 ; extra == 'test' - - pyarrow>=10.0.1 ; extra == 'pyarrow' - - bottleneck>=1.3.6 ; extra == 'performance' - - numba>=0.56.4 ; extra == 'performance' - - numexpr>=2.8.4 ; extra == 'performance' - - scipy>=1.10.0 ; extra == 'computation' - - xarray>=2022.12.0 ; extra == 'computation' - - fsspec>=2022.11.0 ; extra == 'fss' - - s3fs>=2022.11.0 ; extra == 'aws' - - gcsfs>=2022.11.0 ; extra == 'gcp' - - pandas-gbq>=0.19.0 ; extra == 'gcp' + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' - odfpy>=1.4.1 ; extra == 'excel' - - openpyxl>=3.1.0 ; extra == 'excel' - - python-calamine>=0.1.7 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' - pyxlsb>=1.0.10 ; extra == 'excel' - xlrd>=2.0.1 ; extra == 'excel' - - xlsxwriter>=3.0.5 ; extra == 'excel' - - pyarrow>=10.0.1 ; extra == 'parquet' - - pyarrow>=10.0.1 ; extra == 'feather' - - tables>=3.8.0 ; extra == 'hdf5' - - pyreadstat>=1.2.0 ; extra == 'spss' - - sqlalchemy>=2.0.0 ; extra == 'postgresql' - - psycopg2>=2.9.6 ; extra == 'postgresql' - - adbc-driver-postgresql>=0.8.0 ; extra == 'postgresql' - - sqlalchemy>=2.0.0 ; extra == 'mysql' - - pymysql>=1.0.2 ; extra == 'mysql' - - sqlalchemy>=2.0.0 ; extra == 'sql-other' - - adbc-driver-postgresql>=0.8.0 ; extra == 'sql-other' - - adbc-driver-sqlite>=0.8.0 ; extra == 'sql-other' - - beautifulsoup4>=4.11.2 ; extra == 'html' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' - html5lib>=1.1 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'xml' - - matplotlib>=3.6.3 ; extra == 'plot' - - jinja2>=3.1.2 ; extra == 'output-formatting' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' - tabulate>=0.9.0 ; extra == 'output-formatting' - pyqt5>=5.15.9 ; extra == 'clipboard' - - qtpy>=2.3.0 ; extra == 'clipboard' - - zstandard>=0.19.0 ; extra == 'compression' - - dataframe-api-compat>=0.1.7 ; extra == 'consortium-standard' - - adbc-driver-postgresql>=0.8.0 ; extra == 'all' - - adbc-driver-sqlite>=0.8.0 ; extra == 'all' - - beautifulsoup4>=4.11.2 ; extra == 'all' - - bottleneck>=1.3.6 ; extra == 'all' - - dataframe-api-compat>=0.1.7 ; extra == 'all' - - fastparquet>=2022.12.0 ; extra == 'all' - - fsspec>=2022.11.0 ; extra == 'all' - - gcsfs>=2022.11.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' - html5lib>=1.1 ; extra == 'all' - - hypothesis>=6.46.1 ; extra == 'all' - - jinja2>=3.1.2 ; extra == 'all' - - lxml>=4.9.2 ; extra == 'all' - - matplotlib>=3.6.3 ; extra == 'all' - - numba>=0.56.4 ; extra == 'all' - - numexpr>=2.8.4 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' - odfpy>=1.4.1 ; extra == 'all' - - openpyxl>=3.1.0 ; extra == 'all' - - pandas-gbq>=0.19.0 ; extra == 'all' - - psycopg2>=2.9.6 ; extra == 'all' - - pyarrow>=10.0.1 ; extra == 'all' - - pymysql>=1.0.2 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' - pyqt5>=5.15.9 ; extra == 'all' - - pyreadstat>=1.2.0 ; extra == 'all' - - pytest>=7.3.2 ; extra == 'all' - - pytest-xdist>=2.2.0 ; extra == 'all' - - python-calamine>=0.1.7 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' - pyxlsb>=1.0.10 ; extra == 'all' - - qtpy>=2.3.0 ; extra == 'all' - - scipy>=1.10.0 ; extra == 'all' - - s3fs>=2022.11.0 ; extra == 'all' - - sqlalchemy>=2.0.0 ; extra == 'all' - - tables>=3.8.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' - tabulate>=0.9.0 ; extra == 'all' - - xarray>=2022.12.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' - xlrd>=2.0.1 ; extra == 'all' - - xlsxwriter>=3.0.5 ; extra == 'all' - - zstandard>=0.19.0 ; extra == 'all' - requires_python: '>=3.9' -- 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 + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/d6/7d/216a1588b65a7aa5f4535570418a599d943c85afb1d95b0876fc00aa1468/pandas-3.0.1-cp313-cp313-win_amd64.whl name: pandas - version: 2.3.3 - sha256: b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b + version: 3.0.1 + sha256: 9fea306c783e28884c29057a1d9baa11a349bbf99538ec1da44c8476563d1b25 requires_dist: - - numpy>=1.22.4 ; python_full_version < '3.11' - - numpy>=1.23.2 ; python_full_version == '3.11.*' - - numpy>=1.26.0 ; python_full_version >= '3.12' + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' - python-dateutil>=2.8.2 - - pytz>=2020.1 - - tzdata>=2022.7 - - hypothesis>=6.46.1 ; extra == 'test' - - pytest>=7.3.2 ; extra == 'test' - - pytest-xdist>=2.2.0 ; extra == 'test' - - pyarrow>=10.0.1 ; extra == 'pyarrow' - - bottleneck>=1.3.6 ; extra == 'performance' - - numba>=0.56.4 ; extra == 'performance' - - numexpr>=2.8.4 ; extra == 'performance' - - scipy>=1.10.0 ; extra == 'computation' - - xarray>=2022.12.0 ; extra == 'computation' - - fsspec>=2022.11.0 ; extra == 'fss' - - s3fs>=2022.11.0 ; extra == 'aws' - - gcsfs>=2022.11.0 ; extra == 'gcp' - - pandas-gbq>=0.19.0 ; extra == 'gcp' + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' - odfpy>=1.4.1 ; extra == 'excel' - - openpyxl>=3.1.0 ; extra == 'excel' - - python-calamine>=0.1.7 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' - pyxlsb>=1.0.10 ; extra == 'excel' - xlrd>=2.0.1 ; extra == 'excel' - - xlsxwriter>=3.0.5 ; extra == 'excel' - - pyarrow>=10.0.1 ; extra == 'parquet' - - pyarrow>=10.0.1 ; extra == 'feather' - - tables>=3.8.0 ; extra == 'hdf5' - - pyreadstat>=1.2.0 ; extra == 'spss' - - sqlalchemy>=2.0.0 ; extra == 'postgresql' - - psycopg2>=2.9.6 ; extra == 'postgresql' - - adbc-driver-postgresql>=0.8.0 ; extra == 'postgresql' - - sqlalchemy>=2.0.0 ; extra == 'mysql' - - pymysql>=1.0.2 ; extra == 'mysql' - - sqlalchemy>=2.0.0 ; extra == 'sql-other' - - adbc-driver-postgresql>=0.8.0 ; extra == 'sql-other' - - adbc-driver-sqlite>=0.8.0 ; extra == 'sql-other' - - beautifulsoup4>=4.11.2 ; extra == 'html' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' - html5lib>=1.1 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'xml' - - matplotlib>=3.6.3 ; extra == 'plot' - - jinja2>=3.1.2 ; extra == 'output-formatting' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' - tabulate>=0.9.0 ; extra == 'output-formatting' - pyqt5>=5.15.9 ; extra == 'clipboard' - - qtpy>=2.3.0 ; extra == 'clipboard' - - zstandard>=0.19.0 ; extra == 'compression' - - dataframe-api-compat>=0.1.7 ; extra == 'consortium-standard' - - adbc-driver-postgresql>=0.8.0 ; extra == 'all' - - adbc-driver-sqlite>=0.8.0 ; extra == 'all' - - beautifulsoup4>=4.11.2 ; extra == 'all' - - bottleneck>=1.3.6 ; extra == 'all' - - dataframe-api-compat>=0.1.7 ; extra == 'all' - - fastparquet>=2022.12.0 ; extra == 'all' - - fsspec>=2022.11.0 ; extra == 'all' - - gcsfs>=2022.11.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' - html5lib>=1.1 ; extra == 'all' - - hypothesis>=6.46.1 ; extra == 'all' - - jinja2>=3.1.2 ; extra == 'all' - - lxml>=4.9.2 ; extra == 'all' - - matplotlib>=3.6.3 ; extra == 'all' - - numba>=0.56.4 ; extra == 'all' - - numexpr>=2.8.4 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' - odfpy>=1.4.1 ; extra == 'all' - - openpyxl>=3.1.0 ; extra == 'all' - - pandas-gbq>=0.19.0 ; extra == 'all' - - psycopg2>=2.9.6 ; extra == 'all' - - pyarrow>=10.0.1 ; extra == 'all' - - pymysql>=1.0.2 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' - pyqt5>=5.15.9 ; extra == 'all' - - pyreadstat>=1.2.0 ; extra == 'all' - - pytest>=7.3.2 ; extra == 'all' - - pytest-xdist>=2.2.0 ; extra == 'all' - - python-calamine>=0.1.7 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' - pyxlsb>=1.0.10 ; extra == 'all' - - qtpy>=2.3.0 ; extra == 'all' - - scipy>=1.10.0 ; extra == 'all' - - s3fs>=2022.11.0 ; extra == 'all' - - sqlalchemy>=2.0.0 ; extra == 'all' - - tables>=3.8.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' - tabulate>=0.9.0 ; extra == 'all' - - xarray>=2022.12.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' - xlrd>=2.0.1 ; extra == 'all' - - xlsxwriter>=3.0.5 ; extra == 'all' - - zstandard>=0.19.0 ; extra == 'all' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl name: pandas - version: 2.3.3 - sha256: 602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523 + version: 3.0.1 + sha256: 1ff8cf1d2896e34343197685f432450ec99a85ba8d90cce2030c5eee2ef98791 requires_dist: - - numpy>=1.22.4 ; python_full_version < '3.11' - - numpy>=1.23.2 ; python_full_version == '3.11.*' - - numpy>=1.26.0 ; python_full_version >= '3.12' + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' - python-dateutil>=2.8.2 - - pytz>=2020.1 - - tzdata>=2022.7 - - hypothesis>=6.46.1 ; extra == 'test' - - pytest>=7.3.2 ; extra == 'test' - - pytest-xdist>=2.2.0 ; extra == 'test' - - pyarrow>=10.0.1 ; extra == 'pyarrow' - - bottleneck>=1.3.6 ; extra == 'performance' - - numba>=0.56.4 ; extra == 'performance' - - numexpr>=2.8.4 ; extra == 'performance' - - scipy>=1.10.0 ; extra == 'computation' - - xarray>=2022.12.0 ; extra == 'computation' - - fsspec>=2022.11.0 ; extra == 'fss' - - s3fs>=2022.11.0 ; extra == 'aws' - - gcsfs>=2022.11.0 ; extra == 'gcp' - - pandas-gbq>=0.19.0 ; extra == 'gcp' + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' - odfpy>=1.4.1 ; extra == 'excel' - - openpyxl>=3.1.0 ; extra == 'excel' - - python-calamine>=0.1.7 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' - pyxlsb>=1.0.10 ; extra == 'excel' - xlrd>=2.0.1 ; extra == 'excel' - - xlsxwriter>=3.0.5 ; extra == 'excel' - - pyarrow>=10.0.1 ; extra == 'parquet' - - pyarrow>=10.0.1 ; extra == 'feather' - - tables>=3.8.0 ; extra == 'hdf5' - - pyreadstat>=1.2.0 ; extra == 'spss' - - sqlalchemy>=2.0.0 ; extra == 'postgresql' - - psycopg2>=2.9.6 ; extra == 'postgresql' - - adbc-driver-postgresql>=0.8.0 ; extra == 'postgresql' - - sqlalchemy>=2.0.0 ; extra == 'mysql' - - pymysql>=1.0.2 ; extra == 'mysql' - - sqlalchemy>=2.0.0 ; extra == 'sql-other' - - adbc-driver-postgresql>=0.8.0 ; extra == 'sql-other' - - adbc-driver-sqlite>=0.8.0 ; extra == 'sql-other' - - beautifulsoup4>=4.11.2 ; extra == 'html' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' - html5lib>=1.1 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'xml' - - matplotlib>=3.6.3 ; extra == 'plot' - - jinja2>=3.1.2 ; extra == 'output-formatting' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' - tabulate>=0.9.0 ; extra == 'output-formatting' - pyqt5>=5.15.9 ; extra == 'clipboard' - - qtpy>=2.3.0 ; extra == 'clipboard' - - zstandard>=0.19.0 ; extra == 'compression' - - dataframe-api-compat>=0.1.7 ; extra == 'consortium-standard' - - adbc-driver-postgresql>=0.8.0 ; extra == 'all' - - adbc-driver-sqlite>=0.8.0 ; extra == 'all' - - beautifulsoup4>=4.11.2 ; extra == 'all' - - bottleneck>=1.3.6 ; extra == 'all' - - dataframe-api-compat>=0.1.7 ; extra == 'all' - - fastparquet>=2022.12.0 ; extra == 'all' - - fsspec>=2022.11.0 ; extra == 'all' - - gcsfs>=2022.11.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' - html5lib>=1.1 ; extra == 'all' - - hypothesis>=6.46.1 ; extra == 'all' - - jinja2>=3.1.2 ; extra == 'all' - - lxml>=4.9.2 ; extra == 'all' - - matplotlib>=3.6.3 ; extra == 'all' - - numba>=0.56.4 ; extra == 'all' - - numexpr>=2.8.4 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' - odfpy>=1.4.1 ; extra == 'all' - - openpyxl>=3.1.0 ; extra == 'all' - - pandas-gbq>=0.19.0 ; extra == 'all' - - psycopg2>=2.9.6 ; extra == 'all' - - pyarrow>=10.0.1 ; extra == 'all' - - pymysql>=1.0.2 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' - pyqt5>=5.15.9 ; extra == 'all' - - pyreadstat>=1.2.0 ; extra == 'all' - - pytest>=7.3.2 ; extra == 'all' - - pytest-xdist>=2.2.0 ; extra == 'all' - - python-calamine>=0.1.7 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' - pyxlsb>=1.0.10 ; extra == 'all' - - qtpy>=2.3.0 ; extra == 'all' - - scipy>=1.10.0 ; extra == 'all' - - s3fs>=2022.11.0 ; extra == 'all' - - sqlalchemy>=2.0.0 ; extra == 'all' - - tables>=3.8.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' - tabulate>=0.9.0 ; extra == 'all' - - xarray>=2022.12.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' - xlrd>=2.0.1 ; extra == 'all' - - xlsxwriter>=3.0.5 ; extra == 'all' - - zstandard>=0.19.0 ; extra == 'all' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/ff/07/c7087e003ceee9b9a82539b40414ec557aa795b584a1a346e89180853d79/pandas-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl name: pandas - version: 2.3.3 - sha256: 56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713 + version: 3.0.1 + sha256: de09668c1bf3b925c07e5762291602f0d789eca1b3a781f99c1c78f6cac0e7ea requires_dist: - - numpy>=1.22.4 ; python_full_version < '3.11' - - numpy>=1.23.2 ; python_full_version == '3.11.*' - - numpy>=1.26.0 ; python_full_version >= '3.12' + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' - python-dateutil>=2.8.2 - - pytz>=2020.1 - - tzdata>=2022.7 - - hypothesis>=6.46.1 ; extra == 'test' - - pytest>=7.3.2 ; extra == 'test' - - pytest-xdist>=2.2.0 ; extra == 'test' - - pyarrow>=10.0.1 ; extra == 'pyarrow' - - bottleneck>=1.3.6 ; extra == 'performance' - - numba>=0.56.4 ; extra == 'performance' - - numexpr>=2.8.4 ; extra == 'performance' - - scipy>=1.10.0 ; extra == 'computation' - - xarray>=2022.12.0 ; extra == 'computation' - - fsspec>=2022.11.0 ; extra == 'fss' - - s3fs>=2022.11.0 ; extra == 'aws' - - gcsfs>=2022.11.0 ; extra == 'gcp' - - pandas-gbq>=0.19.0 ; extra == 'gcp' + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' - odfpy>=1.4.1 ; extra == 'excel' - - openpyxl>=3.1.0 ; extra == 'excel' - - python-calamine>=0.1.7 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' - pyxlsb>=1.0.10 ; extra == 'excel' - xlrd>=2.0.1 ; extra == 'excel' - - xlsxwriter>=3.0.5 ; extra == 'excel' - - pyarrow>=10.0.1 ; extra == 'parquet' - - pyarrow>=10.0.1 ; extra == 'feather' - - tables>=3.8.0 ; extra == 'hdf5' - - pyreadstat>=1.2.0 ; extra == 'spss' - - sqlalchemy>=2.0.0 ; extra == 'postgresql' - - psycopg2>=2.9.6 ; extra == 'postgresql' - - adbc-driver-postgresql>=0.8.0 ; extra == 'postgresql' - - sqlalchemy>=2.0.0 ; extra == 'mysql' - - pymysql>=1.0.2 ; extra == 'mysql' - - sqlalchemy>=2.0.0 ; extra == 'sql-other' - - adbc-driver-postgresql>=0.8.0 ; extra == 'sql-other' - - adbc-driver-sqlite>=0.8.0 ; extra == 'sql-other' - - beautifulsoup4>=4.11.2 ; extra == 'html' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' - html5lib>=1.1 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'html' - - lxml>=4.9.2 ; extra == 'xml' - - matplotlib>=3.6.3 ; extra == 'plot' - - jinja2>=3.1.2 ; extra == 'output-formatting' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' - tabulate>=0.9.0 ; extra == 'output-formatting' - pyqt5>=5.15.9 ; extra == 'clipboard' - - qtpy>=2.3.0 ; extra == 'clipboard' - - zstandard>=0.19.0 ; extra == 'compression' - - dataframe-api-compat>=0.1.7 ; extra == 'consortium-standard' - - adbc-driver-postgresql>=0.8.0 ; extra == 'all' - - adbc-driver-sqlite>=0.8.0 ; extra == 'all' - - beautifulsoup4>=4.11.2 ; extra == 'all' - - bottleneck>=1.3.6 ; extra == 'all' - - dataframe-api-compat>=0.1.7 ; extra == 'all' - - fastparquet>=2022.12.0 ; extra == 'all' - - fsspec>=2022.11.0 ; extra == 'all' - - gcsfs>=2022.11.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' - html5lib>=1.1 ; extra == 'all' - - hypothesis>=6.46.1 ; extra == 'all' - - jinja2>=3.1.2 ; extra == 'all' - - lxml>=4.9.2 ; extra == 'all' - - matplotlib>=3.6.3 ; extra == 'all' - - numba>=0.56.4 ; extra == 'all' - - numexpr>=2.8.4 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' - odfpy>=1.4.1 ; extra == 'all' - - openpyxl>=3.1.0 ; extra == 'all' - - pandas-gbq>=0.19.0 ; extra == 'all' - - psycopg2>=2.9.6 ; extra == 'all' - - pyarrow>=10.0.1 ; extra == 'all' - - pymysql>=1.0.2 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' - pyqt5>=5.15.9 ; extra == 'all' - - pyreadstat>=1.2.0 ; extra == 'all' - - pytest>=7.3.2 ; extra == 'all' - - pytest-xdist>=2.2.0 ; extra == 'all' - - python-calamine>=0.1.7 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' - pyxlsb>=1.0.10 ; extra == 'all' - - qtpy>=2.3.0 ; extra == 'all' - - scipy>=1.10.0 ; extra == 'all' - - s3fs>=2022.11.0 ; extra == 'all' - - sqlalchemy>=2.0.0 ; extra == 'all' - - tables>=3.8.0 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' - tabulate>=0.9.0 ; extra == 'all' - - xarray>=2022.12.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' - xlrd>=2.0.1 ; extra == 'all' - - xlsxwriter>=3.0.5 ; extra == 'all' - - zstandard>=0.19.0 ; extra == 'all' - requires_python: '>=3.9' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' - 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 +- pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl name: parso - version: 0.8.5 - sha256: 646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887 + version: 0.8.6 + sha256: 2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff requires_dist: - pytest ; extra == 'testing' - docopt ; extra == 'testing' - flake8==5.0.4 ; extra == 'qa' - - mypy==0.971 ; extra == 'qa' + - zuban==0.5.1 ; extra == 'qa' - types-setuptools==67.2.0.1 ; extra == 'qa' requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/05/bb/39e6768529454cc2b57e1e2fa0a0a18ff64397a16303270e215a3e03285f/pathspec-1.0.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + name: partd + version: 1.4.2 + sha256: 978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f + requires_dist: + - locket + - toolz + - numpy>=1.20.0 ; extra == 'complete' + - pandas>=1.3 ; extra == 'complete' + - pyzmq ; extra == 'complete' + - blosc ; extra == 'complete' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl name: pathspec - version: 1.0.0 - sha256: 1373719036e64a2b9de3b8ddd9e30afb082a915619f07265ed76d9ae507800ae + version: 1.0.4 + sha256: fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723 requires_dist: - hyperscan>=0.7 ; extra == 'hyperscan' - typing-extensions>=4 ; extra == 'optional' @@ -8515,10 +9358,10 @@ packages: sha256: 7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523 requires_dist: - ptyprocess>=0.5 -- pypi: https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl name: pillow - version: 12.1.0 - sha256: 6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc + version: 12.1.1 + sha256: 6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -8547,10 +9390,10 @@ packages: - trove-classifiers>=2024.10.12 ; extra == 'tests' - defusedxml ; extra == 'xmp' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl name: pillow - version: 12.1.0 - sha256: db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac + version: 12.1.1 + sha256: e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -8579,10 +9422,10 @@ packages: - trove-classifiers>=2024.10.12 ; extra == 'tests' - defusedxml ; extra == 'xmp' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl name: pillow - version: 12.1.0 - sha256: b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0 + version: 12.1.1 + sha256: fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -8611,10 +9454,10 @@ packages: - trove-classifiers>=2024.10.12 ; extra == 'tests' - defusedxml ; extra == 'xmp' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl name: pillow - version: 12.1.0 - sha256: a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3 + version: 12.1.1 + sha256: 344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -8643,10 +9486,10 @@ packages: - trove-classifiers>=2024.10.12 ; extra == 'tests' - defusedxml ; extra == 'xmp' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: pillow - version: 12.1.0 - sha256: 983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587 + version: 12.1.1 + sha256: 47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -8675,10 +9518,10 @@ packages: - trove-classifiers>=2024.10.12 ; extra == 'tests' - defusedxml ; extra == 'xmp' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl name: pillow - version: 12.1.0 - sha256: 1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0 + version: 12.1.1 + sha256: 365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -8707,10 +9550,10 @@ packages: - trove-classifiers>=2024.10.12 ; extra == 'tests' - defusedxml ; extra == 'xmp' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: pillow - version: 12.1.0 - sha256: 806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75 + version: 12.1.1 + sha256: 597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -8739,10 +9582,10 @@ packages: - trove-classifiers>=2024.10.12 ; extra == 'tests' - defusedxml ; extra == 'xmp' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl name: pillow - version: 12.1.0 - sha256: 461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a + version: 12.1.1 + sha256: 8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -8771,10 +9614,10 @@ packages: - trove-classifiers>=2024.10.12 ; extra == 'tests' - defusedxml ; extra == 'xmp' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl name: pip - version: '25.3' - sha256: 9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd + version: 26.0.1 + sha256: bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl name: pixi-kernel @@ -8788,26 +9631,46 @@ packages: - returns>=0.23 - tomli>=2 ; python_full_version < '3.11' requires_python: '>=3.10,<4.0' -- pypi: https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl name: platformdirs - version: 4.5.1 - sha256: d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31 - 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' + version: 4.9.4 + sha256: 68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + name: plopp + version: 26.3.0 + sha256: 44e231f202e8e9bfba4556762e7a49ec4832c5c1df0c96a744f87e7d50fddc11 + requires_dist: + - lazy-loader>=0.4 + - matplotlib>=3.8 + - scipp>=25.5.0 ; extra == 'scipp' + - scipp>=25.5.0 ; extra == 'all' + - ipympl>0.8.4 ; extra == 'all' + - pythreejs>=2.4.1 ; extra == 'all' + - mpltoolbox>=24.6.0 ; extra == 'all' + - ipywidgets>=8.1.0 ; extra == 'all' + - graphviz>=0.20.3 ; extra == 'all' + - graphviz>=0.20.3 ; extra == 'test' + - h5py>=3.12 ; extra == 'test' + - ipympl>=0.8.4 ; extra == 'test' + - ipywidgets>=8.1.0 ; extra == 'test' + - ipykernel>=6.26,<7 ; extra == 'test' + - mpltoolbox>=24.6.0 ; extra == 'test' + - pandas>=2.2.2 ; extra == 'test' + - plotly>=5.15.0 ; extra == 'test' + - pooch>=1.5 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'test' + - pytest>=8.0 ; extra == 'test' + - pythreejs>=2.4.1 ; extra == 'test' + - scipp>=25.5.0 ; extra == 'test' + - scipy>=1.10.0 ; extra == 'test' + - xarray>=2024.5.0 ; extra == 'test' + - anywidget>=0.9.0 ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl name: plotly - version: 6.5.0 - sha256: 5ac851e100367735250206788a2b1325412aa4a4917a4fe3e6f0bc5aa6f3d90a + version: 6.6.0 + sha256: 8d6daf0f87412e0c0bfe72e809d615217ab57cc715899a1e5145135a7800d1d0 requires_dist: - narwhals>=1.15.1 - packaging @@ -8859,10 +9722,10 @@ packages: name: ply version: '3.11' sha256: 096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce -- pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl name: pooch - version: 1.8.2 - sha256: 3529a57096f7198778a5ceefd5ac3ef0e4d06a6ddaf9fc2d609b806f25302c47 + version: 1.9.0 + sha256: f265597baa9f760d25ceb29d0beb8186c243d6607b0f60b83ecf14078dbc703b requires_dist: - platformdirs>=2.5.0 - packaging>=20.0 @@ -8870,7 +9733,9 @@ packages: - tqdm>=4.41.0,<5.0.0 ; extra == 'progress' - paramiko>=2.7.0 ; extra == 'sftp' - xxhash>=1.4.3 ; extra == 'xxhash' - requires_python: '>=3.7' + - pytest-httpserver ; extra == 'test' + - pytest-localftpserver ; extra == 'test' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl name: pre-commit version: 4.5.1 @@ -8892,12 +9757,14 @@ packages: - pytest-cov ; extra == 'tests' - pytest-lazy-fixtures ; extra == 'tests' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl name: prometheus-client - version: 0.23.1 - sha256: dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99 + version: 0.24.1 + sha256: 150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055 requires_dist: - twisted ; extra == 'twisted' + - aiohttp ; extra == 'aiohttp' + - django ; extra == 'django' 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 @@ -8946,10 +9813,10 @@ packages: version: 0.4.1 sha256: 381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl name: psutil - version: 7.2.1 - sha256: 5e38404ca2bb30ed7267a46c02f06ff842e92da3bb8c5bfdadbd35a5722314d8 + version: 7.2.2 + sha256: 1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979 requires_dist: - psleak ; extra == 'dev' - pytest ; extra == 'dev' @@ -8976,25 +9843,30 @@ packages: - virtualenv ; extra == 'dev' - vulture ; extra == 'dev' - wheel ; extra == 'dev' + - colorama ; os_name == 'nt' and extra == 'dev' + - pyreadline3 ; os_name == 'nt' and extra == 'dev' + - pywin32 ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'dev' + - wheel ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'dev' + - wmi ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'dev' - psleak ; extra == 'test' - pytest ; extra == 'test' - pytest-instafail ; extra == 'test' - pytest-xdist ; extra == 'test' - setuptools ; extra == 'test' + - pywin32 ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'test' + - wheel ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'test' + - wmi ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'test' requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/34/68/d9317542e3f2b180c4306e3f45d3c922d7e86d8ce39f941bb9e2e9d8599e/psutil-7.2.1-cp37-abi3-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl name: psutil - version: 7.2.1 - sha256: b1b0671619343aa71c20ff9767eced0483e4fc9e1f489d50923738caf6a03c17 + version: 7.2.2 + sha256: eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988 requires_dist: - psleak ; extra == 'dev' - pytest ; extra == 'dev' - pytest-instafail ; extra == 'dev' - pytest-xdist ; extra == 'dev' - setuptools ; extra == 'dev' - - pywin32 ; extra == 'dev' - - wheel ; extra == 'dev' - - wmi ; extra == 'dev' - abi3audit ; extra == 'dev' - black ; extra == 'dev' - check-manifest ; extra == 'dev' @@ -9014,21 +9886,25 @@ packages: - validate-pyproject[all] ; extra == 'dev' - virtualenv ; extra == 'dev' - vulture ; extra == 'dev' - - colorama ; extra == 'dev' - - pyreadline3 ; extra == 'dev' + - wheel ; extra == 'dev' + - colorama ; os_name == 'nt' and extra == 'dev' + - pyreadline3 ; os_name == 'nt' and extra == 'dev' + - pywin32 ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'dev' + - wheel ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'dev' + - wmi ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'dev' - psleak ; extra == 'test' - pytest ; extra == 'test' - pytest-instafail ; extra == 'test' - pytest-xdist ; extra == 'test' - setuptools ; extra == 'test' - - pywin32 ; extra == 'test' - - wheel ; extra == 'test' - - wmi ; extra == 'test' + - pywin32 ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'test' + - wheel ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'test' + - wmi ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'test' requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl name: psutil - version: 7.2.1 - sha256: 05cc68dbb8c174828624062e73078e7e35406f4ca2d0866c272c2410d8ef06d1 + version: 7.2.2 + sha256: 076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9 requires_dist: - psleak ; extra == 'dev' - pytest ; extra == 'dev' @@ -9055,16 +9931,24 @@ packages: - virtualenv ; extra == 'dev' - vulture ; extra == 'dev' - wheel ; extra == 'dev' + - colorama ; os_name == 'nt' and extra == 'dev' + - pyreadline3 ; os_name == 'nt' and extra == 'dev' + - pywin32 ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'dev' + - wheel ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'dev' + - wmi ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'dev' - psleak ; extra == 'test' - pytest ; extra == 'test' - pytest-instafail ; extra == 'test' - pytest-xdist ; extra == 'test' - setuptools ; extra == 'test' + - pywin32 ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'test' + - wheel ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'test' + - wmi ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'test' requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl name: psutil - version: 7.2.1 - sha256: b2e953fcfaedcfbc952b44744f22d16575d3aa78eb4f51ae74165b4e96e55f42 + version: 7.2.2 + sha256: ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486 requires_dist: - psleak ; extra == 'dev' - pytest ; extra == 'dev' @@ -9091,11 +9975,19 @@ packages: - virtualenv ; extra == 'dev' - vulture ; extra == 'dev' - wheel ; extra == 'dev' + - colorama ; os_name == 'nt' and extra == 'dev' + - pyreadline3 ; os_name == 'nt' and extra == 'dev' + - pywin32 ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'dev' + - wheel ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'dev' + - wmi ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'dev' - psleak ; extra == 'test' - pytest ; extra == 'test' - pytest-instafail ; extra == 'test' - pytest-xdist ; extra == 'test' - setuptools ; extra == 'test' + - pywin32 ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'test' + - wheel ; implementation_name != 'pypy' and os_name == 'nt' and extra == 'test' + - wmi ; implementation_name != 'pypy' and os_name == 'nt' 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 @@ -9112,10 +10004,10 @@ packages: 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 +- pypi: https://files.pythonhosted.org/packages/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-py2.py3-none-any.whl name: py3dmol - version: 2.5.3 - sha256: 5c1c9ee40bda82b91978e75f3c144be5b90cdf472e765bcef4890db76cc8f843 + version: 2.5.4 + sha256: 32806726b5310524a2b5bfee320737f7feef635cafc945c991062806daa9e43a 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 @@ -9191,11 +10083,79 @@ packages: 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 +- pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl name: pycparser - version: '2.23' - sha256: e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934 - requires_python: '>=3.8' + version: '3.0' + sha256: b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + name: pydantic + version: 2.12.5 + sha256: e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d + requires_dist: + - annotated-types>=0.6.0 + - pydantic-core==2.41.5 + - typing-extensions>=4.14.1 + - typing-inspection>=0.4.2 + - email-validator>=2.0.0 ; extra == 'email' + - tzdata ; python_full_version >= '3.9' and sys_platform == 'win32' and extra == 'timezone' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl + name: pydantic-core + version: 2.41.5 + sha256: 76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl + name: pydantic-core + version: 2.41.5 + sha256: 7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl + name: pydantic-core + version: 2.41.5 + sha256: 941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9 + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl + name: pydantic-core + version: 2.41.5 + sha256: 112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34 + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl + name: pydantic-core + version: 2.41.5 + sha256: 79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11 + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: pydantic-core + version: 2.41.5 + sha256: f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: pydantic-core + version: 2.41.5 + sha256: 406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586 + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl + name: pydantic-core + version: 2.41.5 + sha256: a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6 + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl name: pygments version: 2.19.2 @@ -9203,19 +10163,19 @@ packages: requires_dist: - colorama>=0.4.6 ; extra == 'windows-terminal' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl name: pymdown-extensions - version: '10.20' - sha256: ea9e62add865da80a271d00bfa1c0fa085b20d133fb3fc97afdc88e682f60b2f + version: '10.21' + sha256: 91b879f9f864d49794c2d9534372b10150e6141096c3908a455e45ca72ad9d3f requires_dist: - markdown>=3.6 - pyyaml - pygments>=2.19.1 ; extra == 'extra' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl name: pyparsing - version: 3.3.1 - sha256: 023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82 + version: 3.3.2 + sha256: 850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d requires_dist: - railroad-diagrams ; extra == 'diagrams' - jinja2 ; extra == 'diagrams' @@ -9268,38 +10228,37 @@ packages: - 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-hd63d673_2_cpython.conda - build_number: 2 - sha256: 5b872f7747891e50e990a96d2b235236a5c66cc9f8c9dcb7149aee674ea8145a - md5: c4202a55b4486314fbb8c11bc43a29a0 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda + sha256: bf6a32c69889d38482436a786bea32276756cedf0e9805cc856ffd088e8d00f0 + md5: a5ebcefec0c12a333bcd6d7bf3bddc1f depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.7.1,<3.0a0 + - libexpat >=2.7.4,<3.0a0 - libffi >=3.5.2,<3.6.0a0 - libgcc >=14 - - liblzma >=5.8.1,<6.0a0 + - liblzma >=5.8.2,<6.0a0 - libnsl >=2.0.1,<2.1.0a0 - - libsqlite >=3.50.4,<4.0a0 - - libuuid >=2.41.2,<3.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libuuid >=2.41.3,<3.0a0 - libxcrypt >=4.4.36 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.4,<4.0a0 - - readline >=8.2,<9.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata constrains: - python_abi 3.11.* *_cp311 license: Python-2.0 purls: [] - size: 30874708 - timestamp: 1761174520369 -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.11-hc97d973_100_cp313.conda + size: 30949404 + timestamp: 1772730362552 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.12-hc97d973_100_cp313.conda build_number: 100 - sha256: 9cf014cf28e93ee242bacfbf664e8b45ae06e50b04291e640abeaeb0cba0364c - md5: 0cbb0010f1d8ecb64a428a8d4214609e + sha256: 8a08fe5b7cb5a28aa44e2994d18dbf77f443956990753a4ca8173153ffb6eb56 + md5: 4c875ed0e78c2d407ec55eadffb8cf3d depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 @@ -9307,128 +10266,125 @@ packages: - libexpat >=2.7.3,<3.0a0 - libffi >=3.5.2,<3.6.0a0 - libgcc >=14 - - liblzma >=5.8.1,<6.0a0 + - liblzma >=5.8.2,<6.0a0 - libmpdec >=4.0.0,<5.0a0 - - libsqlite >=3.51.1,<4.0a0 - - libuuid >=2.41.2,<3.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libuuid >=2.41.3,<3.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.4,<4.0a0 + - openssl >=3.5.5,<4.0a0 - python_abi 3.13.* *_cp313 - - readline >=8.2,<9.0a0 + - readline >=8.3,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata license: Python-2.0 purls: [] - size: 37226336 - timestamp: 1765021889577 + size: 37364553 + timestamp: 1770272309861 python_site_packages_path: lib/python3.13/site-packages -- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.14-h74c2667_2_cpython.conda - build_number: 2 - sha256: 0a17479efb8df514c3777c015ffe430d38a3a59c01dc46358e87d7ff459c9aeb - md5: 37ac5f13a245f08746e0d658b245d670 +- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.15-ha9537fe_0_cpython.conda + sha256: e02e12cd8d391f18bb3bf91d07e16b993592ec0d76ee37cf639390b766e0e687 + md5: 93b802a91de90b2c17b808608726bf45 depends: - - __osx >=10.13 + - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.1,<3.0a0 + - libexpat >=2.7.4,<3.0a0 - libffi >=3.5.2,<3.6.0a0 - - liblzma >=5.8.1,<6.0a0 - - libsqlite >=3.50.4,<4.0a0 + - liblzma >=5.8.2,<6.0a0 + - libsqlite >=3.51.2,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.4,<4.0a0 - - readline >=8.2,<9.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata constrains: - python_abi 3.11.* *_cp311 license: Python-2.0 purls: [] - size: 15697126 - timestamp: 1761174493171 -- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.11-h17c18a5_100_cp313.conda + size: 15664115 + timestamp: 1772730794934 +- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.12-h894a449_100_cp313.conda build_number: 100 - sha256: 58e23beaf3174a809c785900477c37df9f88993b5a3ccd0d76d57d6688a1be37 - md5: 6ffffd784fe1126b73329e29c80ddf53 + sha256: 9548dcf58cf6045aa4aa1f2f3fa6110115ca616a8d5fa142a24081d2b9d91291 + md5: 99b1fa1fe8a8ab58224969f4568aadca depends: - __osx >=10.13 - bzip2 >=1.0.8,<2.0a0 - libexpat >=2.7.3,<3.0a0 - libffi >=3.5.2,<3.6.0a0 - - liblzma >=5.8.1,<6.0a0 + - liblzma >=5.8.2,<6.0a0 - libmpdec >=4.0.0,<5.0a0 - - libsqlite >=3.51.1,<4.0a0 + - libsqlite >=3.51.2,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.4,<4.0a0 + - openssl >=3.5.5,<4.0a0 - python_abi 3.13.* *_cp313 - - readline >=8.2,<9.0a0 + - readline >=8.3,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata license: Python-2.0 purls: [] - size: 17360881 - timestamp: 1765022591905 + size: 17570178 + timestamp: 1770272361922 python_site_packages_path: lib/python3.13/site-packages -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.14-h18782d2_2_cpython.conda - build_number: 2 - sha256: 64a2bc6be8582fae75f1f2da7bdc49afd81c2793f65bb843fc37f53c99734063 - md5: da948e6cd735249ab4cfbb3fdede785e +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.15-h8561d8f_0_cpython.conda + sha256: 9a846065863925b2562126a5c6fecd7a972e84aaa4de9e686ad3715ca506acfa + md5: 49c7d96c58b969585cf09fb01d74e08e depends: - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.1,<3.0a0 + - libexpat >=2.7.4,<3.0a0 - libffi >=3.5.2,<3.6.0a0 - - liblzma >=5.8.1,<6.0a0 - - libsqlite >=3.50.4,<4.0a0 + - liblzma >=5.8.2,<6.0a0 + - libsqlite >=3.51.2,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.4,<4.0a0 - - readline >=8.2,<9.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata constrains: - python_abi 3.11.* *_cp311 license: Python-2.0 purls: [] - size: 14788204 - timestamp: 1761174033541 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.11-hfc2f54d_100_cp313.conda + size: 14753109 + timestamp: 1772730203101 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.12-h20e6be0_100_cp313.conda build_number: 100 - sha256: c476f4e9b6d97c46b496b442878924868a54e5727251549ebfc82027aa52af68 - md5: 18a8c69608151098a8fb75eea64cc266 + sha256: 9a4f16a64def0853f0a7b6a7beb40d498fd6b09bee10b90c3d6069b664156817 + md5: 179c0f5ae4f22bc3be567298ed0b17b9 depends: - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 - libexpat >=2.7.3,<3.0a0 - libffi >=3.5.2,<3.6.0a0 - - liblzma >=5.8.1,<6.0a0 + - liblzma >=5.8.2,<6.0a0 - libmpdec >=4.0.0,<5.0a0 - - libsqlite >=3.51.1,<4.0a0 + - libsqlite >=3.51.2,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.4,<4.0a0 + - openssl >=3.5.5,<4.0a0 - python_abi 3.13.* *_cp313 - - readline >=8.2,<9.0a0 + - readline >=8.3,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata license: Python-2.0 purls: [] - size: 12920650 - timestamp: 1765020887340 + size: 12770674 + timestamp: 1770272314517 python_site_packages_path: lib/python3.13/site-packages -- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.14-h0159041_2_cpython.conda - build_number: 2 - sha256: d5f455472597aefcdde1bc39bca313fcb40bf084f3ad987da0441f2a2ec242e4 - md5: 02a9ba5950d8b78e6c9862d6ba7a5045 +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda + sha256: a1f1031088ce69bc99c82b95980c1f54e16cbd5c21f042e9c1ea25745a8fc813 + md5: d09dbf470b41bca48cbe6a78ba1e009b depends: - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.1,<3.0a0 + - libexpat >=2.7.4,<3.0a0 - libffi >=3.5.2,<3.6.0a0 - - liblzma >=5.8.1,<6.0a0 - - libsqlite >=3.50.4,<4.0a0 + - liblzma >=5.8.2,<6.0a0 + - libsqlite >=3.51.2,<4.0a0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.4,<4.0a0 + - openssl >=3.5.5,<4.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata - ucrt >=10.0.20348.0 @@ -9438,21 +10394,21 @@ packages: - python_abi 3.11.* *_cp311 license: Python-2.0 purls: [] - size: 18514691 - timestamp: 1761172844103 -- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.11-h09917c8_100_cp313.conda + size: 18416208 + timestamp: 1772728847666 +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.12-h09917c8_100_cp313.conda build_number: 100 - sha256: 0ee0402368783e1fad10025719530499c517a3dbbdfbe18351841d9b7aef1d6a - md5: 9e4c9a7ee9c4ab5b3778ab73e583283e + sha256: da70aec20ff5a5ae18bbba9fdd1e18190b419605cafaafb3bdad8becf11ce94d + md5: 4440c24966d0aa0c8f1e1d5006dac2d6 depends: - bzip2 >=1.0.8,<2.0a0 - libexpat >=2.7.3,<3.0a0 - libffi >=3.5.2,<3.6.0a0 - - liblzma >=5.8.1,<6.0a0 + - liblzma >=5.8.2,<6.0a0 - libmpdec >=4.0.0,<5.0a0 - - libsqlite >=3.51.1,<4.0a0 + - libsqlite >=3.51.2,<4.0a0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.4,<4.0a0 + - openssl >=3.5.5,<4.0a0 - python_abi 3.13.* *_cp313 - tk >=8.6.13,<8.7.0a0 - tzdata @@ -9461,8 +10417,8 @@ packages: - vc14_runtime >=14.44.35208 license: Python-2.0 purls: [] - size: 16617922 - timestamp: 1765019627175 + size: 16535316 + timestamp: 1770270322707 python_site_packages_path: Lib/site-packages - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl name: python-dateutil @@ -9471,17 +10427,35 @@ packages: requires_dist: - six>=1.5 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' -- pypi: https://files.pythonhosted.org/packages/50/74/c655a6eda0fd188d490c14142a0f0380655ac7099604e1fbf8fa1a97f0a1/python_engineio-4.13.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + name: python-discovery + version: 1.1.2 + sha256: d18edd61b382d62f8bcd004a71ebaabc87df31dbefb30aeed59f4fc6afa005be + requires_dist: + - filelock>=3.15.4 + - platformdirs>=4.3.6,<5 + - furo>=2025.12.19 ; extra == 'docs' + - sphinx-autodoc-typehints>=3.6.3 ; extra == 'docs' + - sphinx>=9.1 ; extra == 'docs' + - sphinxcontrib-mermaid>=2 ; extra == 'docs' + - covdefaults>=2.3 ; extra == 'testing' + - coverage>=7.5.4 ; extra == 'testing' + - pytest-mock>=3.14 ; extra == 'testing' + - pytest>=8.3.5 ; extra == 'testing' + - setuptools>=75.1 ; extra == 'testing' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl name: python-engineio - version: 4.13.0 - sha256: 57b94eac094fa07b050c6da59f48b12250ab1cd920765f4849963e3d89ad9de3 + version: 4.13.1 + sha256: f32ad10589859c11053ad7d9bb3c9695cdf862113bfb0d20bc4d890198287399 requires_dist: - simple-websocket>=0.10.0 - requests>=2.21.0 ; extra == 'client' - websocket-client>=0.54.0 ; extra == 'client' - - aiohttp>=3.4 ; extra == 'asyncio-client' + - aiohttp>=3.11 ; extra == 'asyncio-client' - tox ; extra == 'dev' - sphinx ; extra == 'docs' + - furo ; extra == 'docs' requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl name: python-json-logger @@ -9509,10 +10483,10 @@ packages: - mkdocs-literate-nav ; extra == 'dev' - mike ; extra == 'dev' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/28/d2/2ccc2b69a187b80fda3152745670cfba936704f296a9fa54c6c8ac694d12/python_socketio-5.16.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl name: python-socketio - version: 5.16.0 - sha256: d95802961e15c7bd54ecf884c6e7644f81be8460f0a02ee66b473df58088ee8a + version: 5.16.1 + sha256: a3eb1702e92aa2f2b5d3ba00261b61f062cce51f1cfb6900bf3ab4d1934d2d35 requires_dist: - bidict>=0.21.0 - python-engineio>=4.11.0 @@ -9521,6 +10495,7 @@ packages: - aiohttp>=3.4 ; extra == 'asyncio-client' - tox ; extra == 'dev' - sphinx ; extra == 'docs' + - furo ; extra == 'docs' requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda build_number: 8 @@ -9533,19 +10508,36 @@ packages: purls: [] size: 7002 timestamp: 1752805902938 -- 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/a6/a1/409c1651c9f874d598c10f51ff586c416625601df4bca315d08baec4c3e3/pywinpty-3.0.2-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + name: pythreejs + version: 2.4.2 + sha256: 8418807163ad91f4df53b58c4e991b26214852a1236f28f1afeaadf99d095818 + requires_dist: + - ipywidgets>=7.2.1 + - ipydatawidgets>=1.1.1 + - numpy + - traitlets + - sphinx>=1.5 ; extra == 'docs' + - nbsphinx>=0.2.13 ; extra == 'docs' + - nbsphinx-link ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - scipy ; extra == 'examples' + - matplotlib ; extra == 'examples' + - scikit-image ; extra == 'examples' + - ipywebrtc ; extra == 'examples' + - nbval ; extra == 'test' + - pytest-check-links ; extra == 'test' + - numpy>=1.14 ; extra == 'test' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/79/c3/3e75075c7f71735f22b66fab0481f2c98e3a4d58cba55cb50ba29114bcf6/pywinpty-3.0.3-cp311-cp311-win_amd64.whl name: pywinpty - version: 3.0.2 - sha256: 327790d70e4c841ebd9d0f295a780177149aeb405bca44c7115a3de5c2054b23 + version: 3.0.3 + sha256: dff25a9a6435f527d7c65608a7e62783fc12076e7d44487a4911ee91be5a8ac8 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl name: pywinpty - version: 3.0.2 - sha256: 18f78b81e4cfee6aabe7ea8688441d30247b73e52cd9657138015c5f4ee13a51 + version: 3.0.3 + sha256: 9c91dbb026050c77bdcef964e63a4f10f01a639113c4d3658332614544c467ab 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 @@ -9730,10 +10722,10 @@ packages: - 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 +- pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl name: rich - version: 14.2.0 - sha256: 76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd + version: 14.3.3 + sha256: 793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d requires_dist: - ipywidgets>=7.5.1,<9 ; extra == 'jupyter' - markdown-it-py>=2.2.0 @@ -9779,32 +10771,296 @@ packages: version: 0.30.0 sha256: e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl name: ruff - version: 0.14.10 - sha256: d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d + version: 0.15.5 + sha256: 6edd3792d408ebcf61adabc01822da687579a1a023f297618ac27a5b51ef0080 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl name: ruff - version: 0.14.10 - sha256: 59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f + version: 0.15.5 + sha256: 821d41c5fa9e19117616c35eaa3f4b75046ec76c65e7ae20a333e9a8696bc7fe requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl name: ruff - version: 0.14.10 - sha256: 674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f + version: 0.15.5 + sha256: 89f463f7c8205a9f8dea9d658d59eff49db05f88f89cc3047fb1a02d9f344010 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: ruff - version: 0.14.10 - sha256: 466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154 + version: 0.15.5 + sha256: c1cb7169f53c1ddb06e71a9aebd7e98fc0fea936b39afb36d8e86d36ecc2636a requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + name: sciline + version: 25.11.1 + sha256: 13c378287b8157e819b9b67d7e973c65bc6bdc545a3602d18204c365b0c336f9 + requires_dist: + - cyclebane>=24.6.0 + - pytest ; extra == 'test' + - pytest-randomly>=3 ; extra == 'test' + - dask ; extra == 'test' + - graphviz ; extra == 'test' + - jsonschema ; extra == 'test' + - numpy ; extra == 'test' + - pandas ; extra == 'test' + - pydantic ; extra == 'test' + - rich ; extra == 'test' + - rich ; extra == 'progress' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/12/0d/3f98a936a30bff4a460b51b9f85c4d994f94249930b2d8bedeb8111a359e/scipp-25.12.0-cp311-cp311-macosx_11_0_x86_64.whl + name: scipp + version: 25.12.0 + sha256: 6391dc46739006e1e4eb7f2fcbcdbdd40f11a3cbae53e93b63b5ba32909bd792 + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/18/5c/bc0ca94bff65fe0d206a369b54625f8ec7852dfd1d835174692026f34df9/scipp-25.12.0-cp313-cp313-macosx_11_0_arm64.whl + name: scipp + version: 25.12.0 + sha256: 0285c91b202dea9aeb18f29d73ff135208a19b2068cd30e17ee81fc435b1943c + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/4e/b6/ffe0bb67cec66cd450acff599bb07507bbf5ffda1a3a15dd2d8dbe7a6da7/scipp-25.12.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: scipp + version: 25.12.0 + sha256: 9ec0200eeb660965056b27f5f05505a961d8921b78d0a92c04743d1c22ce7203 + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/6a/3a/ab0eb61593569d5a0d080002e4b8c0998cb1116d8710781b7225c304b880/scipp-25.12.0-cp311-cp311-macosx_11_0_arm64.whl + name: scipp + version: 25.12.0 + sha256: e27082f5bd3655f15479ce87be495235b9bcd9b5db654a7219261be0c6117b31 + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/94/98/aa2d4b9d28969cc7f62409f9f9fc5b5a853af651255eba03e9bee8546dd9/scipp-25.12.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: scipp + version: 25.12.0 + sha256: e5694bef45299c4813ee2fe863fab600c3b0f5e13e2735072dbbb5cf1804d0e0 + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/bd/75/6a3786de6645ac2ccd94fbf83c59cc6b929bfa3a89cb62c8cb3be4de0606/scipp-25.12.0-cp311-cp311-win_amd64.whl + name: scipp + version: 25.12.0 + sha256: aee5f585232e2a7a664f57bb9695164715271b74896704e7ee8a669dd7b06008 + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/e5/22/75e119e0a200914f88f121cd956e1eb7f72c8ace4b63171f52ba0334d142/scipp-25.12.0-cp313-cp313-macosx_11_0_x86_64.whl + name: scipp + version: 25.12.0 + sha256: 27ebc37f3b7e20c9dca9cf1a0ac53c4ffaea31c02dfc8ba2b5aa008a252cbcba + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/eb/d1/b3cd2733a96a36c54c36889b2cfdd0331c1e5b57fa1757485a22d0ec3142/scipp-25.12.0-cp313-cp313-win_amd64.whl + name: scipp + version: 25.12.0 + sha256: 015db5035749750cf026db56fe537af10f159dc3cd0f51f0ea9f4ecc3a7a5da8 + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl + name: scippneutron + version: 26.3.0 + sha256: 6bc9e36f68059bb792460cc897e6247236289f170134a953ed9fee8578872dd7 + requires_dist: + - python-dateutil>=2.8 + - email-validator>=2 + - h5py>=3.12 + - lazy-loader>=0.4 + - mpltoolbox>=24.6.0 + - numpy>=1.20 + - plopp>=24.9.1 + - pydantic>=2 + - scipp>=24.7.0 + - scippnexus>=23.11.0 + - scipy>=1.7.0 + - scipp[all]>=23.7.0 ; extra == 'all' + - pooch>=1.5 ; extra == 'all' + - hypothesis>=6.100 ; extra == 'test' + - ipympl>0.9.0 ; extra == 'test' + - ipykernel>6.30 ; extra == 'test' + - pace-neutrons>=0.3 ; extra == 'test' + - pooch>=1.5 ; extra == 'test' + - psutil>=5.0 ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - pytest-xdist>=3.0 ; extra == 'test' + - pythreejs>=2.4.1 ; extra == 'test' + - sciline>=25.1.0 ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + name: scippnexus + version: 26.1.1 + sha256: 899a0a5e71291b7809d902c17b6c74addf5a805397eabcec557491ff74eead12 + requires_dist: + - scipp>=24.2.0 + - scipy>=1.10.0 + - h5py>=3.12 + - pytest>=7.0 ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: scipy - version: 1.16.3 - sha256: 7dc1360c06535ea6116a2220f760ae572db9f661aba2d88074fe30ec2aa1ff88 + version: 1.17.1 + sha256: 43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4 requires_dist: - - numpy>=1.25.2,<2.6 + - numpy>=1.26.4,<2.7 - pytest>=8.0.0 ; extra == 'test' - pytest-cov ; extra == 'test' - pytest-timeout ; extra == 'test' @@ -9833,22 +11089,22 @@ packages: - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' - jupyterlite-pyodide-kernel ; extra == 'doc' - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' - mypy==1.10.0 ; extra == 'dev' - typing-extensions ; extra == 'dev' - types-psutil ; extra == 'dev' - pycodestyle ; extra == 'dev' - - ruff>=0.0.292 ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' - cython-lint>=0.12.2 ; extra == 'dev' - - rich-click ; extra == 'dev' - - doit>=0.36.0 ; extra == 'dev' - - pydevtool ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl name: scipy - version: 1.16.3 - sha256: 16b8bc35a4cc24db80a0ec836a9286d0e31b2503cb2fd7ff7fb0e0374a97081d + version: 1.17.1 + sha256: 37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448 requires_dist: - - numpy>=1.25.2,<2.6 + - numpy>=1.26.4,<2.7 - pytest>=8.0.0 ; extra == 'test' - pytest-cov ; extra == 'test' - pytest-timeout ; extra == 'test' @@ -9877,22 +11133,22 @@ packages: - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' - jupyterlite-pyodide-kernel ; extra == 'doc' - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' - mypy==1.10.0 ; extra == 'dev' - typing-extensions ; extra == 'dev' - types-psutil ; extra == 'dev' - pycodestyle ; extra == 'dev' - - ruff>=0.0.292 ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' - cython-lint>=0.12.2 ; extra == 'dev' - - rich-click ; extra == 'dev' - - doit>=0.36.0 ; extra == 'dev' - - pydevtool ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl name: scipy - version: 1.16.3 - sha256: d2ec56337675e61b312179a1ad124f5f570c00f920cc75e1000025451b88241c + version: 1.17.1 + sha256: 7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d requires_dist: - - numpy>=1.25.2,<2.6 + - numpy>=1.26.4,<2.7 - pytest>=8.0.0 ; extra == 'test' - pytest-cov ; extra == 'test' - pytest-timeout ; extra == 'test' @@ -9921,22 +11177,22 @@ packages: - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' - jupyterlite-pyodide-kernel ; extra == 'doc' - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' - mypy==1.10.0 ; extra == 'dev' - typing-extensions ; extra == 'dev' - types-psutil ; extra == 'dev' - pycodestyle ; extra == 'dev' - - ruff>=0.0.292 ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' - cython-lint>=0.12.2 ; extra == 'dev' - - rich-click ; extra == 'dev' - - doit>=0.36.0 ; extra == 'dev' - - pydevtool ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/7c/89/d70e9f628749b7e4db2aa4cd89735502ff3f08f7b9b27d2e799485987cd9/scipy-1.16.3-cp311-cp311-macosx_12_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl name: scipy - version: 1.16.3 - sha256: 8be1ca9170fcb6223cc7c27f4305d680ded114a1567c0bd2bfcbf947d1b17511 + version: 1.17.1 + sha256: a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee requires_dist: - - numpy>=1.25.2,<2.6 + - numpy>=1.26.4,<2.7 - pytest>=8.0.0 ; extra == 'test' - pytest-cov ; extra == 'test' - pytest-timeout ; extra == 'test' @@ -9965,22 +11221,22 @@ packages: - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' - jupyterlite-pyodide-kernel ; extra == 'doc' - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' - mypy==1.10.0 ; extra == 'dev' - typing-extensions ; extra == 'dev' - types-psutil ; extra == 'dev' - pycodestyle ; extra == 'dev' - - ruff>=0.0.292 ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' - cython-lint>=0.12.2 ; extra == 'dev' - - rich-click ; extra == 'dev' - - doit>=0.36.0 ; extra == 'dev' - - pydevtool ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/9b/5f/6f37d7439de1455ce9c5a556b8d1db0979f03a796c030bafdf08d35b7bf9/scipy-1.16.3-cp311-cp311-macosx_10_14_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl name: scipy - version: 1.16.3 - sha256: 40be6cf99e68b6c4321e9f8782e7d5ff8265af28ef2cd56e9c9b2638fa08ad97 + version: 1.17.1 + sha256: d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff requires_dist: - - numpy>=1.25.2,<2.6 + - numpy>=1.26.4,<2.7 - pytest>=8.0.0 ; extra == 'test' - pytest-cov ; extra == 'test' - pytest-timeout ; extra == 'test' @@ -10009,22 +11265,22 @@ packages: - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' - jupyterlite-pyodide-kernel ; extra == 'doc' - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' - mypy==1.10.0 ; extra == 'dev' - typing-extensions ; extra == 'dev' - types-psutil ; extra == 'dev' - pycodestyle ; extra == 'dev' - - ruff>=0.0.292 ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' - cython-lint>=0.12.2 ; extra == 'dev' - - rich-click ; extra == 'dev' - - doit>=0.36.0 ; extra == 'dev' - - pydevtool ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/ca/6e/8942461cf2636cdae083e3eb72622a7fbbfa5cf559c7d13ab250a5dbdc01/scipy-1.16.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl name: scipy - version: 1.16.3 - sha256: 0151a0749efeaaab78711c78422d413c583b8cdd2011a3c1d6c794938ee9fdb2 + version: 1.17.1 + sha256: 4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b requires_dist: - - numpy>=1.25.2,<2.6 + - numpy>=1.26.4,<2.7 - pytest>=8.0.0 ; extra == 'test' - pytest-cov ; extra == 'test' - pytest-timeout ; extra == 'test' @@ -10053,22 +11309,22 @@ packages: - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' - jupyterlite-pyodide-kernel ; extra == 'doc' - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' - mypy==1.10.0 ; extra == 'dev' - typing-extensions ; extra == 'dev' - types-psutil ; extra == 'dev' - pycodestyle ; extra == 'dev' - - ruff>=0.0.292 ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' - cython-lint>=0.12.2 ; extra == 'dev' - - rich-click ; extra == 'dev' - - doit>=0.36.0 ; extra == 'dev' - - pydevtool ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl name: scipy - version: 1.16.3 - sha256: 062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304 + version: 1.17.1 + sha256: 766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd requires_dist: - - numpy>=1.25.2,<2.6 + - numpy>=1.26.4,<2.7 - pytest>=8.0.0 ; extra == 'test' - pytest-cov ; extra == 'test' - pytest-timeout ; extra == 'test' @@ -10097,22 +11353,22 @@ packages: - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' - jupyterlite-pyodide-kernel ; extra == 'doc' - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' - mypy==1.10.0 ; extra == 'dev' - typing-extensions ; extra == 'dev' - types-psutil ; extra == 'dev' - pycodestyle ; extra == 'dev' - - ruff>=0.0.292 ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' - cython-lint>=0.12.2 ; extra == 'dev' - - rich-click ; extra == 'dev' - - doit>=0.36.0 ; extra == 'dev' - - pydevtool ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/f1/d0/22ec7036ba0b0a35bccb7f25ab407382ed34af0b111475eb301c16f8a2e5/scipy-1.16.3-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: scipy - version: 1.16.3 - sha256: 53c3844d527213631e886621df5695d35e4f6a75f620dca412bcd292f6b87d78 + version: 1.17.1 + sha256: 581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464 requires_dist: - - numpy>=1.25.2,<2.6 + - numpy>=1.26.4,<2.7 - pytest>=8.0.0 ; extra == 'test' - pytest-cov ; extra == 'test' - pytest-timeout ; extra == 'test' @@ -10141,30 +11397,29 @@ packages: - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' - jupyterlite-pyodide-kernel ; extra == 'doc' - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' - mypy==1.10.0 ; extra == 'dev' - typing-extensions ; extra == 'dev' - types-psutil ; extra == 'dev' - pycodestyle ; extra == 'dev' - - ruff>=0.0.292 ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' - cython-lint>=0.12.2 ; extra == 'dev' - - rich-click ; extra == 'dev' - - doit>=0.36.0 ; extra == 'dev' - - pydevtool ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/1b/5a/f2f2e5eda25579f754acd83399c522ee03d6acbe001dfe53c8a1ec928b44/send2trash-2.0.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl name: send2trash - version: 2.0.0 - sha256: e70d5ce41dbb890882cc78bc25d137478330b39a391e756fadf82e34da4d85b8 - requires_dist: - - pywin32 ; sys_platform == 'win32' and extra == 'win32' - - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'objc' - - pywin32 ; sys_platform == 'win32' and extra == 'nativelib' - - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'nativelib' - requires_python: '!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*' -- pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl + version: 2.1.0 + sha256: 0da2f112e6d6bb22de6aa6daa7e144831a4febf2a87261451c4ad849fe9a873c + requires_dist: + - pytest>=8 ; extra == 'test' + - pywin32>=305 ; sys_platform == 'win32' and extra == 'nativelib' + - pyobjc>=9.0 ; sys_platform == 'darwin' and extra == 'nativelib' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl name: setuptools - version: 80.9.0 - sha256: 062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 + version: 82.0.1 + sha256: a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb requires_dist: - pytest>=6,!=8.1.* ; extra == 'test' - virtualenv>=13.0.0 ; extra == 'test' @@ -10205,16 +11460,15 @@ packages: - 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' + - ruff>=0.13.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' + - mypy==1.18.* ; 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' @@ -10240,15 +11494,261 @@ packages: version: 1.17.0 sha256: 4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' -- pypi: https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl name: soupsieve - version: 2.8.1 - sha256: a11fe2a6f3d76ab3cf2de04eb339c1be5b506a8a47f2ceb6d139803177f85434 + version: 2.8.3 + sha256: ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/16/56/a31e8d3c9e8d21100b83bbe1c1f3f7c94db317393a229e193461e5e6d2a4/spglib-2.6.0-cp313-cp313-win_amd64.whl + name: spglib + version: 2.6.0 + sha256: ff1632524f6ac0031423474e48d6b69f4932ecb7eb4446a501f59619e2b5cbc9 + requires_dist: + - numpy>=1.20,<3 + - importlib-resources ; python_full_version < '3.10' + - typing-extensions>=4.9.0 ; python_full_version < '3.13' + - pytest ; extra == 'test' + - pyyaml ; extra == 'test' + - sphinx>=7.0 ; extra == 'docs' + - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - myst-parser>=2.0 ; extra == 'docs' + - linkify-it-py ; extra == 'docs' + - sphinx-tippy ; extra == 'docs' + - spglib[test] ; extra == 'test-cov' + - pytest-cov ; extra == 'test-cov' + - spglib[test] ; extra == 'test-benchmark' + - pytest-benchmark ; extra == 'test-benchmark' + - spglib[test] ; extra == 'dev' + - pre-commit ; extra == 'dev' + - spglib[docs] ; extra == 'doc' + - spglib[test] ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/1b/a5/174d33068d4383df4be9ee1ea9251f17820a622f3be744ca2ab7334818ee/spglib-2.6.0-cp313-cp313-macosx_10_13_x86_64.whl + name: spglib + version: 2.6.0 + sha256: d7872819b6448024ecf7dfccac45147d8cac839bdfff6763b92784aae63bd139 + requires_dist: + - numpy>=1.20,<3 + - importlib-resources ; python_full_version < '3.10' + - typing-extensions>=4.9.0 ; python_full_version < '3.13' + - pytest ; extra == 'test' + - pyyaml ; extra == 'test' + - sphinx>=7.0 ; extra == 'docs' + - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - myst-parser>=2.0 ; extra == 'docs' + - linkify-it-py ; extra == 'docs' + - sphinx-tippy ; extra == 'docs' + - spglib[test] ; extra == 'test-cov' + - pytest-cov ; extra == 'test-cov' + - spglib[test] ; extra == 'test-benchmark' + - pytest-benchmark ; extra == 'test-benchmark' + - spglib[test] ; extra == 'dev' + - pre-commit ; extra == 'dev' + - spglib[docs] ; extra == 'doc' + - spglib[test] ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/25/a8/d89e1bde525baba10eb8d0be79a5bbaf56c59a47b32bb954866d96a228e3/spglib-2.6.0-cp311-cp311-win_amd64.whl + name: spglib + version: 2.6.0 + sha256: 9a72daeefcabf62ca88eff77adacf5b2b9d91a17c4f93d61d86f635476de8400 + requires_dist: + - numpy>=1.20,<3 + - importlib-resources ; python_full_version < '3.10' + - typing-extensions>=4.9.0 ; python_full_version < '3.13' + - pytest ; extra == 'test' + - pyyaml ; extra == 'test' + - sphinx>=7.0 ; extra == 'docs' + - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - myst-parser>=2.0 ; extra == 'docs' + - linkify-it-py ; extra == 'docs' + - sphinx-tippy ; extra == 'docs' + - spglib[test] ; extra == 'test-cov' + - pytest-cov ; extra == 'test-cov' + - spglib[test] ; extra == 'test-benchmark' + - pytest-benchmark ; extra == 'test-benchmark' + - spglib[test] ; extra == 'dev' + - pre-commit ; extra == 'dev' + - spglib[docs] ; extra == 'doc' + - spglib[test] ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/2d/47/634fe8323c6c2bfa86e10eb41ebfe410db5e6231aa1727a31ce4f002480f/spglib-2.6.0-cp313-cp313-macosx_11_0_arm64.whl + name: spglib + version: 2.6.0 + sha256: 12db7a0d6ad84c55e61eda67590a438edeb48e57ffd5df868cd931b57fb8c630 + requires_dist: + - numpy>=1.20,<3 + - importlib-resources ; python_full_version < '3.10' + - typing-extensions>=4.9.0 ; python_full_version < '3.13' + - pytest ; extra == 'test' + - pyyaml ; extra == 'test' + - sphinx>=7.0 ; extra == 'docs' + - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - myst-parser>=2.0 ; extra == 'docs' + - linkify-it-py ; extra == 'docs' + - sphinx-tippy ; extra == 'docs' + - spglib[test] ; extra == 'test-cov' + - pytest-cov ; extra == 'test-cov' + - spglib[test] ; extra == 'test-benchmark' + - pytest-benchmark ; extra == 'test-benchmark' + - spglib[test] ; extra == 'dev' + - pre-commit ; extra == 'dev' + - spglib[docs] ; extra == 'doc' + - spglib[test] ; extra == 'testing' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/0e/50/80a8d080ac7d3d321e5e5d420c9a522b0aa770ec7013ea91f9a8b7d36e4a/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/53/01/1c0485ae02e645bc517bf5d5a6ca674f62c97e247890b954cbfe85c64dae/spglib-2.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: spglib + version: 2.6.0 + sha256: cc9d856d7cc936dc73b6b303aaf8b2fb4230a8527659373450c6e1139cbb2a5c + requires_dist: + - numpy>=1.20,<3 + - importlib-resources ; python_full_version < '3.10' + - typing-extensions>=4.9.0 ; python_full_version < '3.13' + - pytest ; extra == 'test' + - pyyaml ; extra == 'test' + - sphinx>=7.0 ; extra == 'docs' + - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - myst-parser>=2.0 ; extra == 'docs' + - linkify-it-py ; extra == 'docs' + - sphinx-tippy ; extra == 'docs' + - spglib[test] ; extra == 'test-cov' + - pytest-cov ; extra == 'test-cov' + - spglib[test] ; extra == 'test-benchmark' + - pytest-benchmark ; extra == 'test-benchmark' + - spglib[test] ; extra == 'dev' + - pre-commit ; extra == 'dev' + - spglib[docs] ; extra == 'doc' + - spglib[test] ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/55/41/591cd1e94254c20f00bb1f32c0b1a6de68c03d54e6daf78dd7b146d0b3fc/spglib-2.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: spglib + version: 2.6.0 + sha256: ff6b2f21226f7ece7758eb1d320907168018aa0a30a57c2b0a24cbf8f6860211 + requires_dist: + - numpy>=1.20,<3 + - importlib-resources ; python_full_version < '3.10' + - typing-extensions>=4.9.0 ; python_full_version < '3.13' + - pytest ; extra == 'test' + - pyyaml ; extra == 'test' + - sphinx>=7.0 ; extra == 'docs' + - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - myst-parser>=2.0 ; extra == 'docs' + - linkify-it-py ; extra == 'docs' + - sphinx-tippy ; extra == 'docs' + - spglib[test] ; extra == 'test-cov' + - pytest-cov ; extra == 'test-cov' + - spglib[test] ; extra == 'test-benchmark' + - pytest-benchmark ; extra == 'test-benchmark' + - spglib[test] ; extra == 'dev' + - pre-commit ; extra == 'dev' + - spglib[docs] ; extra == 'doc' + - spglib[test] ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/6c/44/30888e2a5b2fa2e6df18606b442cb8b126b0bea5a2f1ec4a2a82538ffecf/spglib-2.6.0-cp311-cp311-macosx_10_9_x86_64.whl + name: spglib + version: 2.6.0 + sha256: c22d87849e1086cbe88399c08c93b4e7babec92c1db49f15ef8b081339b67e25 + requires_dist: + - numpy>=1.20,<3 + - importlib-resources ; python_full_version < '3.10' + - typing-extensions>=4.9.0 ; python_full_version < '3.13' + - pytest ; extra == 'test' + - pyyaml ; extra == 'test' + - sphinx>=7.0 ; extra == 'docs' + - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - myst-parser>=2.0 ; extra == 'docs' + - linkify-it-py ; extra == 'docs' + - sphinx-tippy ; extra == 'docs' + - spglib[test] ; extra == 'test-cov' + - pytest-cov ; extra == 'test-cov' + - spglib[test] ; extra == 'test-benchmark' + - pytest-benchmark ; extra == 'test-benchmark' + - spglib[test] ; extra == 'dev' + - pre-commit ; extra == 'dev' + - spglib[docs] ; extra == 'doc' + - spglib[test] ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f6/ca/270d463f6c34f539bb55acdab14099c092d3be28c8af64d61399aa07610c/spglib-2.6.0-cp311-cp311-macosx_11_0_arm64.whl + name: spglib + version: 2.6.0 + sha256: 02d2e730a3b2cb43e318944493d0c288592b0e2ddbab0d222a548312659ee22a + requires_dist: + - numpy>=1.20,<3 + - importlib-resources ; python_full_version < '3.10' + - typing-extensions>=4.9.0 ; python_full_version < '3.13' + - pytest ; extra == 'test' + - pyyaml ; extra == 'test' + - sphinx>=7.0 ; extra == 'docs' + - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - myst-parser>=2.0 ; extra == 'docs' + - linkify-it-py ; extra == 'docs' + - sphinx-tippy ; extra == 'docs' + - spglib[test] ; extra == 'test-cov' + - pytest-cov ; extra == 'test-cov' + - spglib[test] ; extra == 'test-benchmark' + - pytest-benchmark ; extra == 'test-benchmark' + - spglib[test] ; extra == 'dev' + - pre-commit ; extra == 'dev' + - spglib[docs] ; extra == 'doc' + - spglib[test] ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/21/dd/3b7c53f1dbbf736fd27041aee68f8ac52226b610f914085b1652c2323442/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: sqlalchemy + version: 2.0.48 + sha256: 6f7b7243850edd0b8b97043f04748f31de50cf426e939def5c16bedb540698f7 + requires_dist: + - importlib-metadata ; python_full_version < '3.8' + - 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' + - pyodbc ; extra == 'mssql' + - pymssql ; extra == 'mssql-pymssql' + - pyodbc ; extra == 'mssql-pyodbc' + - mysqlclient>=1.4.0 ; extra == 'mysql' + - mysql-connector-python ; extra == 'mysql-connector' + - mariadb>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10 ; extra == 'mariadb-connector' + - cx-oracle>=8 ; extra == 'oracle' + - oracledb>=1.0.1 ; extra == 'oracle-oracledb' + - psycopg2>=2.7 ; extra == 'postgresql' + - pg8000>=1.29.1 ; extra == 'postgresql-pg8000' + - greenlet>=1 ; extra == 'postgresql-asyncpg' + - asyncpg ; extra == 'postgresql-asyncpg' + - psycopg2-binary ; extra == 'postgresql-psycopg2binary' + - psycopg2cffi ; extra == 'postgresql-psycopg2cffi' + - psycopg>=3.0.7 ; extra == 'postgresql-psycopg' + - psycopg[binary]>=3.0.7 ; extra == 'postgresql-psycopgbinary' + - pymysql ; extra == 'pymysql' + - greenlet>=1 ; extra == 'aiomysql' + - aiomysql>=0.2.0 ; extra == 'aiomysql' + - greenlet>=1 ; extra == 'aioodbc' + - aioodbc ; extra == 'aioodbc' + - greenlet>=1 ; extra == 'asyncmy' + - asyncmy>=0.2.3,!=0.2.4,!=0.2.6 ; extra == 'asyncmy' + - greenlet>=1 ; extra == 'aiosqlite' + - aiosqlite ; extra == 'aiosqlite' + - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' + - sqlcipher3-binary ; extra == 'sqlcipher' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl name: sqlalchemy - version: 2.0.45 - sha256: 672c45cae53ba88e0dad74b9027dddd09ef6f441e927786b05bec75d949fbb2e + version: 2.0.48 + sha256: a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096 requires_dist: - importlib-metadata ; python_full_version < '3.8' - 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' @@ -10283,10 +11783,10 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/a2/1c/769552a9d840065137272ebe86ffbb0bc92b0f1e0a68ee5266a225f8cd7b/sqlalchemy-2.0.45-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/58/d5/dd767277f6feef12d05651538f280277e661698f617fa4d086cce6055416/sqlalchemy-2.0.48-cp311-cp311-win_amd64.whl name: sqlalchemy - version: 2.0.45 - sha256: 2e90a344c644a4fa871eb01809c32096487928bd2038bf10f3e4515cb688cc56 + version: 2.0.48 + sha256: 583849c743e0e3c9bb7446f5b5addeacedc168d657a69b418063dfdb2d90081c requires_dist: - importlib-metadata ; python_full_version < '3.8' - 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' @@ -10321,10 +11821,10 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/bc/fa/09d0a11fe9f15c7fa5c7f0dd26be3d235b0c0cbf2f9544f43bc42efc8a24/sqlalchemy-2.0.45-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl name: sqlalchemy - version: 2.0.45 - sha256: a15b98adb7f277316f2c276c090259129ee4afca783495e212048daf846654b2 + version: 2.0.48 + sha256: d612c976cbc2d17edfcc4c006874b764e85e990c29ce9bd411f926bbfb02b9a2 requires_dist: - importlib-metadata ; python_full_version < '3.8' - 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' @@ -10359,10 +11859,10 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl name: sqlalchemy - version: 2.0.45 - sha256: 5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0 + version: 2.0.48 + sha256: e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4 requires_dist: - importlib-metadata ; python_full_version < '3.8' - 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' @@ -10397,10 +11897,10 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/c0/c5/d17113020b2d43073412aeca09b60d2009442420372123b8d49cc253f8b8/sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/d7/6d/b8b78b5b80f3c3ab3f7fa90faa195ec3401f6d884b60221260fd4d51864c/sqlalchemy-2.0.48-cp311-cp311-macosx_11_0_arm64.whl name: sqlalchemy - version: 2.0.45 - sha256: afbf47dc4de31fa38fd491f3705cac5307d21d4bb828a4f020ee59af412744ee + version: 2.0.48 + sha256: 1b4c575df7368b3b13e0cebf01d4679f9a28ed2ae6c1cd0b1d5beffb6b2007dc requires_dist: - importlib-metadata ; python_full_version < '3.8' - 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' @@ -10435,10 +11935,10 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/f6/2b/60ce3ee7a5ae172bfcd419ce23259bb874d2cddd44f67c5df3760a1e22f9/sqlalchemy-2.0.45-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: sqlalchemy - version: 2.0.45 - sha256: 12c694ed6468333a090d2f60950e4250b928f457e4962389553d6ba5fe9951ac + version: 2.0.48 + sha256: b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed requires_dist: - importlib-metadata ; python_full_version < '3.8' - 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' @@ -10495,26 +11995,26 @@ packages: - pytest>=7.1.0 ; extra == 'dev' - hypothesis>=6.70.0 ; extra == 'dev' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl name: tabulate - version: 0.9.0 - sha256: 024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f + version: 0.10.0 + sha256: f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3 requires_dist: - wcwidth ; extra == 'widechars' - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-hd094cb3_1.conda - sha256: c31cac57913a699745d124cdc016a63e31c5749f16f60b3202414d071fc50573 - md5: 17c38aaf14c640b85c4617ccb59c1146 + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-h3155e25_2.conda + sha256: abd9a489f059fba85c8ffa1abdaa4d515d6de6a3325238b8e81203b913cf65a9 + md5: 0f9817ffbe25f9e69ceba5ea70c52606 depends: - - libhwloc >=2.12.1,<2.12.2.0a0 + - libhwloc >=2.12.2,<2.12.3.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: Apache-2.0 license_family: APACHE purls: [] - size: 155714 - timestamp: 1762510341121 + size: 155869 + timestamp: 1767886839029 - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl name: terminado version: 0.18.1 @@ -10543,99 +12043,116 @@ packages: - pytest ; extra == 'test' - ruff ; extra == 'test' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda - sha256: 1544760538a40bcd8ace2b1d8ebe3eb5807ac268641f8acdc18c69c5ebfeaf64 - md5: 86bc20552bf46075e3d92b67f089172d +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac + md5: cffd3bdd58090148f4cfcd831f4b26ab depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 - libzlib >=1.3.1,<2.0a0 constrains: - xorg-libx11 >=1.8.12,<2.0a0 license: TCL license_family: BSD purls: [] - size: 3284905 - timestamp: 1763054914403 -- conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_3.conda - sha256: 0d0b6cef83fec41bc0eb4f3b761c4621b7adfb14378051a8177bd9bb73d26779 - md5: bd9f1de651dbd80b51281c694827f78f + size: 3301196 + timestamp: 1769460227866 +- conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda + sha256: 7f0d9c320288532873e2d8486c331ec6d87919c9028208d3f6ac91dc8f99a67b + md5: 6e6efb7463f8cef69dbcb4c2205bf60e depends: - __osx >=10.13 - libzlib >=1.3.1,<2.0a0 license: TCL license_family: BSD purls: [] - size: 3262702 - timestamp: 1763055085507 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda - sha256: ad0c67cb03c163a109820dc9ecf77faf6ec7150e942d1e8bb13e5d39dc058ab7 - md5: a73d54a5abba6543cb2f0af1bfbd6851 + size: 3282953 + timestamp: 1769460532442 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + sha256: 799cab4b6cde62f91f750149995d149bc9db525ec12595e8a1d91b9317f038b3 + md5: a9d86bc62f39b94c4661716624eb21b0 depends: - __osx >=11.0 - libzlib >=1.3.1,<2.0a0 license: TCL license_family: BSD purls: [] - size: 3125484 - timestamp: 1763055028377 -- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_3.conda - sha256: 4581f4ffb432fefa1ac4f85c5682cc27014bcd66e7beaa0ee330e927a7858790 - md5: 7cb36e506a7dba4817970f8adb6396f9 + size: 3127137 + timestamp: 1769460817696 +- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + sha256: 0e79810fae28f3b69fe7391b0d43f5474d6bd91d451d5f2bde02f55ae481d5e3 + md5: 0481bfd9814bf525bd4b3ee4b51494c4 depends: - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 license: TCL license_family: BSD purls: [] - size: 3472313 - timestamp: 1763055164278 + size: 3526350 + timestamp: 1769460339384 +- pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + name: tof + version: 26.1.0 + sha256: 2603a7b94a7296ee503a0edadb314701431808c8ededb5c442334f89633962a5 + requires_dist: + - plopp>=23.10.0 + - pooch>=1.5.0 + - scipp>=25.1.0 + - lazy-loader>=0.3 + - pytest>=8.0 ; extra == 'test' + - scippneutron>=24.12.0 ; extra == 'test' + requires_python: '>=3.11' - 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 +- pypi: https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl name: tomli - version: 2.3.0 - sha256: 4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be + version: 2.4.0 + sha256: d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa requires_python: '>=3.8' -- 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/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl name: tomli - version: 2.3.0 - sha256: 883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba + version: 2.4.0 + sha256: 9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e 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 +- pypi: https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl name: tomli - version: 2.3.0 - sha256: 5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b + version: 2.4.0 + sha256: 84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0 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 +- pypi: https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl name: tomli - version: 2.3.0 - sha256: a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441 + version: 2.4.0 + sha256: 3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl name: tomli - version: 2.3.0 - sha256: 0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999 + version: 2.4.0 + sha256: b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867 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 +- pypi: https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl name: tomli - version: 2.3.0 - sha256: 88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45 + version: 2.4.0 + sha256: 5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9 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 +- pypi: https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: tomli - version: 2.3.0 - sha256: 4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf + version: 2.4.0 + sha256: 1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e requires_python: '>=3.8' -- 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/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: tomli - version: 2.3.0 - sha256: be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae + version: 2.4.0 + sha256: 5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76 requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl + name: toolz + version: 1.1.0 + sha256: 15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8 + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: tornado version: 6.5.4 @@ -10671,33 +12188,50 @@ packages: - pytest-mypy-testing ; extra == 'test' - pytest>=7.0,<8.2 ; extra == 'test' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + name: traittypes + version: 0.2.3 + sha256: 49016082ce740d6556d9bb4672ee2d899cd14f9365f17cbb79d5d96b47096d4e + requires_dist: + - traitlets>=4.2.2 + - numpy ; extra == 'test' + - pandas ; extra == 'test' + - xarray ; extra == 'test' + - pytest ; extra == 'test' +- pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl name: trove-classifiers - version: 2025.12.1.14 - sha256: a8206978ede95937b9959c3aff3eb258bbf7b07dff391ddd4ea7e61f316635ab -- pypi: https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl + version: 2026.1.14.14 + sha256: 1f9553927f18d0513d8e5ff80ab8980b8202ce37ecae0e3274ed2ef11880e74d +- pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl name: typeguard - version: 4.4.4 - sha256: b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e + version: 4.5.1 + sha256: 44d2bf329d49a244110a090b55f5f91aa82d9a9834ebfd30bcc73651e4a8cc40 requires_dist: - importlib-metadata>=3.6 ; python_full_version < '3.10' - typing-extensions>=4.14.0 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl name: typer - version: 0.21.1 - sha256: 7985e89081c636b88d172c2ee0cfe33c253160994d47bdfdc302defd7d1f1d01 + version: 0.24.1 + sha256: 112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e requires_dist: - - click>=8.0.0 - - typing-extensions>=3.7.4.3 + - click>=8.2.1 - shellingham>=1.3.0 - - rich>=10.11.0 - requires_python: '>=3.9' + - rich>=12.3.0 + - annotated-doc>=0.0.2 + requires_python: '>=3.10' - 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/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl + name: typing-inspection + version: 0.4.2 + sha256: 4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7 + requires_dist: + - typing-extensions>=4.12.0 + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl name: tzdata version: '2025.3' @@ -10765,10 +12299,10 @@ packages: - flake8-use-fstring ; extra == 'dev' - pep8-naming ; extra == 'dev' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl name: urllib3 - version: 2.6.2 - sha256: ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd + version: 2.6.3 + sha256: bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4 requires_dist: - brotli>=1.2.0 ; platform_python_implementation == 'CPython' and extra == 'brotli' - brotlicffi>=1.2.0.0 ; platform_python_implementation != 'CPython' and extra == 'brotli' @@ -10776,30 +12310,30 @@ packages: - pysocks>=1.5.6,!=1.5.7,<2.0 ; extra == 'socks' - backports-zstd>=1.0.0 ; python_full_version < '3.14' and extra == 'zstd' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/32/49/9e3e19ba756c4a5e6acb4ea74336d3035f7959254fbb05f5eb77bff067ed/uv-0.9.22-py3-none-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/6f/34/2e5cd576d312eb1131b615f49ee95ff6efb740965324843617adae729cf2/uv-0.10.9-py3-none-macosx_10_12_x86_64.whl name: uv - version: 0.9.22 - sha256: 9c238525272506845fe07c0b9088c5e33fcd738e1f49ef49dc3c8112096d2e3a + version: 0.10.9 + sha256: 880dd4cffe4bd184e8871ddf4c7d3c3b042e1f16d2682310644aa8d61eaea3e6 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/40/15/63fb7a6908db2f03716c4a50aea7e27a7440fe6a09854282c401139afaf7/uv-0.9.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/79/34/b104c413079874493eed7bf11838b47b697cf1f0ed7e9de374ea37b4e4e0/uv-0.10.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: uv - version: 0.9.22 - sha256: 1f45e1e0f26dd47fa01eb421c54cfd39de10fd52ac0a9d7ae45b92fce7d92b0b + version: 0.10.9 + sha256: 7c9d6deb30edbc22123be75479f99fb476613eaf38a8034c0e98bba24a344179 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/5e/68/bb76c97c284ce7fb8efa868994c2510588faa7075e60d8865d1373e54b7b/uv-0.9.22-py3-none-macosx_10_12_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/89/35/684f641de4de2b20db7d2163c735b2bb211e3b3c84c241706d6448e5e868/uv-0.10.9-py3-none-macosx_11_0_arm64.whl name: uv - version: 0.9.22 - sha256: b78f2605d65c4925631d891dec99b677b05f50c774dedc6ef8968039a5bcfdb0 + version: 0.10.9 + sha256: a7a784254380552398a6baf4149faf5b31a4003275f685c28421cf8197178a08 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/af/49/7230b1d56aeaee0eefd346a70f582463f11fb7036d2d020bcf68053bd994/uv-0.9.22-py3-none-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/c9/e9/adf7a12136573937d12ac189569e2e90e7fad18b458192083df6986f3013/uv-0.10.9-py3-none-win_amd64.whl name: uv - version: 0.9.22 - sha256: 2a4155cf7d0231d0adae94257ee10d70c57c2f592207536ddd55d924590a8c15 + version: 0.10.9 + sha256: af79552276d8bd622048ab2d67ec22120a6af64d83963c46b1482218c27b571f requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl name: validate-pyproject - version: 0.24.1 - sha256: b7b05fa9117204c9c4606ab317acd095b18d1bfc78fb7dc8cc06f77d0582ca2d + version: '0.25' + sha256: f9d05e2686beff82f9ea954f582306b036ced3d3feb258c1110f2c2a495b1981 requires_dist: - fastjsonschema>=2.16.2,<=3 - packaging>=24.2 ; extra == 'all' @@ -10873,35 +12407,18 @@ packages: - mypy ; extra == 'test' - pretend ; extra == 'test' - pytest ; extra == 'test' -- pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl name: virtualenv - version: 20.35.4 - sha256: c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b + version: 21.2.0 + sha256: 1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f requires_dist: - distlib>=0.3.7,<1 - - filelock>=3.12.2,<4 + - filelock>=3.24.2,<4 ; python_full_version >= '3.10' + - filelock>=3.16.1,<=3.19.1 ; python_full_version < '3.10' - importlib-metadata>=6.6 ; python_full_version < '3.8' - platformdirs>=3.9.1,<5 + - python-discovery>=1 - 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 @@ -10945,11 +12462,11 @@ packages: 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 +- pypi: https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl name: wcwidth - version: 0.2.14 - sha256: a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1 - requires_python: '>=3.6' + version: 0.6.0 + sha256: 1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl name: webcolors version: 25.10.0 @@ -10972,6 +12489,11 @@ packages: - sphinx-rtd-theme>=1.1.0 ; extra == 'docs' - myst-parser>=2.0.0 ; extra == 'docs' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl + name: widgetsnbextension + version: 4.0.15 + sha256: 8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366 + requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl name: wsproto version: 1.3.2 @@ -10996,78 +12518,78 @@ packages: - coverage ; extra == 'test' - xraydb[dev,doc,test] ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/24/84/e237607faf4e099dbb8a4f511cfd5efcb5f75918baad200ff7380635631b/yarl-1.23.0-cp311-cp311-macosx_10_9_x86_64.whl name: yarl - version: 1.22.0 - sha256: 01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a + version: 1.23.0 + sha256: cbb0fef01f0c6b38cb0f39b1f78fc90b807e0e3c86a7ff3ce74ad77ce5c7880c requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 - requires_python: '>=3.9' -- 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 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: yarl - version: 1.22.0 - sha256: bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b + version: 1.23.0 + sha256: 34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4 requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/67/b6/8925d68af039b835ae876db5838e82e76ec87b9782ecc97e192b809c4831/yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl name: yarl - version: 1.22.0 - sha256: 22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c + version: 1.23.0 + sha256: 4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5 requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 - requires_python: '>=3.9' -- 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 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl name: yarl - version: 1.22.0 - sha256: 4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d + version: 1.23.0 + sha256: baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3 requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/93/22/b85eca6fa2ad9491af48c973e4c8cf6b103a73dbb271fe3346949449fca0/yarl-1.23.0-cp311-cp311-win_amd64.whl name: yarl - version: 1.22.0 - sha256: 669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6 + version: 1.23.0 + sha256: bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46 requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/9a/64/c53487d9f4968045b8afa51aed7ca44f58b2589e772f32745f3744476c82/yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: yarl - version: 1.22.0 - sha256: 47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d + version: 1.23.0 + sha256: 99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598 requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl name: yarl - version: 1.22.0 - sha256: 078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b + version: 1.23.0 + sha256: 7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b2/0d/71ceabc14c146ba8ee3804ca7b3d42b1664c8440439de5214d366fec7d3a/yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl name: yarl - version: 1.22.0 - sha256: 792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028 + version: 1.23.0 + sha256: dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432 requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 - requires_python: '>=3.9' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl name: zipp version: 3.23.0 diff --git a/pixi.toml b/pixi.toml index 04f71c03..c0a8608e 100644 --- a/pixi.toml +++ b/pixi.toml @@ -33,6 +33,18 @@ platforms = ['win-64', 'linux-64', 'osx-64', 'osx-arm64'] # Channels for fetching packages channels = ['conda-forge'] +##################### +# System requirements +##################### + +[system-requirements] + +# Set minimum supported version for macOS to be 14.0 to ensure packages +# like `skipp` that only have wheels for macOS 14.0+ (macosx_14_0_arm64) +# are used instead of building from source. This is a workaround for +# Pixi, see https://github.com/prefix-dev/pixi/issues/5667 +macos = '14.0' + ########## # FEATURES ########## @@ -46,11 +58,12 @@ gsl = '*' # GNU Scientific Library; required for pdffit2. #libcblas = '*' # CBLAS library for linear algebra; required for pdffit2. [pypi-dependencies] # == [feature.default.pypi-dependencies] -pip = '*' # Native package installer -uv = '*' # Package manager -jupyterlab = '*' # Jupyter notebooks -pixi-kernel = '*' # Pixi Jupyter kernel -easydiffraction = { version = '*', extras = ['all'] } # Main package +pip = '*' # Native package installer +uv = '*' # Package manager +jupyterlab = '*' # Jupyter notebooks +pixi-kernel = '*' # Pixi Jupyter kernel +#easydiffraction = { version = '*', extras = ['all'] } # Main package +easydiffraction = { path = ".", editable = true, extras = ['all'] } # Specific features @@ -190,7 +203,7 @@ 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 = "python -m uv pip install --force-reinstall --editable '.[all]'" +#dev-install = "python -m uv pip install --force-reinstall --editable '.[all]'" npm-config = 'npm config set registry https://registry.npmjs.org/' prettier-install = 'npm install --no-save --no-audit --no-fund prettier prettier-plugin-toml' pre-commit-setup = 'pre-commit clean && pre-commit uninstall && pre-commit install --hook-type pre-commit --hook-type pre-push --overwrite' @@ -198,10 +211,10 @@ pre-commit-update = 'pre-commit autoupdate' clean-pycache = "find . -type d -name '__pycache__' -prune -exec rm -rf '{}' +" dev = { depends-on = [ - 'dev-install', + #'dev-install', 'npm-config', 'prettier-install', - 'pre-commit-setup', + #'pre-commit-setup', ] } wheel = { depends-on = ['npm-config', 'prettier-install'] } diff --git a/pyproject.toml b/pyproject.toml index fb0118e3..d59c6205 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ classifiers = [ ] requires-python = '>=3.11,<3.14' dependencies = [ - 'essdiffraction', # ESS Diffraction library + 'essdiffraction', # ESS-specific diffraction library 'numpy', # Numerical computing library 'colorama', # Color terminal output 'tabulate', # Pretty-print tabular data for terminal output @@ -180,7 +180,7 @@ 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 = 65 # Temporarily reduce to allow gradual improvement +fail_under = 60 # Temporarily reduce to allow gradual improvement ######################## # Configuration for ruff @@ -191,13 +191,7 @@ fail_under = 65 # Temporarily reduce to allow gradual improvement [tool.ruff] # Temporarily exclude some directories until we have improved the code quality there -#exclude = ['tests', 'tmp'] -exclude = [ - 'tmp', - 'tests/unit', - 'tests/integration/fitting', - 'tests/integration/scipp-analysis/tmp', -] +exclude = ['tmp', 'tests/unit'] indent-width = 4 line-length = 99 # Enable new rules that are not yet stable, like DOC @@ -236,7 +230,7 @@ select = [ #'RET', # Return statement issues (e.g., inconsistent returns) #'RUF', # Ruff-specific checks (e.g., enforcing best practices) #'SLF', # Self argument-related issues (e.g., missing or misused self) - #'T20', # Flake8-print-specific checks (e.g., print statements left in code) + #'T20', # Flake8-print-specific checks (e.g., print statements left in code) #'TD', # Type definition issues (e.g., incorrect or missing type definitions) #'TRY', # Tryceratops Try/Except-related issues (e.g., broad exceptions, empty except blocks) #'UP', # Pyupgrade-specific checks @@ -262,7 +256,7 @@ ignore = [ # Temporarily increase McCabe complexity limit to 19 to allow # refactoring in smaller steps. [tool.ruff.lint.mccabe] -max-complexity = 19 # default is 10 +max-complexity = 37 # 19 # default is 10 [tool.ruff.lint.flake8-tidy-imports] ban-relative-imports = 'all' diff --git a/src/easydiffraction/analysis/calculators/base.py b/src/easydiffraction/analysis/calculators/base.py index 80c86a36..f4d99c4d 100644 --- a/src/easydiffraction/analysis/calculators/base.py +++ b/src/easydiffraction/analysis/calculators/base.py @@ -29,6 +29,7 @@ def calculate_structure_factors( self, sample_model: SampleModelBase, experiment: ExperimentBase, + called_by_minimizer: bool, ) -> None: """Calculate structure factors for a single sample model and experiment. diff --git a/src/easydiffraction/analysis/calculators/cryspy.py b/src/easydiffraction/analysis/calculators/cryspy.py index 18e7858d..4e5e9b2a 100644 --- a/src/easydiffraction/analysis/calculators/cryspy.py +++ b/src/easydiffraction/analysis/calculators/cryspy.py @@ -14,6 +14,7 @@ from easydiffraction.analysis.calculators.base import CalculatorBase from easydiffraction.experiments.experiment.base import ExperimentBase from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum from easydiffraction.sample_models.sample_model.base import SampleModelBase try: @@ -51,7 +52,8 @@ def calculate_structure_factors( self, sample_model: SampleModelBase, experiment: ExperimentBase, - ) -> None: + called_by_minimizer: bool = False, + ): """Raises a NotImplementedError as HKL calculation is not implemented. @@ -60,8 +62,49 @@ def calculate_structure_factors( factors for. experiment: The experiment associated with the sample models. + called_by_minimizer: Whether the calculation is called by a + minimizer. """ - raise NotImplementedError('HKL calculation is not implemented for CryspyCalculator.') + combined_name = f'{sample_model.name}_{experiment.name}' + + if called_by_minimizer: + if self._cryspy_dicts and combined_name in self._cryspy_dicts: + cryspy_dict = self._recreate_cryspy_dict(sample_model, experiment) + else: + cryspy_obj = self._recreate_cryspy_obj(sample_model, experiment) + cryspy_dict = cryspy_obj.get_dictionary() + else: + cryspy_obj = self._recreate_cryspy_obj(sample_model, experiment) + cryspy_dict = cryspy_obj.get_dictionary() + + self._cryspy_dicts[combined_name] = copy.deepcopy(cryspy_dict) + + cryspy_in_out_dict: Dict[str, Any] = {} + + # Calculate the pattern using Cryspy + # TODO: Redirect stderr to suppress Cryspy warnings. + # This is a temporary solution to avoid cluttering the output. + # E.g. cryspy/A_functions_base/powder_diffraction_tof.py:106: + # RuntimeWarning: overflow encountered in exp + # Remove this when Cryspy is updated to handle warnings better. + with contextlib.redirect_stderr(io.StringIO()): + rhochi_calc_chi_sq_by_dictionary( + cryspy_dict, + dict_in_out=cryspy_in_out_dict, + flag_use_precalculated_data=False, + flag_calc_analytical_derivatives=False, + ) + + cryspy_block_name = f'diffrn_{experiment.name}' + + try: + y_calc = cryspy_in_out_dict[cryspy_block_name]['intensity_calc'] + stol = cryspy_in_out_dict[cryspy_block_name]['sthovl'] + except KeyError: + print(f'[CryspyCalculator] Error: No calculated data for {cryspy_block_name}') + return [], [] + + return stol, y_calc def calculate_pattern( self, @@ -82,7 +125,7 @@ def calculate_pattern( sample_model: The sample model to calculate the pattern for. experiment: The experiment associated with the sample model. called_by_minimizer: Whether the calculation is called by a - minimizer. + minimizer. Returns: The calculated diffraction pattern as a NumPy array or a @@ -160,7 +203,9 @@ 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'] @@ -188,51 +233,66 @@ 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 - - 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 - cryspy_resolution[1] = experiment.peak.broad_gauss_v.value - cryspy_resolution[2] = experiment.peak.broad_gauss_w.value - cryspy_resolution[3] = experiment.peak.broad_lorentz_x.value - cryspy_resolution[4] = experiment.peak.broad_lorentz_y.value - - elif experiment.type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: - cryspy_expt_name = f'tof_{experiment.name}' + ############################## + + if experiment.type.sample_form.value == SampleFormEnum.POWDER: + 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 + cryspy_resolution[1] = experiment.peak.broad_gauss_v.value + cryspy_resolution[2] = experiment.peak.broad_gauss_w.value + cryspy_resolution[3] = experiment.peak.broad_lorentz_x.value + cryspy_resolution[4] = experiment.peak.broad_lorentz_y.value + + 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 + cryspy_expt_dict['dtt2'][0] = experiment.instrument.calib_d_to_tof_quad.value + 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 + cryspy_sigma[1] = experiment.peak.broad_gauss_sigma_1.value + cryspy_sigma[2] = experiment.peak.broad_gauss_sigma_2.value + + cryspy_beta = cryspy_expt_dict['profile_betas'] + cryspy_beta[0] = experiment.peak.broad_mix_beta_0.value + cryspy_beta[1] = experiment.peak.broad_mix_beta_1.value + + cryspy_alpha = cryspy_expt_dict['profile_alphas'] + cryspy_alpha[0] = experiment.peak.asym_alpha_0.value + cryspy_alpha[1] = experiment.peak.asym_alpha_1.value + + if experiment.type.sample_form.value == SampleFormEnum.SINGLE_CRYSTAL: + cryspy_expt_name = f'diffrn_{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 - cryspy_expt_dict['dtt2'][0] = experiment.instrument.calib_d_to_tof_quad.value - 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 - cryspy_sigma[1] = experiment.peak.broad_gauss_sigma_1.value - cryspy_sigma[2] = experiment.peak.broad_gauss_sigma_2.value + if experiment.type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: + cryspy_expt_dict['wavelength'][0] = experiment.instrument.setup_wavelength.value - cryspy_beta = cryspy_expt_dict['profile_betas'] - cryspy_beta[0] = experiment.peak.broad_mix_beta_0.value - cryspy_beta[1] = experiment.peak.broad_mix_beta_1.value - - cryspy_alpha = cryspy_expt_dict['profile_alphas'] - cryspy_alpha[0] = experiment.peak.asym_alpha_0.value - cryspy_alpha[1] = experiment.peak.asym_alpha_1.value + # Extinction + cryspy_expt_dict['extinction_radius'][0] = experiment.extinction.radius.value + cryspy_expt_dict['extinction_mosaicity'][0] = experiment.extinction.mosaicity.value return cryspy_dict @@ -260,7 +320,7 @@ def _recreate_cryspy_obj( # Add single experiment to cryspy_obj cryspy_experiment_cif = self._convert_experiment_to_cryspy_cif( experiment, - linked_phase=sample_model, + linked_sample_model=sample_model, ) cryspy_experiment_obj = str_to_globaln(cryspy_experiment_cif) @@ -285,24 +345,28 @@ def _convert_sample_model_to_cryspy_cif( def _convert_experiment_to_cryspy_cif( self, experiment: ExperimentBase, - linked_phase: Any, + linked_sample_model: Any, ) -> str: """Converts an experiment to a Cryspy CIF string. Args: experiment: The experiment to convert. - linked_phase: The linked phase associated with the + linked_sample_model: The sample model linked to the experiment. Returns: The Cryspy CIF string representation of the experiment. """ + # Try to get experiment attributes expt_type = getattr(experiment, 'type', None) instrument = getattr(experiment, 'instrument', None) peak = getattr(experiment, 'peak', None) + extinction = getattr(experiment, 'extinction', None) + # Add experiment datablock name cif_lines = [f'data_{experiment.name}'] + # Add experiment type attribute dat if expt_type is not None: cif_lines.append('') radiation_probe = expt_type.radiation_probe.value @@ -310,22 +374,39 @@ def _convert_experiment_to_cryspy_cif( radiation_probe = radiation_probe.replace('xray', 'X-rays') cif_lines.append(f'_setup_radiation {radiation_probe}') + # Add instrument attribute data if instrument: # 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', - } + if expt_type.sample_form.value == SampleFormEnum.POWDER: + instrument_mapping = { + 'setup_wavelength': '_setup_wavelength', + 'calib_twotheta_offset': '_setup_offset_2theta', + } + elif expt_type.sample_form.value == SampleFormEnum.SINGLE_CRYSTAL: + instrument_mapping = { + 'setup_wavelength': '_setup_wavelength', + } + # Add dummy 0.0 value for _setup_field required by + # Cryspy + cif_lines.append('') + cif_lines.append('_setup_field 0.0') 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', - } + if expt_type.sample_form.value == SampleFormEnum.POWDER: + 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', + } + elif expt_type.sample_form.value == SampleFormEnum.SINGLE_CRYSTAL: + instrument_mapping = {} # TODO: Check this mapping! + # Add dummy 0.0 value for _setup_field required by + # Cryspy + cif_lines.append('') + cif_lines.append('_setup_field 0.0') cif_lines.append('') for local_attr_name, engine_key_name in instrument_mapping.items(): # attr_obj = instrument.__dict__.get(local_attr_name) @@ -333,6 +414,7 @@ def _convert_experiment_to_cryspy_cif( if attr_obj is not None: cif_lines.append(f'{engine_key_name} {attr_obj.value}') + # Add peak attribute data if peak: if expt_type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: peak_mapping = { @@ -360,57 +442,140 @@ def _convert_experiment_to_cryspy_cif( if attr_obj is not None: cif_lines.append(f'{engine_key_name} {attr_obj.value}') - x_data = experiment.data.x - twotheta_min = f'{np.round(x_data.min(), 5):.5f}' # float(x_data.min()) - twotheta_max = f'{np.round(x_data.max(), 5):.5f}' # float(x_data.max()) - cif_lines.append('') - if expt_type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: - cif_lines.append(f'_range_2theta_min {twotheta_min}') - cif_lines.append(f'_range_2theta_max {twotheta_max}') - elif expt_type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: - cif_lines.append(f'_range_time_min {twotheta_min}') - cif_lines.append(f'_range_time_max {twotheta_max}') - - cif_lines.append('') - cif_lines.append('loop_') - cif_lines.append('_phase_label') - cif_lines.append('_phase_scale') - cif_lines.append(f'{linked_phase.name} 1.0') - - if expt_type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: + # Add extinction attribute data + if extinction and expt_type.sample_form.value == SampleFormEnum.SINGLE_CRYSTAL: + extinction_mapping = { + 'mosaicity': '_extinction_mosaicity', + 'radius': '_extinction_radius', + } cif_lines.append('') - cif_lines.append('loop_') - cif_lines.append('_pd_background_2theta') - cif_lines.append('_pd_background_intensity') - cif_lines.append(f'{twotheta_min} 0.0') - cif_lines.append(f'{twotheta_max} 0.0') - elif expt_type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: + cif_lines.append('_extinction_model gauss') + for local_attr_name, engine_key_name in extinction_mapping.items(): + attr_obj = getattr(extinction, local_attr_name) + if attr_obj is not None: + cif_lines.append(f'{engine_key_name} {attr_obj.value}') + + # Add range data + if expt_type.sample_form.value == SampleFormEnum.POWDER: + x_data = experiment.data.x + twotheta_min = f'{np.round(x_data.min(), 5):.5f}' # float(x_data.min()) + twotheta_max = f'{np.round(x_data.max(), 5):.5f}' # float(x_data.max()) cif_lines.append('') - cif_lines.append('loop_') - cif_lines.append('_tof_backgroundpoint_time') - cif_lines.append('_tof_backgroundpoint_intensity') - cif_lines.append(f'{twotheta_min} 0.0') - cif_lines.append(f'{twotheta_max} 0.0') + if expt_type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: + cif_lines.append(f'_range_2theta_min {twotheta_min}') + cif_lines.append(f'_range_2theta_max {twotheta_max}') + elif expt_type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: + cif_lines.append(f'_range_time_min {twotheta_min}') + cif_lines.append(f'_range_time_max {twotheta_max}') - if expt_type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: + # Add orientation matrix data + # Hardcoded example values for now, as we don't use them yet, + # but Cryspy requires them for single crystal data. + if expt_type.sample_form.value == SampleFormEnum.SINGLE_CRYSTAL: cif_lines.append('') - cif_lines.append('loop_') - cif_lines.append('_pd_meas_2theta') - cif_lines.append('_pd_meas_intensity') - cif_lines.append('_pd_meas_intensity_sigma') - elif expt_type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: + cif_lines.append('_diffrn_orient_matrix_type CCSL') + cif_lines.append('_diffrn_orient_matrix_ub_11 -0.088033') + cif_lines.append('_diffrn_orient_matrix_ub_12 -0.088004') + cif_lines.append('_diffrn_orient_matrix_ub_13 0.069970') + cif_lines.append('_diffrn_orient_matrix_ub_21 0.034058') + cif_lines.append('_diffrn_orient_matrix_ub_22 -0.188170') + cif_lines.append('_diffrn_orient_matrix_ub_23 -0.013039') + cif_lines.append('_diffrn_orient_matrix_ub_31 0.223600') + cif_lines.append('_diffrn_orient_matrix_ub_32 0.125751') + cif_lines.append('_diffrn_orient_matrix_ub_33 0.029490') + + # Add phase data + if expt_type.sample_form.value == SampleFormEnum.SINGLE_CRYSTAL: + cif_lines.append('') + cif_lines.append(f'_phase_label {linked_sample_model.name}') + cif_lines.append('_phase_scale 1.0') + elif expt_type.sample_form.value == SampleFormEnum.POWDER: cif_lines.append('') cif_lines.append('loop_') - cif_lines.append('_tof_meas_time') - cif_lines.append('_tof_meas_intensity') - cif_lines.append('_tof_meas_intensity_sigma') - - y_data: np.ndarray = experiment.data.meas - sy_data: np.ndarray = experiment.data.meas_su - - for x_val, y_val, sy_val in zip(x_data, y_data, sy_data, strict=True): - cif_lines.append(f' {x_val:.5f} {y_val:.5f} {sy_val:.5f}') + cif_lines.append('_phase_label') + cif_lines.append('_phase_scale') + cif_lines.append(f'{linked_sample_model.name} 1.0') + # Add background data + if expt_type.sample_form.value == SampleFormEnum.POWDER: + if expt_type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: + cif_lines.append('') + cif_lines.append('loop_') + cif_lines.append('_pd_background_2theta') + cif_lines.append('_pd_background_intensity') + cif_lines.append(f'{twotheta_min} 0.0') + cif_lines.append(f'{twotheta_max} 0.0') + elif expt_type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: + cif_lines.append('') + cif_lines.append('loop_') + cif_lines.append('_tof_backgroundpoint_time') # TODO: !!!!???? + cif_lines.append('_tof_backgroundpoint_intensity') # TODO: !!!!???? + cif_lines.append(f'{twotheta_min} 0.0') # TODO: !!!!???? + cif_lines.append(f'{twotheta_max} 0.0') # TODO: !!!!???? + + # Add measured data: Single crystal + if expt_type.sample_form.value == SampleFormEnum.SINGLE_CRYSTAL: + if expt_type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: + cif_lines.append('') + cif_lines.append('loop_') + cif_lines.append('_diffrn_refln_index_h') + cif_lines.append('_diffrn_refln_index_k') + cif_lines.append('_diffrn_refln_index_l') + cif_lines.append('_diffrn_refln_intensity') + cif_lines.append('_diffrn_refln_intensity_sigma') + indices_h: np.ndarray = experiment.data.index_h + indices_k: np.ndarray = experiment.data.index_k + indices_l: np.ndarray = experiment.data.index_l + y_data: np.ndarray = experiment.data.intensity_meas + sy_data: np.ndarray = experiment.data.intensity_meas_su + for index_h, index_k, index_l, y_val, sy_val in zip( + indices_h, indices_k, indices_l, y_data, sy_data, strict=True + ): + cif_lines.append( + f'{index_h:4.0f}{index_k:4.0f}{index_l:4.0f} {y_val:.5f} {sy_val:.5f}' + ) + elif expt_type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: + cif_lines.append('') + cif_lines.append('loop_') + cif_lines.append('_diffrn_refln_index_h') + cif_lines.append('_diffrn_refln_index_k') + cif_lines.append('_diffrn_refln_index_l') + cif_lines.append('_diffrn_refln_intensity') + cif_lines.append('_diffrn_refln_intensity_sigma') + cif_lines.append('_diffrn_refln_wavelength') + indices_h: np.ndarray = experiment.data.index_h + indices_k: np.ndarray = experiment.data.index_k + indices_l: np.ndarray = experiment.data.index_l + y_data: np.ndarray = experiment.data.intensity_meas + sy_data: np.ndarray = experiment.data.intensity_meas_su + wl_data: np.ndarray = experiment.data.wavelength + for index_h, index_k, index_l, y_val, sy_val, wl_val in zip( + indices_h, indices_k, indices_l, y_data, sy_data, wl_data, strict=True + ): + cif_lines.append( + f'{index_h:4.0f}{index_k:4.0f}{index_l:4.0f} {y_val:.5f} ' + f'{sy_val:.5f} {wl_val:.5f}' + ) + # Add measured data: Powder + elif expt_type.sample_form.value == SampleFormEnum.POWDER: + if expt_type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: + cif_lines.append('') + cif_lines.append('loop_') + cif_lines.append('_pd_meas_2theta') + cif_lines.append('_pd_meas_intensity') + cif_lines.append('_pd_meas_intensity_sigma') + elif expt_type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: + cif_lines.append('') + cif_lines.append('loop_') + cif_lines.append('_tof_meas_time') + cif_lines.append('_tof_meas_intensity') + cif_lines.append('_tof_meas_intensity_sigma') + y_data: np.ndarray = experiment.data.intensity_meas + sy_data: np.ndarray = experiment.data.intensity_meas_su + for x_val, y_val, sy_val in zip(x_data, y_data, sy_data, strict=True): + cif_lines.append(f' {x_val:.5f} {y_val:.5f} {sy_val:.5f}') + + # Combine all lines into a single CIF string cryspy_experiment_cif = '\n'.join(cif_lines) return cryspy_experiment_cif diff --git a/src/easydiffraction/analysis/fit_helpers/metrics.py b/src/easydiffraction/analysis/fit_helpers/metrics.py index b6ef9928..23d5d42b 100644 --- a/src/easydiffraction/analysis/fit_helpers/metrics.py +++ b/src/easydiffraction/analysis/fit_helpers/metrics.py @@ -143,9 +143,9 @@ def get_reliability_inputs( sample_model._update_categories() experiment._update_categories() - y_calc = experiment.data.calc - y_meas = experiment.data.meas - y_meas_su = experiment.data.meas_su + y_calc = experiment.data.intensity_calc + y_meas = experiment.data.intensity_meas + y_meas_su = experiment.data.intensity_meas_su if y_meas is not None and y_calc is not None: # If standard uncertainty is not provided, use ones diff --git a/src/easydiffraction/analysis/fitting.py b/src/easydiffraction/analysis/fitting.py index ee6e3c84..0541318e 100644 --- a/src/easydiffraction/analysis/fitting.py +++ b/src/easydiffraction/analysis/fitting.py @@ -182,9 +182,9 @@ def _residual_function( # Calculate the difference between measured and calculated # patterns - y_calc: np.ndarray = experiment.data.calc - y_meas: np.ndarray = experiment.data.meas - y_meas_su: np.ndarray = experiment.data.meas_su + y_calc: np.ndarray = experiment.data.intensity_calc + y_meas: np.ndarray = experiment.data.intensity_meas + y_meas_su: np.ndarray = experiment.data.intensity_meas_su diff = (y_meas - y_calc) / y_meas_su # Residuals are squared before going into reduced diff --git a/src/easydiffraction/display/plotters/ascii.py b/src/easydiffraction/display/plotters/ascii.py index f9ef8904..7b5ff6a8 100644 --- a/src/easydiffraction/display/plotters/ascii.py +++ b/src/easydiffraction/display/plotters/ascii.py @@ -8,6 +8,7 @@ """ import asciichartpy +import numpy as np from easydiffraction.display.plotters.base import DEFAULT_HEIGHT from easydiffraction.display.plotters.base import SERIES_CONFIG @@ -43,7 +44,7 @@ def _get_legend_item(self, label): item = f'{color_start}{line}{color_end} {name}' return item - def plot( + def plot_powder( self, x, y_series, @@ -52,7 +53,11 @@ def plot( title, height=None, ): - """Render a compact ASCII chart in the terminal. + """Render a line plot for powder diffraction data. + + Suitable for powder diffraction data where intensity is plotted + against an x-axis variable (2θ, TOF, d-spacing). Uses ASCII + characters for terminal display. Args: x: 1D array-like of x values (only used for range @@ -84,3 +89,74 @@ def plot( padded = '\n'.join(' ' + line for line in chart.splitlines()) print(padded) + + def plot_single_crystal( + self, + x_calc, + y_meas, + y_meas_su, + axes_labels, + title, + height=None, + ): + """Render a scatter plot for single crystal diffraction data. + + Creates an ASCII scatter plot showing measured vs calculated + values with a diagonal reference line. + + Args: + x_calc: 1D array-like of calculated values (x-axis). + y_meas: 1D array-like of measured values (y-axis). + y_meas_su: 1D array-like of measurement uncertainties + (ignored in ASCII mode). + axes_labels: Pair of strings for the x and y titles. + title: Figure title. + height: Number of text rows for the chart (default: 15). + """ + # Intentionally unused; ASCII scatter doesn't show error bars + del y_meas_su + + if height is None: + height = DEFAULT_HEIGHT + width = 60 # TODO: Make width configurable + + # Determine axis limits + vmin = float(min(np.min(y_meas), np.min(x_calc))) + vmax = float(max(np.max(y_meas), np.max(x_calc))) + pad = 0.05 * (vmax - vmin) if vmax > vmin else 1.0 + vmin -= pad + vmax += pad + + # Create empty grid + grid = [[' ' for _ in range(width)] for _ in range(height)] + + # Draw diagonal line (calc == meas) + for i in range(min(width, height)): + row = height - 1 - int(i * height / width) + col = i + if 0 <= row < height and 0 <= col < width: + grid[row][col] = '·' + + # Plot data points + for xv, yv in zip(x_calc, y_meas, strict=False): + col = int((xv - vmin) / (vmax - vmin) * (width - 1)) + row = height - 1 - int((yv - vmin) / (vmax - vmin) * (height - 1)) + if 0 <= row < height and 0 <= col < width: + grid[row][col] = '●' + + # Build chart string with axes + chart_lines = [] + for row in grid: + label = '│' + chart_lines.append(label + ''.join(row)) + + # X-axis + x_axis = '└' + '─' * width + + # Print output + console.paragraph(f'{title}') + console.print(f'{axes_labels[1]}') + for line in chart_lines: + print(f' {line}') + print(f' {x_axis}') + console.print(f'{" " * (width - 3)}{axes_labels[0]}') diff --git a/src/easydiffraction/display/plotters/base.py b/src/easydiffraction/display/plotters/base.py index 43c22767..85892950 100644 --- a/src/easydiffraction/display/plotters/base.py +++ b/src/easydiffraction/display/plotters/base.py @@ -4,36 +4,133 @@ from abc import ABC from abc import abstractmethod +from enum import Enum import numpy as np from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum -DEFAULT_HEIGHT = 9 +DEFAULT_HEIGHT = 25 DEFAULT_MIN = -np.inf DEFAULT_MAX = np.inf + +class XAxisType(str, Enum): + """X-axis types for diffraction plots. + + Values match attribute names in data models for direct use + with ``getattr(pattern, x_axis)``. + """ + + TWO_THETA = 'two_theta' + TIME_OF_FLIGHT = 'time_of_flight' + R = 'x' + + INTENSITY_CALC = 'intensity_calc' + + D_SPACING = 'd_spacing' + SIN_THETA_OVER_LAMBDA = 'sin_theta_over_lambda' + + +# Map (SampleFormEnum, ScatteringTypeEnum, BeamModeEnum) to default +# x-axis type +DEFAULT_X_AXIS = { + # Powder Bragg diffraction + ( + SampleFormEnum.POWDER, + ScatteringTypeEnum.BRAGG, + BeamModeEnum.CONSTANT_WAVELENGTH, + ): XAxisType.TWO_THETA, + ( + SampleFormEnum.POWDER, + ScatteringTypeEnum.BRAGG, + BeamModeEnum.TIME_OF_FLIGHT, + ): XAxisType.TIME_OF_FLIGHT, + # Powder total scattering (PDF) — always r-space + ( + SampleFormEnum.POWDER, + ScatteringTypeEnum.TOTAL, + BeamModeEnum.CONSTANT_WAVELENGTH, + ): XAxisType.R, + ( + SampleFormEnum.POWDER, + ScatteringTypeEnum.TOTAL, + BeamModeEnum.TIME_OF_FLIGHT, + ): XAxisType.R, + # Single crystal Bragg diffraction + ( + SampleFormEnum.SINGLE_CRYSTAL, + ScatteringTypeEnum.BRAGG, + BeamModeEnum.CONSTANT_WAVELENGTH, + ): XAxisType.INTENSITY_CALC, + ( + SampleFormEnum.SINGLE_CRYSTAL, + ScatteringTypeEnum.BRAGG, + BeamModeEnum.TIME_OF_FLIGHT, + ): XAxisType.INTENSITY_CALC, +} + DEFAULT_AXES_LABELS = { - (ScatteringTypeEnum.BRAGG, BeamModeEnum.CONSTANT_WAVELENGTH): [ + # Powder Bragg diffraction + ( + SampleFormEnum.POWDER, + ScatteringTypeEnum.BRAGG, + XAxisType.TWO_THETA, + ): [ '2θ (degree)', 'Intensity (arb. units)', ], - (ScatteringTypeEnum.BRAGG, BeamModeEnum.TIME_OF_FLIGHT): [ + ( + SampleFormEnum.POWDER, + ScatteringTypeEnum.BRAGG, + XAxisType.TIME_OF_FLIGHT, + ): [ 'TOF (µs)', 'Intensity (arb. units)', ], - (ScatteringTypeEnum.BRAGG, 'd-spacing'): [ + ( + SampleFormEnum.POWDER, + ScatteringTypeEnum.BRAGG, + XAxisType.D_SPACING, + ): [ 'd (Å)', 'Intensity (arb. units)', ], - (ScatteringTypeEnum.TOTAL, BeamModeEnum.CONSTANT_WAVELENGTH): [ - 'r (Å)', - 'G(r) (Å)', + # Powder total scattering (PDF) + ( + SampleFormEnum.POWDER, + ScatteringTypeEnum.TOTAL, + XAxisType.R, + ): [ + 'r (Å)', + 'G(r) (Å)', + ], + # Single crystal Bragg diffraction + ( + SampleFormEnum.SINGLE_CRYSTAL, + ScatteringTypeEnum.BRAGG, + XAxisType.INTENSITY_CALC, + ): [ + 'I²calc', + 'I²meas', + ], + ( + SampleFormEnum.SINGLE_CRYSTAL, + ScatteringTypeEnum.BRAGG, + XAxisType.D_SPACING, + ): [ + 'd (Å)', + 'Intensity (arb. units)', ], - (ScatteringTypeEnum.TOTAL, BeamModeEnum.TIME_OF_FLIGHT): [ - 'r (Å)', - 'G(r) (Å)', + ( + SampleFormEnum.SINGLE_CRYSTAL, + ScatteringTypeEnum.BRAGG, + XAxisType.SIN_THETA_OVER_LAMBDA, + ): [ + 'sin(θ)/λ (Å⁻¹)', + 'Intensity (arb. units)', ], } @@ -58,10 +155,16 @@ class PlotterBase(ABC): Implementations accept x values, multiple y-series, optional labels and render a plot to the chosen medium. + + Two main plot types are supported: + - ``plot_powder``: Line plots for powder diffraction patterns + (intensity vs. 2θ/TOF/d-spacing). + - ``plot_single_crystal``: Scatter plots comparing measured vs. + calculated values (e.g., F²meas vs F²calc for single crystal). """ @abstractmethod - def plot( + def plot_powder( self, x, y_series, @@ -70,7 +173,10 @@ def plot( title, height, ): - """Render a plot. + """Render a line plot for powder diffraction data. + + Suitable for powder diffraction data where intensity is plotted + against an x-axis variable (2θ, TOF, d-spacing). Args: x: 1D array of x-axis values. @@ -81,3 +187,28 @@ def plot( height: Backend-specific height (text rows or pixels). """ pass + + @abstractmethod + def plot_single_crystal( + self, + x_calc, + y_meas, + y_meas_su, + axes_labels, + title, + height, + ): + """Render a scatter plot for single crystal diffraction data. + + Suitable for single crystal diffraction data where measured + values are plotted against calculated values with error bars. + + Args: + x_calc: 1D array of calculated values (x-axis). + y_meas: 1D array of measured values (y-axis). + y_meas_su: 1D array of measurement uncertainties. + axes_labels: Pair of strings for the x and y titles. + title: Figure title. + height: Backend-specific height (text rows or pixels). + """ + pass diff --git a/src/easydiffraction/display/plotters/plotly.py b/src/easydiffraction/display/plotters/plotly.py index f91156d1..bf2b4907 100644 --- a/src/easydiffraction/display/plotters/plotly.py +++ b/src/easydiffraction/display/plotters/plotly.py @@ -36,8 +36,13 @@ class PlotlyPlotter(PlotterBase): if in_pycharm(): pio.renderers.default = 'browser' - def _get_trace(self, x, y, label): - """Create a Plotly trace for a single data series. + def _get_powder_trace( + self, + x, + y, + label, + ): + """Create a Plotly trace for powder diffraction data. Args: x: 1D array-like of x-axis values. @@ -63,34 +68,144 @@ def _get_trace(self, x, y, label): return trace - def plot( + def _get_single_crystal_trace( + self, + x_calc, + y_meas, + y_meas_su, + ): + """Create a Plotly trace for single crystal diffraction data. + + Args: + x_calc: 1D array-like of calculated values (x-axis). + y_meas: 1D array-like of measured values (y-axis). + y_meas_su: 1D array-like of measurement uncertainties. + + Returns: + A configured :class:`plotly.graph_objects.Scatter` trace + with markers and error bars. + """ + trace = go.Scatter( + x=x_calc, + y=y_meas, + mode='markers', + marker=dict( + symbol='circle', + size=10, + line=dict(width=0.5), + color=DEFAULT_COLORS['meas'], + ), + error_y=dict( + type='data', + array=y_meas_su, + visible=True, + ), + hovertemplate='calc: %{x}
meas: %{y}
', + ) + + return trace + + def _get_diagonal_shape(self): + """Create a diagonal reference line shape. + + Returns a y=x diagonal line spanning the plot area using paper + coordinates (0,0) to (1,1). + + Returns: + A dict configuring a diagonal line shape. + """ + return dict( + type='line', + x0=0, + y0=0, + x1=1, + y1=1, + xref='paper', + yref='paper', + layer='below', + line=dict(width=0.5), + ) + + def _get_config(self): + """Return the Plotly figure configuration. + + Returns: + A dict with display and mode bar settings. + """ + return dict( + displaylogo=False, + modeBarButtonsToRemove=[ + 'select2d', + 'lasso2d', + 'zoomIn2d', + 'zoomOut2d', + 'autoScale2d', + ], + ) + + def _get_figure( + self, + data, + layout, + ): + """Create and configure a Plotly figure. + + Args: + data: List of traces to include in the figure. + layout: Layout configuration dict. + + Returns: + A configured :class:`plotly.graph_objects.Figure`. + """ + fig = go.Figure(data=data, layout=layout) + # Format axis ticks: + # decimals for small numbers, grouped thousands for large + fig.update_xaxes(tickformat=',.6~g', separatethousands=True) + fig.update_yaxes(tickformat=',.6~g', separatethousands=True) + return fig + + def _show_figure( + self, + fig, + ): + """Display a Plotly figure. + + Renders the figure using the appropriate method for the current + environment (browser for PyCharm, inline HTML for Jupyter). + + Args: + fig: A :class:`plotly.graph_objects.Figure` to display. + """ + config = self._get_config() + + if in_pycharm() or display is None or HTML is None: + fig.show(config=config) + else: + html_fig = pio.to_html( + fig, + include_plotlyjs='cdn', + full_html=False, + config=config, + ) + display(HTML(html_fig)) + + def _get_layout( self, - x, - y_series, - labels, - axes_labels, title, - height=None, + axes_labels, + **kwargs, ): - """Render an interactive Plotly figure. + """Create a Plotly layout configuration. 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 = [] - for idx, y in enumerate(y_series): - label = labels[idx] - trace = self._get_trace(x, y, label) - data.append(trace) + axes_labels: Pair of strings for the x and y titles. + **kwargs: Additional layout parameters (e.g., shapes). - layout = go.Layout( + Returns: + A configured :class:`plotly.graph_objects.Layout`. + """ + return go.Layout( margin=dict( autoexpand=True, r=30, @@ -118,38 +233,87 @@ def plot( mirror=True, zeroline=False, ), + **kwargs, ) - config = dict( - displaylogo=False, - modeBarButtonsToRemove=[ - 'select2d', - 'lasso2d', - 'zoomIn2d', - 'zoomOut2d', - 'autoScale2d', - ], - ) + def plot_powder( + self, + x, + y_series, + labels, + axes_labels, + title, + height=None, + ): + """Render a line plot for powder diffraction data. + + Suitable for powder diffraction data where intensity is plotted + against an x-axis variable (2θ, TOF, d-spacing). + + Args: + x: 1D array-like of x-axis values. + y_series: Sequence of y arrays to plot. + labels: Series identifiers corresponding to y_series. + axes_labels: Pair of strings for the x and y titles. + title: Figure title. + height: Ignored; Plotly auto-sizes based on renderer. + """ + # Intentionally unused; accepted for API compatibility + del height + + data = [] + for idx, y in enumerate(y_series): + label = labels[idx] + trace = self._get_powder_trace(x, y, label) + data.append(trace) - fig = go.Figure( - data=data, - layout=layout, + layout = self._get_layout( + title, + axes_labels, ) - # 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) + fig = self._get_figure(data, layout) + self._show_figure(fig) - # Show the figure - if in_pycharm() or display is None or HTML is None: - fig.show(config=config) - else: - html_fig = pio.to_html( - fig, - include_plotlyjs='cdn', - full_html=False, - config=config, + def plot_single_crystal( + self, + x_calc, + y_meas, + y_meas_su, + axes_labels, + title, + height=None, + ): + """Render a scatter plot for single crystal diffraction data. + + Suitable for single crystal diffraction data where measured + values are plotted against calculated values with error bars + and a diagonal reference line. + + Args: + x_calc: 1D array-like of calculated values (x-axis). + y_meas: 1D array-like of measured values (y-axis). + y_meas_su: 1D array-like of measurement uncertainties. + axes_labels: Pair of strings for the x and y titles. + title: Figure title. + height: Ignored; Plotly auto-sizes based on renderer. + """ + # Intentionally unused; accepted for API compatibility + del height + + data = [ + self._get_single_crystal_trace( + x_calc, + y_meas, + y_meas_su, ) - display(HTML(html_fig)) + ] + + layout = self._get_layout( + title, + axes_labels, + shapes=[self._get_diagonal_shape()], + ) + + fig = self._get_figure(data, layout) + self._show_figure(fig) diff --git a/src/easydiffraction/display/plotting.py b/src/easydiffraction/display/plotting.py index c0c98deb..90d54261 100644 --- a/src/easydiffraction/display/plotting.py +++ b/src/easydiffraction/display/plotting.py @@ -18,6 +18,8 @@ from easydiffraction.display.plotters.base import DEFAULT_HEIGHT from easydiffraction.display.plotters.base import DEFAULT_MAX from easydiffraction.display.plotters.base import DEFAULT_MIN +from easydiffraction.display.plotters.base import DEFAULT_X_AXIS +from easydiffraction.display.plotters.base import XAxisType from easydiffraction.display.plotters.plotly import PlotlyPlotter from easydiffraction.display.tables import TableRenderer from easydiffraction.utils.environment import in_jupyter @@ -50,6 +52,10 @@ def description(self) -> str: class Plotter(RendererBase): """User-facing plotting facade backed by concrete plotters.""" + # ------------------------------------------------------------------ + # Private special methods + # ------------------------------------------------------------------ + def __init__(self): super().__init__() # X-axis limits @@ -58,6 +64,10 @@ def __init__(self): # Chart height self.height = DEFAULT_HEIGHT + # ------------------------------------------------------------------ + # Private class methods + # ------------------------------------------------------------------ + @classmethod def _factory(cls) -> type[RendererFactoryBase]: # type: ignore[override] return PlotterFactory @@ -66,20 +76,174 @@ def _factory(cls) -> type[RendererFactoryBase]: # type: ignore[override] def _default_engine(cls) -> str: return PlotterEngineEnum.default().value - def show_config(self): - """Display the current plotting configuration.""" - headers = [ - ('Parameter', 'left'), - ('Value', 'left'), - ] - rows = [ - ['Plotting engine', self.engine], - ['x-axis limits', f'[{self.x_min}, {self.x_max}]'], - ['Chart height', self.height], - ] - df = pd.DataFrame(rows, columns=pd.MultiIndex.from_tuples(headers)) - console.paragraph('Current plotter configuration') - TableRenderer.get().render(df) + # ------------------------------------------------------------------ + # Private helper methods + # ------------------------------------------------------------------ + + def _auto_x_range_for_ascii(self, pattern, x_array, x_min, x_max): + """For the ASCII engine, narrow the range around the tallest + peak. + + Args: + pattern: Data pattern object (needs ``intensity_meas``). + x_array: Full x-axis array. + x_min: Current minimum (may be ``None``). + x_max: Current maximum (may be ``None``). + + Returns: + Tuple of ``(x_min, x_max)``, possibly narrowed. + """ + if self._engine == 'asciichartpy' and (x_min is None or x_max is None): + max_intensity_pos = np.argmax(pattern.intensity_meas) + half_range = 50 + start = max(0, max_intensity_pos - half_range) + end = min(len(x_array) - 1, max_intensity_pos + half_range) + x_min = x_array[start] + x_max = x_array[end] + return x_min, x_max + + def _filtered_y_array( + self, + y_array, + x_array, + x_min, + x_max, + ): + """Filter an array by the inclusive x-range limits. + + Args: + y_array: 1D array-like of y values. + x_array: 1D array-like of x values (same length as + ``y_array``). + x_min: Minimum x limit (or ``None`` to use default). + x_max: Maximum x limit (or ``None`` to use default). + + Returns: + Filtered ``y_array`` values where ``x_array`` lies within + ``[x_min, x_max]``. + """ + if x_min is None: + x_min = self.x_min + if x_max is None: + x_max = self.x_max + + mask = (x_array >= x_min) & (x_array <= x_max) + filtered_y_array = y_array[mask] + + return filtered_y_array + + def _get_axes_labels(self, sample_form, scattering_type, x_axis): + """Look up axis labels for the given experiment / x-axis + combination. + """ + return DEFAULT_AXES_LABELS[(sample_form, scattering_type, x_axis)] + + def _prepare_powder_data( + self, + pattern, + expt_name, + expt_type, + x_min, + x_max, + x, + need_meas=False, + need_calc=False, + show_residual=False, + ): + """Validate, resolve axes, auto-range, and filter arrays. + + Args: + pattern: Data pattern object with intensity arrays. + expt_name: Experiment name for error messages. + expt_type: Experiment type with sample_form, scattering, + and beam enums. + x_min: Optional minimum x-axis limit. + x_max: Optional maximum x-axis limit. + x: Explicit x-axis type or ``None``. + need_meas: Whether ``intensity_meas`` is required. + need_calc: Whether ``intensity_calc`` is required. + show_residual: If ``True``, compute meas − calc residual. + + Returns: + A dict with keys ``x_filtered``, ``y_series``, ``y_labels``, + ``axes_labels``, and ``x_axis``; or ``None`` when a required + array is missing. + """ + x_axis, x_name, sample_form, scattering_type, _ = self._resolve_x_axis(expt_type, x) + + # Get x-array from pattern + x_array = getattr(pattern, x_axis, None) + if x_array is None: + log.error(f'No {x_name} data available for experiment {expt_name}') + return None + + # Validate required intensities + if need_meas and pattern.intensity_meas is None: + log.error(f'No measured data available for experiment {expt_name}') + return None + if need_calc and pattern.intensity_calc is None: + log.error(f'No calculated data available for experiment {expt_name}') + return None + + # Auto-range for ASCII engine + x_min, x_max = self._auto_x_range_for_ascii(pattern, x_array, x_min, x_max) + + # Filter x + x_filtered = self._filtered_y_array(x_array, x_array, x_min, x_max) + + # Filter y arrays and build series / labels + y_series = [] + y_labels = [] + + y_meas = None + if need_meas: + y_meas = self._filtered_y_array(pattern.intensity_meas, x_array, x_min, x_max) + y_series.append(y_meas) + y_labels.append('meas') + + y_calc = None + if need_calc: + y_calc = self._filtered_y_array(pattern.intensity_calc, x_array, x_min, x_max) + y_series.append(y_calc) + y_labels.append('calc') + + if show_residual and y_meas is not None and y_calc is not None: + y_resid = y_meas - y_calc + y_series.append(y_resid) + y_labels.append('resid') + + axes_labels = self._get_axes_labels(sample_form, scattering_type, x_axis) + + return { + 'x_filtered': x_filtered, + 'y_series': y_series, + 'y_labels': y_labels, + 'axes_labels': axes_labels, + 'x_axis': x_axis, + } + + def _resolve_x_axis(self, expt_type, x): + """Determine the x-axis type from experiment metadata. + + Args: + expt_type: Experiment type with sample_form, + scattering_type, and beam_mode enums. + x: Explicit x-axis type or ``None`` to auto-detect. + + Returns: + Tuple of ``(x_axis, x_name, sample_form, scattering_type, + beam_mode)``. + """ + sample_form = expt_type.sample_form.value + scattering_type = expt_type.scattering_type.value + beam_mode = expt_type.beam_mode.value + x_axis = DEFAULT_X_AXIS[(sample_form, scattering_type, beam_mode)] if x is None else x + x_name = getattr(x_axis, 'value', x_axis) + return x_axis, x_name, sample_form, scattering_type, beam_mode + + # ------------------------------------------------------------------ + # Public properties + # ------------------------------------------------------------------ @property def x_min(self): @@ -132,6 +296,25 @@ def height(self, value): else: self._height = DEFAULT_HEIGHT + # ------------------------------------------------------------------ + # Public methods + # ------------------------------------------------------------------ + + def show_config(self): + """Display the current plotting configuration.""" + headers = [ + ('Parameter', 'left'), + ('Value', 'left'), + ] + rows = [ + ['Plotting engine', self.engine], + ['x-axis limits', f'[{self.x_min}, {self.x_max}]'], + ['Chart height', self.height], + ] + df = pd.DataFrame(rows, columns=pd.MultiIndex.from_tuples(headers)) + console.paragraph('Current plotter configuration') + TableRenderer.get().render(df) + def plot_meas( self, pattern, @@ -139,77 +322,38 @@ def plot_meas( expt_type, x_min=None, x_max=None, - d_spacing=False, + x=None, ): """Plot measured pattern using the current engine. Args: - pattern: Object with ``x`` and ``meas`` arrays (and - ``d`` when ``d_spacing`` is true). + pattern: Object with x-axis arrays (``two_theta``, + ``time_of_flight``, ``d_spacing``) and ``meas`` array. expt_name: Experiment name for the title. expt_type: Experiment type with scattering/beam enums. x_min: Optional minimum x-axis limit. x_max: Optional maximum x-axis limit. - d_spacing: If ``True``, plot against d-spacing values. + x: X-axis type (``'two_theta'``, ``'time_of_flight'``, or + ``'d_spacing'``). If ``None``, auto-detected from + beam mode. """ - if pattern.x is None: - log.error(f'No data available for experiment {expt_name}') - return - if pattern.meas is None: - log.error(f'No measured 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 - start = max(0, max_intensity_pos - half_range) - end = min(len(x_array) - 1, max_intensity_pos + half_range) - x_min = x_array[start] - x_max = x_array[end] - - # 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, - x_min=x_min, - x_max=x_max, + ctx = self._prepare_powder_data( + pattern, + expt_name, + expt_type, + x_min, + x_max, + x, + need_meas=True, ) - y_meas = self._filtered_y_array( - y_array=pattern.meas, - x_array=x_array, - x_min=x_min, - x_max=x_max, - ) - - y_series = [y_meas] - y_labels = ['meas'] + if ctx is None: + return - if d_spacing: - axes_labels = DEFAULT_AXES_LABELS[ - ( - expt_type.scattering_type.value, - 'd-spacing', - ) - ] - else: - axes_labels = DEFAULT_AXES_LABELS[ - ( - expt_type.scattering_type.value, - expt_type.beam_mode.value, - ) - ] - - # TODO: Before, it was self._plotter.plot. Check what is better. - self._backend.plot( - x=x, - y_series=y_series, - labels=y_labels, - axes_labels=axes_labels, + self._backend.plot_powder( + x=ctx['x_filtered'], + y_series=ctx['y_series'], + labels=ctx['y_labels'], + axes_labels=ctx['axes_labels'], title=f"Measured data for experiment 🔬 '{expt_name}'", height=self.height, ) @@ -221,76 +365,38 @@ def plot_calc( expt_type, x_min=None, x_max=None, - d_spacing=False, + x=None, ): """Plot calculated pattern using the current engine. Args: - pattern: Object with ``x`` and ``calc`` arrays (and - ``d`` when ``d_spacing`` is true). + pattern: Object with x-axis arrays (``two_theta``, + ``time_of_flight``, ``d_spacing``) and ``calc`` array. expt_name: Experiment name for the title. expt_type: Experiment type with scattering/beam enums. x_min: Optional minimum x-axis limit. x_max: Optional maximum x-axis limit. - d_spacing: If ``True``, plot against d-spacing values. + x: X-axis type (``'two_theta'``, ``'time_of_flight'``, or + ``'d_spacing'``). If ``None``, auto-detected from + beam mode. """ - if pattern.x is None: - log.error(f'No data available for experiment {expt_name}') - return - if pattern.calc is None: - log.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 - start = max(0, max_intensity_pos - half_range) - end = min(len(x_array) - 1, max_intensity_pos + half_range) - x_min = x_array[start] - x_max = x_array[end] - - # 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, - x_min=x_min, - x_max=x_max, - ) - y_calc = self._filtered_y_array( - y_array=pattern.calc, - x_array=x_array, - x_min=x_min, - x_max=x_max, + ctx = self._prepare_powder_data( + pattern, + expt_name, + expt_type, + x_min, + x_max, + x, + need_calc=True, ) + if ctx is None: + return - y_series = [y_calc] - y_labels = ['calc'] - - if d_spacing: - axes_labels = DEFAULT_AXES_LABELS[ - ( - expt_type.scattering_type.value, - 'd-spacing', - ) - ] - else: - axes_labels = DEFAULT_AXES_LABELS[ - ( - expt_type.scattering_type.value, - expt_type.beam_mode.value, - ) - ] - - self._backend.plot( - x=x, - y_series=y_series, - labels=y_labels, - axes_labels=axes_labels, + self._backend.plot_powder( + x=ctx['x_filtered'], + y_series=ctx['y_series'], + labels=ctx['y_labels'], + axes_labels=ctx['axes_labels'], title=f"Calculated data for experiment 🔬 '{expt_name}'", height=self.height, ) @@ -303,125 +409,91 @@ def plot_meas_vs_calc( x_min=None, x_max=None, show_residual=False, - d_spacing=False, + x=None, ): """Plot measured and calculated series and optional residual. + Supports both powder and single crystal data with a unified API. + + For powder diffraction: + - x='two_theta', 'time_of_flight', or 'd_spacing' + - Auto-detected from beam mode if not specified + + For single crystal diffraction: + - x='intensity_calc' (default): scatter plot + - x='d_spacing' or 'sin_theta_over_lambda': line plot + Args: - pattern: Object with ``x``, ``meas`` and ``calc`` arrays - (and ``d`` when ``d_spacing`` is true). + pattern: Data pattern object with meas/calc arrays. expt_name: Experiment name for the title. - expt_type: Experiment type with scattering/beam enums. + expt_type: Experiment type with sample_form, + scattering, and beam enums. x_min: Optional minimum x-axis limit. x_max: Optional maximum x-axis limit. - show_residual: If ``True``, add residual series. - d_spacing: If ``True``, plot against d-spacing values. + show_residual: If ``True``, add residual series + (powder only). + x: X-axis type. If ``None``, auto-detected from sample form + and beam mode. """ - if pattern.x is None: - log.error(f'No data available for experiment {expt_name}') - return - if pattern.meas is None: + x_axis, _, sample_form, scattering_type, _ = self._resolve_x_axis(expt_type, x) + + # Validate required data (before x-array check, matching + # original behavior for plot_meas_vs_calc) + if pattern.intensity_meas is None: log.error(f'No measured data available for experiment {expt_name}') return - if pattern.calc is None: + if pattern.intensity_calc is None: log.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 - start = max(0, max_intensity_pos - half_range) - end = min(len(x_array) - 1, max_intensity_pos + half_range) - x_min = x_array[start] - x_max = x_array[end] + title = f"Measured vs Calculated data for experiment 🔬 '{expt_name}'" + + # Single crystal scatter plot (I²calc vs I²meas) + if x_axis == XAxisType.INTENSITY_CALC or x_axis == 'intensity_calc': + axes_labels = self._get_axes_labels(sample_form, scattering_type, x_axis) + + if pattern.intensity_meas_su is None: + log.warning(f'No measurement uncertainties for experiment {expt_name}') + meas_su = np.zeros_like(pattern.intensity_meas) + else: + meas_su = pattern.intensity_meas_su + + self._backend.plot_single_crystal( + x_calc=pattern.intensity_calc, + y_meas=pattern.intensity_meas, + y_meas_su=meas_su, + axes_labels=axes_labels, + title=f"Measured vs Calculated data for experiment 🔬 '{expt_name}'", + height=self.height, + ) + return - # 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, - x_min=x_min, - x_max=x_max, - ) - y_meas = self._filtered_y_array( - y_array=pattern.meas, - x_array=x_array, - x_min=x_min, - x_max=x_max, - ) - y_calc = self._filtered_y_array( - y_array=pattern.calc, - x_array=x_array, - x_min=x_min, - x_max=x_max, + # Line plot (PD or SC with d_spacing/sin_theta_over_lambda) + # TODO: Rename from _prepare_powder_data as it also supports + # single crystal line plots + ctx = self._prepare_powder_data( + pattern, + expt_name, + expt_type, + x_min, + x_max, + x, + need_meas=True, + need_calc=True, + show_residual=show_residual, ) + if ctx is None: + return - y_series = [y_meas, y_calc] - y_labels = ['meas', 'calc'] - - if d_spacing: - axes_labels = DEFAULT_AXES_LABELS[ - ( - expt_type.scattering_type.value, - 'd-spacing', - ) - ] - else: - axes_labels = DEFAULT_AXES_LABELS[ - ( - expt_type.scattering_type.value, - expt_type.beam_mode.value, - ) - ] - - if show_residual: - y_resid = y_meas - y_calc - y_series.append(y_resid) - y_labels.append('resid') - - self._backend.plot( - x=x, - y_series=y_series, - labels=y_labels, - axes_labels=axes_labels, - title=f"Measured vs Calculated data for experiment 🔬 '{expt_name}'", + self._backend.plot_powder( + x=ctx['x_filtered'], + y_series=ctx['y_series'], + labels=ctx['y_labels'], + axes_labels=ctx['axes_labels'], + title=title, height=self.height, ) - def _filtered_y_array( - self, - y_array, - x_array, - x_min, - x_max, - ): - """Filter an array by the inclusive x-range limits. - - Args: - y_array: 1D array-like of y values. - x_array: 1D array-like of x values (same length as - ``y_array``). - x_min: Minimum x limit (or ``None`` to use default). - x_max: Maximum x limit (or ``None`` to use default). - - Returns: - Filtered ``y_array`` values where ``x_array`` lies within - ``[x_min, x_max]``. - """ - if x_min is None: - x_min = self.x_min - if x_max is None: - x_max = self.x_max - - mask = (x_array >= x_min) & (x_array <= x_max) - filtered_y_array = y_array[mask] - - return filtered_y_array - class PlotterFactory(RendererFactoryBase): """Factory for plotter implementations.""" diff --git a/src/easydiffraction/experiments/categories/background/chebyshev.py b/src/easydiffraction/experiments/categories/background/chebyshev.py index 3532e35e..2a21b0d0 100644 --- a/src/easydiffraction/experiments/categories/background/chebyshev.py +++ b/src/easydiffraction/experiments/categories/background/chebyshev.py @@ -138,14 +138,14 @@ def _update(self, called_by_minimizer=False): if not self._items: log.warning('No background points found. Setting background to zero.') - data._set_bkg(np.zeros_like(x)) + data._set_intensity_bkg(np.zeros_like(x)) return u = (x - x.min()) / (x.max() - x.min()) * 2 - 1 coefs = [term.coef.value for term in self._items] y = chebval(u, coefs) - data._set_bkg(y) + data._set_intensity_bkg(y) def show(self) -> None: """Print a table of polynomial orders and coefficients.""" diff --git a/src/easydiffraction/experiments/categories/background/line_segment.py b/src/easydiffraction/experiments/categories/background/line_segment.py index bf202ce3..2df4ca2b 100644 --- a/src/easydiffraction/experiments/categories/background/line_segment.py +++ b/src/easydiffraction/experiments/categories/background/line_segment.py @@ -139,7 +139,7 @@ def _update(self, called_by_minimizer=False): if not self._items: log.debug('No background points found. Setting background to zero.') - data._set_bkg(np.zeros_like(x)) + data._set_intensity_bkg(np.zeros_like(x)) return segments_x = np.array([point.x.value for point in self._items]) @@ -153,7 +153,7 @@ def _update(self, called_by_minimizer=False): ) y = interp_func(x) - data._set_bkg(y) + data._set_intensity_bkg(y) def show(self) -> None: """Print a table of control points (x, intensity).""" diff --git a/src/easydiffraction/experiments/categories/data/bragg_pd.py b/src/easydiffraction/experiments/categories/data/bragg_pd.py index da92da30..70b0991c 100644 --- a/src/easydiffraction/experiments/categories/data/bragg_pd.py +++ b/src/easydiffraction/experiments/categories/data/bragg_pd.py @@ -214,8 +214,8 @@ def time_of_flight(self) -> NumericDescriptor: class PdCwlDataPoint( - PdDataPointBaseMixin, - PdCwlDataPointMixin, + PdDataPointBaseMixin, # TODO: rename to BasePdDataPointMixin??? + PdCwlDataPointMixin, # TODO: rename to CwlPdDataPointMixin??? CategoryItem, # Must be last to ensure mixins initialized first ): """Powder diffraction data point for constant-wavelength @@ -250,6 +250,10 @@ class PdDataBase(CategoryCollection): # default _update_priority = 100 + ################# + # Private methods + ################# + # Should be set only once def _set_point_id(self, values) -> None: @@ -257,12 +261,12 @@ def _set_point_id(self, values) -> None: for p, v in zip(self._items, values, strict=True): p.point_id._value = v - def _set_meas(self, values) -> None: + def _set_intensity_meas(self, values) -> None: """Helper method to set measured intensity.""" for p, v in zip(self._items, values, strict=True): p.intensity_meas._value = v - def _set_meas_su(self, values) -> None: + def _set_intensity_meas_su(self, values) -> None: """Helper method to set standard uncertainty of measured intensity. """ @@ -276,12 +280,12 @@ def _set_d_spacing(self, values) -> None: for p, v in zip(self._calc_items, values, strict=True): p.d_spacing._value = v - def _set_calc(self, values) -> None: + def _set_intensity_calc(self, values) -> None: """Helper method to set calculated intensity.""" for p, v in zip(self._calc_items, values, strict=True): p.intensity_calc._value = v - def _set_bkg(self, values) -> None: + def _set_intensity_bkg(self, values) -> None: """Helper method to set background intensity.""" for p, v in zip(self._calc_items, values, strict=True): p.intensity_bkg._value = v @@ -307,20 +311,66 @@ def _calc_items(self): """Get only the items included in calculations.""" return [item for item, mask in zip(self._items, self._calc_mask, strict=False) if mask] + # Misc + + def _update(self, called_by_minimizer=False): + experiment = self._parent + experiments = experiment._parent + project = experiments._parent + sample_models = project.sample_models + # calculator = experiment.calculator # TODO: move from analysis + calculator = project.analysis.calculator + + initial_calc = np.zeros_like(self.x) + calc = initial_calc + + # TODO: refactor _get_valid_linked_phases to only be responsible + # for returning list. Warning message should be defined here, + # at least some of them. + # TODO: Adapt following the _update method in bragg_sc.py + for linked_phase in experiment._get_valid_linked_phases(sample_models): + sample_model_id = linked_phase._identity.category_entry_name + sample_model_scale = linked_phase.scale.value + sample_model = sample_models[sample_model_id] + + sample_model_calc = calculator.calculate_pattern( + sample_model, + experiment, + called_by_minimizer=called_by_minimizer, + ) + + sample_model_scaled_calc = sample_model_scale * sample_model_calc + calc += sample_model_scaled_calc + + self._set_intensity_calc(calc + self.intensity_bkg) + + ################### + # Public properties + ################### + @property def calc_status(self) -> np.ndarray: - return np.fromiter((p.calc_status.value for p in self._items), dtype=object) + return np.fromiter( + (p.calc_status.value for p in self._items), + dtype=object, # TODO: needed? DataTypes.NUMERIC? + ) @property - def d(self) -> np.ndarray: - return np.fromiter((p.d_spacing.value for p in self._calc_items), dtype=float) + def d_spacing(self) -> np.ndarray: + return np.fromiter( + (p.d_spacing.value for p in self._calc_items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) @property - def meas(self) -> np.ndarray: - return np.fromiter((p.intensity_meas.value for p in self._calc_items), dtype=float) + def intensity_meas(self) -> np.ndarray: + return np.fromiter( + (p.intensity_meas.value for p in self._calc_items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) @property - def meas_su(self) -> np.ndarray: + def intensity_meas_su(self) -> np.ndarray: # TODO: The following is a temporary workaround to handle zero # or near-zero uncertainties in the data, when dats is loaded # from CIF files. This is necessary because zero uncertainties @@ -333,44 +383,27 @@ def meas_su(self) -> np.ndarray: # BraggPdExperiment._load_ascii_data_to_experiment() handles # this for ASCII data, but we also need to handle CIF data and # come up with a consistent approach for both data sources. - original = np.fromiter((p.intensity_meas_su.value for p in self._calc_items), dtype=float) + original = np.fromiter( + (p.intensity_meas_su.value for p in self._calc_items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) # Replace values smaller than 0.0001 with 1.0 modified = np.where(original < 0.0001, 1.0, original) return modified @property - def calc(self) -> np.ndarray: - return np.fromiter((p.intensity_calc.value for p in self._calc_items), dtype=float) + def intensity_calc(self) -> np.ndarray: + return np.fromiter( + (p.intensity_calc.value for p in self._calc_items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) @property - def bkg(self) -> np.ndarray: - return np.fromiter((p.intensity_bkg.value for p in self._calc_items), dtype=float) - - def _update(self, called_by_minimizer=False): - experiment = self._parent - experiments = experiment._parent - project = experiments._parent - sample_models = project.sample_models - # calculator = experiment.calculator # TODO: move from analysis - calculator = project.analysis.calculator - - initial_calc = np.zeros_like(self.x) - calc = initial_calc - for linked_phase in experiment._get_valid_linked_phases(sample_models): - sample_model_id = linked_phase._identity.category_entry_name - sample_model_scale = linked_phase.scale.value - sample_model = sample_models[sample_model_id] - - sample_model_calc = calculator.calculate_pattern( - sample_model, - experiment, - called_by_minimizer=called_by_minimizer, - ) - - sample_model_scaled_calc = sample_model_scale * sample_model_calc - calc += sample_model_scaled_calc - - self._set_calc(calc + self.bkg) + def intensity_bkg(self) -> np.ndarray: + return np.fromiter( + (p.intensity_bkg.value for p in self._calc_items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) class PdCwlData(PdDataBase): @@ -381,27 +414,27 @@ class PdCwlData(PdDataBase): def __init__(self): super().__init__(item_type=PdCwlDataPoint) + ################# + # Private methods + ################# + # Should be set only once - def _set_x(self, values) -> None: + def _create_items_set_xcoord_and_id(self, values) -> None: """Helper method to set 2θ values.""" # TODO: split into multiple methods + + # Create items self._items = [self._item_type() for _ in range(values.size)] + + # Set two-theta values for p, v in zip(self._items, values, strict=True): p.two_theta._value = v - self._set_point_id([str(i + 1) for i in range(values.size)]) - @property - def all_x(self) -> np.ndarray: - """Get the 2θ values for all data points in this collection.""" - return np.fromiter((p.two_theta.value for p in self._items), dtype=float) + # Set point IDs + self._set_point_id([str(i + 1) for i in range(values.size)]) - @property - def x(self) -> np.ndarray: - """Get the 2θ values for data points included in - calculations. - """ - return np.fromiter((p.two_theta.value for p in self._calc_items), dtype=float) + # Misc def _update(self, called_by_minimizer=False): super()._update(called_by_minimizer) @@ -413,6 +446,33 @@ def _update(self, called_by_minimizer=False): ) self._set_d_spacing(d_spacing) + ################### + # Public properties + ################### + + @property + def two_theta(self) -> np.ndarray: + """Get the 2θ values for data points included in + calculations. + """ + return np.fromiter( + (p.two_theta.value for p in self._calc_items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) + + @property + def x(self) -> np.ndarray: + """Alias for two_theta.""" + return self.two_theta + + @property + def unfiltered_x(self) -> np.ndarray: + """Get the 2θ values for all data points in this collection.""" + return np.fromiter( + (p.two_theta.value for p in self._items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) + class PdTofData(PdDataBase): # TODO: ??? @@ -422,25 +482,27 @@ class PdTofData(PdDataBase): def __init__(self): super().__init__(item_type=PdTofDataPoint) - def _set_x(self, values) -> None: + ################# + # Private methods + ################# + + # Should be set only once + + def _create_items_set_xcoord_and_id(self, values) -> None: """Helper method to set time-of-flight values.""" # TODO: split into multiple methods + + # Create items self._items = [self._item_type() for _ in range(values.size)] + + # Set time-of-flight values for p, v in zip(self._items, values, strict=True): p.time_of_flight._value = v - self._set_point_id([str(i + 1) for i in range(values.size)]) - @property - def all_x(self) -> np.ndarray: - """Get the TOF values for all data points in this collection.""" - return np.fromiter((p.time_of_flight.value for p in self._items), dtype=float) + # Set point IDs + self._set_point_id([str(i + 1) for i in range(values.size)]) - @property - def x(self) -> np.ndarray: - """Get the TOF values for data points included in - calculations. - """ - return np.fromiter((p.time_of_flight.value for p in self._calc_items), dtype=float) + # Misc def _update(self, called_by_minimizer=False): super()._update(called_by_minimizer) @@ -453,3 +515,30 @@ def _update(self, called_by_minimizer=False): experiment.instrument.calib_d_to_tof_quad.value, ) self._set_d_spacing(d_spacing) + + ################### + # Public properties + ################### + + @property + def time_of_flight(self) -> np.ndarray: + """Get the TOF values for data points included in + calculations. + """ + return np.fromiter( + (p.time_of_flight.value for p in self._calc_items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) + + @property + def x(self) -> np.ndarray: + """Alias for time_of_flight.""" + return self.time_of_flight + + @property + def unfiltered_x(self) -> np.ndarray: + """Get the TOF values for all data points in this collection.""" + return np.fromiter( + (p.time_of_flight.value for p in self._items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) diff --git a/src/easydiffraction/experiments/categories/data/bragg_sc.py b/src/easydiffraction/experiments/categories/data/bragg_sc.py index f738b2df..c48a15e9 100644 --- a/src/easydiffraction/experiments/categories/data/bragg_sc.py +++ b/src/easydiffraction/experiments/categories/data/bragg_sc.py @@ -3,22 +3,79 @@ from __future__ import annotations +import numpy as np + 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 +from easydiffraction.utils.logging import log +from easydiffraction.utils.utils import sin_theta_over_lambda_to_d_spacing + +class Refln(CategoryItem): + """Single reflection for single crystal diffraction data + category. + """ -class Refln: - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self) -> None: + super().__init__() - self._index_h = StringDescriptor( + self._id = StringDescriptor( + name='id', + description='Identifier of the reflection.', + value_spec=AttributeSpec( + type_=DataTypes.STRING, + default='0', + # 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-z0-9_]*$'), + ), + cif_handler=CifHandler( + names=[ + '_refln.id', + ] + ), + ) + self._d_spacing = NumericDescriptor( + name='d_spacing', + description='The distance between lattice planes in the crystal for this reflection.', + value_spec=AttributeSpec( + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(ge=0), + ), + units='Å', + cif_handler=CifHandler( + names=[ + '_refln.d_spacing', + ] + ), + ) + self._sin_theta_over_lambda = NumericDescriptor( + name='sin_theta_over_lambda', + description='The sin(θ)/λ value for this reflection.', + value_spec=AttributeSpec( + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(ge=0), + ), + units='Å⁻¹', + cif_handler=CifHandler( + names=[ + '_refln.sin_theta_over_lambda', + ] + ), + ) + self._index_h = NumericDescriptor( name='index_h', - description='...', + description='Miller index h of a measured reflection.', value_spec=AttributeSpec( type_=DataTypes.NUMERIC, default=0.0, @@ -30,9 +87,9 @@ def __init__(self, **kwargs): ] ), ) - self._index_k = StringDescriptor( + self._index_k = NumericDescriptor( name='index_k', - description='...', + description='Miller index k of a measured reflection.', value_spec=AttributeSpec( type_=DataTypes.NUMERIC, default=0.0, @@ -44,9 +101,9 @@ def __init__(self, **kwargs): ] ), ) - self._index_l = StringDescriptor( + self._index_l = NumericDescriptor( name='index_l', - description='...', + description='Miller index l of a measured reflection.', value_spec=AttributeSpec( type_=DataTypes.NUMERIC, default=0.0, @@ -60,7 +117,7 @@ def __init__(self, **kwargs): ) self._intensity_meas = NumericDescriptor( name='intensity_meas', - description='...', + description=' The intensity of the reflection derived from the measurements.', value_spec=AttributeSpec( type_=DataTypes.NUMERIC, default=0.0, @@ -74,7 +131,7 @@ def __init__(self, **kwargs): ) self._intensity_meas_su = NumericDescriptor( name='intensity_meas_su', - description='...', + description='Standard uncertainty of the measured intensity.', value_spec=AttributeSpec( type_=DataTypes.NUMERIC, default=0.0, @@ -86,11 +143,251 @@ def __init__(self, **kwargs): ] ), ) + self._intensity_calc = NumericDescriptor( + name='intensity_calc', + description='The intensity of the reflection calculated from the atom site data.', + value_spec=AttributeSpec( + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(ge=0), + ), + cif_handler=CifHandler( + names=[ + '_refln.intensity_calc', + ] + ), + ) + self._wavelength = NumericDescriptor( + name='wavelength', + description='The mean wavelength of radiation used to measure this reflection.', + value_spec=AttributeSpec( + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(ge=0), + ), + units='Å', + cif_handler=CifHandler( + names=[ + '_refln.wavelength', + ] + ), + ) + + self._identity.category_code = 'refln' + self._identity.category_entry_name = lambda: str(self.id.value) + + @property + def id(self) -> StringDescriptor: + return self._id + + @property + def d_spacing(self) -> NumericDescriptor: + return self._d_spacing - class ReflnData(CategoryCollection): - """...""" + @property + def sin_theta_over_lambda(self) -> NumericDescriptor: + return self._sin_theta_over_lambda - _update_priority = 100 + @property + def index_h(self) -> NumericDescriptor: + return self._index_h - def __init__(self): - super().__init__(item_type=Refln) + @property + def index_k(self) -> NumericDescriptor: + return self._index_k + + @property + def index_l(self) -> NumericDescriptor: + return self._index_l + + @property + def intensity_meas(self) -> NumericDescriptor: + return self._intensity_meas + + @property + def intensity_meas_su(self) -> NumericDescriptor: + return self._intensity_meas_su + + @property + def intensity_calc(self) -> NumericDescriptor: + return self._intensity_calc + + @property + def wavelength(self) -> NumericDescriptor: + return self._wavelength + + +class ReflnData(CategoryCollection): + """Collection of reflections for single crystal diffraction data.""" + + _update_priority = 100 + + def __init__(self): + super().__init__(item_type=Refln) + + ################# + # Private methods + ################# + + # Should be set only once + + def _create_items_set_hkl_and_id(self, indices_h, indices_k, indices_l) -> None: + """Helper method to set Miller indices.""" + # TODO: split into multiple methods + + # Create items + self._items = [self._item_type() for _ in range(indices_h.size)] + + # Set indices + for item, index_h, index_k, index_l in zip( + self._items, indices_h, indices_k, indices_l, strict=True + ): + item.index_h._value = index_h + item.index_k._value = index_k + item.index_l._value = index_l + + # Set reflection IDs + self._set_id([str(i + 1) for i in range(indices_h.size)]) + + def _set_id(self, values) -> None: + """Helper method to set reflection IDs.""" + for p, v in zip(self._items, values, strict=True): + p.id._value = v + + def _set_intensity_meas(self, values) -> None: + """Helper method to set measured intensity.""" + for p, v in zip(self._items, values, strict=True): + p.intensity_meas._value = v + + def _set_intensity_meas_su(self, values) -> None: + """Helper method to set standard uncertainty of measured + intensity. + """ + for p, v in zip(self._items, values, strict=True): + p.intensity_meas_su._value = v + + def _set_wavelength(self, values) -> None: + """Helper method to set wavelength.""" + for p, v in zip(self._items, values, strict=True): + p.wavelength._value = v + + # Can be set multiple times + + def _set_d_spacing(self, values) -> None: + """Helper method to set d-spacing values.""" + for p, v in zip(self._items, values, strict=True): + p.d_spacing._value = v + + def _set_sin_theta_over_lambda(self, values) -> None: + """Helper method to set sin(theta)/lambda values.""" + for p, v in zip(self._items, values, strict=True): + p.sin_theta_over_lambda._value = v + + def _set_intensity_calc(self, values) -> None: + """Helper method to set calculated intensity.""" + for p, v in zip(self._items, values, strict=True): + p.intensity_calc._value = v + + # Misc + + def _update(self, called_by_minimizer=False): + experiment = self._parent + experiments = experiment._parent + project = experiments._parent + sample_models = project.sample_models + # calculator = experiment.calculator # TODO: move from analysis + calculator = project.analysis.calculator + + linked_crystal = experiment.linked_crystal + linked_crystal_id = experiment.linked_crystal.id.value + + if linked_crystal_id not in sample_models.names: + log.error( + f"Linked crystal ID '{linked_crystal_id}' not found in " + f'sample model IDs {sample_models.names}.' + ) + return + + sample_model_id = linked_crystal_id + sample_model_scale = linked_crystal.scale.value + sample_model = sample_models[sample_model_id] + + stol, raw_calc = calculator.calculate_structure_factors( + sample_model, + experiment, + called_by_minimizer=called_by_minimizer, + ) + + d_spacing = sin_theta_over_lambda_to_d_spacing(stol) + calc = sample_model_scale * raw_calc + + self._set_d_spacing(d_spacing) + self._set_sin_theta_over_lambda(stol) + self._set_intensity_calc(calc) + + ################### + # Public properties + ################### + + @property + def d_spacing(self) -> np.ndarray: + return np.fromiter( + (p.d_spacing.value for p in self._items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) + + @property + def sin_theta_over_lambda(self) -> np.ndarray: + return np.fromiter( + (p.sin_theta_over_lambda.value for p in self._items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) + + @property + def index_h(self) -> np.ndarray: + return np.fromiter( + (p.index_h.value for p in self._items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) + + @property + def index_k(self) -> np.ndarray: + return np.fromiter( + (p.index_k.value for p in self._items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) + + @property + def index_l(self) -> np.ndarray: + return np.fromiter( + (p.index_l.value for p in self._items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) + + @property + def intensity_meas(self) -> np.ndarray: + return np.fromiter( + (p.intensity_meas.value for p in self._items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) + + @property + def intensity_meas_su(self) -> np.ndarray: + return np.fromiter( + (p.intensity_meas_su.value for p in self._items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) + + @property + def intensity_calc(self) -> np.ndarray: + return np.fromiter( + (p.intensity_calc.value for p in self._items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) + + @property + def wavelength(self) -> np.ndarray: + return np.fromiter( + (p.wavelength.value for p in self._items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) diff --git a/src/easydiffraction/experiments/categories/data/factory.py b/src/easydiffraction/experiments/categories/data/factory.py index 2d60560c..a7d4df0a 100644 --- a/src/easydiffraction/experiments/categories/data/factory.py +++ b/src/easydiffraction/experiments/categories/data/factory.py @@ -8,7 +8,8 @@ from easydiffraction.experiments.categories.data.bragg_pd import PdCwlData from easydiffraction.experiments.categories.data.bragg_pd import PdTofData -from easydiffraction.experiments.categories.data.total import TotalData +from easydiffraction.experiments.categories.data.bragg_sc import ReflnData +from easydiffraction.experiments.categories.data.total_pd import TotalData from easydiffraction.experiments.experiment.enums import BeamModeEnum from easydiffraction.experiments.experiment.enums import SampleFormEnum from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum @@ -18,7 +19,7 @@ class DataFactory: - """Factory for creating powder diffraction data collections.""" + """Factory for creating diffraction data collections.""" _supported = { SampleFormEnum.POWDER: { @@ -31,6 +32,12 @@ class DataFactory: BeamModeEnum.TIME_OF_FLIGHT: TotalData, }, }, + SampleFormEnum.SINGLE_CRYSTAL: { + ScatteringTypeEnum.BRAGG: { + BeamModeEnum.CONSTANT_WAVELENGTH: ReflnData, + BeamModeEnum.TIME_OF_FLIGHT: ReflnData, + }, + }, } @classmethod diff --git a/src/easydiffraction/experiments/categories/data/total.py b/src/easydiffraction/experiments/categories/data/total_pd.py similarity index 79% rename from src/easydiffraction/experiments/categories/data/total.py rename to src/easydiffraction/experiments/categories/data/total_pd.py index 5dc69b9d..0e43c3a4 100644 --- a/src/easydiffraction/experiments/categories/data/total.py +++ b/src/easydiffraction/experiments/categories/data/total_pd.py @@ -145,6 +145,10 @@ class TotalDataBase(CategoryCollection): _update_priority = 100 + ################# + # Private methods + ################# + # Should be set only once def _set_point_id(self, values) -> None: @@ -152,12 +156,12 @@ def _set_point_id(self, values) -> None: for p, v in zip(self._items, values, strict=True): p.point_id._value = v - def _set_meas(self, values) -> None: + def _set_g_r_meas(self, values) -> None: """Helper method to set measured G(r).""" for p, v in zip(self._items, values, strict=True): p.g_r_meas._value = v - def _set_meas_su(self, values) -> None: + def _set_g_r_meas_su(self, values) -> None: """Helper method to set standard uncertainty of measured G(r). """ @@ -166,7 +170,7 @@ def _set_meas_su(self, values) -> None: # Can be set multiple times - def _set_calc(self, values) -> None: + def _set_g_r_calc(self, values) -> None: """Helper method to set calculated G(r).""" for p, v in zip(self._calc_items, values, strict=True): p.g_r_calc._value = v @@ -192,36 +196,23 @@ def _calc_items(self): """Get only the items included in calculations.""" return [item for item, mask in zip(self._items, self._calc_mask, strict=False) if mask] - @property - def calc_status(self) -> np.ndarray: - return np.fromiter((p.calc_status.value for p in self._items), dtype=object) - - @property - def meas(self) -> np.ndarray: - return np.fromiter((p.g_r_meas.value for p in self._calc_items), dtype=float) - - @property - def meas_su(self) -> np.ndarray: - return np.fromiter((p.g_r_meas_su.value for p in self._calc_items), dtype=float) - - @property - def calc(self) -> np.ndarray: - return np.fromiter((p.g_r_calc.value for p in self._calc_items), dtype=float) - - @property - def bkg(self) -> np.ndarray: - """Background is always zero for PDF data.""" - return np.zeros_like(self.calc) + # Misc def _update(self, called_by_minimizer=False): experiment = self._parent experiments = experiment._parent project = experiments._parent sample_models = project.sample_models + # calculator = experiment.calculator # TODO: move from analysis calculator = project.analysis.calculator initial_calc = np.zeros_like(self.x) calc = initial_calc + + # TODO: refactor _get_valid_linked_phases to only be responsible + # for returning list. Warning message should be defined here, + # at least some of them. + # TODO: Adapt following the _update method in bragg_sc.py for linked_phase in experiment._get_valid_linked_phases(sample_models): sample_model_id = linked_phase._identity.category_entry_name sample_model_scale = linked_phase.scale.value @@ -236,7 +227,44 @@ def _update(self, called_by_minimizer=False): sample_model_scaled_calc = sample_model_scale * sample_model_calc calc += sample_model_scaled_calc - self._set_calc(calc) + self._set_g_r_calc(calc) + + ################### + # Public properties + ################### + + @property + def calc_status(self) -> np.ndarray: + return np.fromiter( + (p.calc_status.value for p in self._items), + dtype=object, # TODO: needed? DataTypes.NUMERIC? + ) + + @property + def intensity_meas(self) -> np.ndarray: + return np.fromiter( + (p.g_r_meas.value for p in self._calc_items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) + + @property + def intensity_meas_su(self) -> np.ndarray: + return np.fromiter( + (p.g_r_meas_su.value for p in self._calc_items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) + + @property + def intensity_calc(self) -> np.ndarray: + return np.fromiter( + (p.g_r_calc.value for p in self._calc_items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) + + @property + def intensity_bkg(self) -> np.ndarray: + """Background is always zero for PDF data.""" + return np.zeros_like(self.intensity_calc) class TotalData(TotalDataBase): @@ -249,19 +277,42 @@ class TotalData(TotalDataBase): def __init__(self): super().__init__(item_type=TotalDataPoint) - def _set_x(self, values) -> None: + ################# + # Private methods + ################# + + # Should be set only once + + def _create_items_set_xcoord_and_id(self, values) -> None: """Helper method to set r values.""" + # TODO: split into multiple methods + + # Create items self._items = [self._item_type() for _ in range(values.size)] + + # Set r values for p, v in zip(self._items, values, strict=True): p.r._value = v + + # Set point IDs self._set_point_id([str(i + 1) for i in range(values.size)]) - @property - def all_x(self) -> np.ndarray: - """Get the r values for all data points.""" - return np.fromiter((p.r.value for p in self._items), dtype=float) + ################### + # Public properties + ################### @property def x(self) -> np.ndarray: """Get the r values for data points included in calculations.""" - return np.fromiter((p.r.value for p in self._calc_items), dtype=float) + return np.fromiter( + (p.r.value for p in self._calc_items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) + + @property + def unfiltered_x(self) -> np.ndarray: + """Get the r values for all data points.""" + return np.fromiter( + (p.r.value for p in self._items), + dtype=float, # TODO: needed? DataTypes.NUMERIC? + ) diff --git a/src/easydiffraction/experiments/categories/excluded_regions.py b/src/easydiffraction/experiments/categories/excluded_regions.py index 0ebce0a4..c882fad0 100644 --- a/src/easydiffraction/experiments/categories/excluded_regions.py +++ b/src/easydiffraction/experiments/categories/excluded_regions.py @@ -126,7 +126,7 @@ def _update(self, called_by_minimizer=False): del called_by_minimizer data = self._parent.data - x = data.all_x + x = data.unfiltered_x # Start with a mask of all False (nothing excluded yet) combined_mask = np.full_like(x, fill_value=False, dtype=bool) diff --git a/src/easydiffraction/experiments/categories/extinction.py b/src/easydiffraction/experiments/categories/extinction.py new file mode 100644 index 00000000..329f3ca5 --- /dev/null +++ b/src/easydiffraction/experiments/categories/extinction.py @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +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 Extinction(CategoryItem): + """Extinction correction category for single crystals.""" + + def __init__(self) -> None: + super().__init__() + + self._mosaicity = Parameter( + name='mosaicity', + description='Mosaicity value for extinction correction.', + value_spec=AttributeSpec( + type_=DataTypes.NUMERIC, + default=1.0, + content_validator=RangeValidator(), + ), + units='deg', + cif_handler=CifHandler( + names=[ + '_extinction.mosaicity', + ] + ), + ) + self._radius = Parameter( + name='radius', + description='Crystal radius for extinction correction.', + value_spec=AttributeSpec( + type_=DataTypes.NUMERIC, + default=1.0, + content_validator=RangeValidator(), + ), + units='µm', + cif_handler=CifHandler( + names=[ + '_extinction.radius', + ] + ), + ) + + self._identity.category_code = 'extinction' + + @property + def mosaicity(self): + return self._mosaicity + + @mosaicity.setter + def mosaicity(self, value): + self._mosaicity.value = value + + @property + def radius(self): + return self._radius + + @radius.setter + def radius(self, value): + self._radius.value = value diff --git a/src/easydiffraction/experiments/categories/instrument/cwl.py b/src/easydiffraction/experiments/categories/instrument/cwl.py index 0653f93c..8c714c11 100644 --- a/src/easydiffraction/experiments/categories/instrument/cwl.py +++ b/src/easydiffraction/experiments/categories/instrument/cwl.py @@ -9,20 +9,14 @@ from easydiffraction.io.cif.handler import CifHandler -class CwlInstrument(InstrumentBase): - def __init__( - self, - *, - setup_wavelength=None, - calib_twotheta_offset=None, - ) -> None: +class CwlInstrumentBase(InstrumentBase): + def __init__(self) -> 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(), @@ -34,11 +28,31 @@ def __init__( ] ), ) + + @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 + + +class CwlScInstrument(CwlInstrumentBase): + def __init__(self) -> None: + super().__init__() + + +class CwlPdInstrument(CwlInstrumentBase): + def __init__(self) -> None: + super().__init__() + 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(), @@ -51,16 +65,6 @@ def __init__( ), ) - @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).""" diff --git a/src/easydiffraction/experiments/categories/instrument/factory.py b/src/easydiffraction/experiments/categories/instrument/factory.py index 443957a1..d1d5982c 100644 --- a/src/easydiffraction/experiments/categories/instrument/factory.py +++ b/src/easydiffraction/experiments/categories/instrument/factory.py @@ -13,6 +13,7 @@ from typing import Type from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum if TYPE_CHECKING: @@ -28,17 +29,26 @@ class InstrumentFactory: ST = ScatteringTypeEnum BM = BeamModeEnum + SF = SampleFormEnum @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 + from easydiffraction.experiments.categories.instrument.cwl import CwlPdInstrument + from easydiffraction.experiments.categories.instrument.cwl import CwlScInstrument + from easydiffraction.experiments.categories.instrument.tof import TofPdInstrument + from easydiffraction.experiments.categories.instrument.tof import TofScInstrument return { cls.ST.BRAGG: { - cls.BM.CONSTANT_WAVELENGTH: CwlInstrument, - cls.BM.TIME_OF_FLIGHT: TofInstrument, + cls.BM.CONSTANT_WAVELENGTH: { + cls.SF.POWDER: CwlPdInstrument, + cls.SF.SINGLE_CRYSTAL: CwlScInstrument, + }, + cls.BM.TIME_OF_FLIGHT: { + cls.SF.POWDER: TofPdInstrument, + cls.SF.SINGLE_CRYSTAL: TofScInstrument, + }, } } @@ -47,11 +57,14 @@ def create( cls, scattering_type: Optional[ScatteringTypeEnum] = None, beam_mode: Optional[BeamModeEnum] = None, + sample_form: Optional[SampleFormEnum] = None, ) -> InstrumentBase: if beam_mode is None: beam_mode = BeamModeEnum.default() if scattering_type is None: scattering_type = ScatteringTypeEnum.default() + if sample_form is None: + sample_form = SampleFormEnum.default() supported = cls._supported_map() @@ -70,5 +83,13 @@ def create( f'Supported beam modes: {supported_beam_modes}' ) - instrument_class: Type[InstrumentBase] = supported[scattering_type][beam_mode] + supported_sample_forms = list(supported[scattering_type][beam_mode].keys()) + if sample_form not in supported_sample_forms: + raise ValueError( + f"Unsupported sample form: '{sample_form}' for scattering type: " + f"'{scattering_type}' and beam mode: '{beam_mode}'.\n " + f'Supported sample forms: {supported_sample_forms}' + ) + + instrument_class: Type[InstrumentBase] = supported[scattering_type][beam_mode][sample_form] return instrument_class() diff --git a/src/easydiffraction/experiments/categories/instrument/tof.py b/src/easydiffraction/experiments/categories/instrument/tof.py index ede52d3c..0b3d6558 100644 --- a/src/easydiffraction/experiments/categories/instrument/tof.py +++ b/src/easydiffraction/experiments/categories/instrument/tof.py @@ -9,23 +9,19 @@ 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: +class TofScInstrument(InstrumentBase): + def __init__(self) -> None: + super().__init__() + + +class TofPdInstrument(InstrumentBase): + def __init__(self) -> 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(), @@ -41,7 +37,6 @@ def __init__( 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(), @@ -57,7 +52,6 @@ def __init__( 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(), @@ -73,7 +67,6 @@ def __init__( 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(), @@ -89,7 +82,6 @@ def __init__( 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(), diff --git a/src/easydiffraction/experiments/categories/linked_crystal.py b/src/easydiffraction/experiments/categories/linked_crystal.py new file mode 100644 index 00000000..81417de2 --- /dev/null +++ b/src/easydiffraction/experiments/categories/linked_crystal.py @@ -0,0 +1,71 @@ +# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +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 LinkedCrystal(CategoryItem): + """Linked crystal category for referencing from the experiment for + single crystal diffraction. + """ + + def __init__(self) -> None: + super().__init__() + + self._id = StringDescriptor( + name='id', + description='Identifier of the linked crystal.', + value_spec=AttributeSpec( + type_=DataTypes.STRING, + default='Si', + content_validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), + ), + cif_handler=CifHandler( + names=[ + '_sc_crystal_block.id', + ] + ), + ) + self._scale = Parameter( + name='scale', + description='Scale factor of the linked crystal.', + value_spec=AttributeSpec( + type_=DataTypes.NUMERIC, + default=1.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_sc_crystal_block.scale', + ] + ), + ) + + self._identity.category_code = 'linked_crystal' + + @property + def id(self) -> StringDescriptor: + """Identifier of the linked crystal.""" + return self._id + + @id.setter + def id(self, value: str): + """Set the linked crystal 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 diff --git a/src/easydiffraction/experiments/categories/linked_phases.py b/src/easydiffraction/experiments/categories/linked_phases.py index 3ff827d5..497ef69c 100644 --- a/src/easydiffraction/experiments/categories/linked_phases.py +++ b/src/easydiffraction/experiments/categories/linked_phases.py @@ -54,6 +54,7 @@ def __init__( ] ), ) + self._identity.category_code = 'linked_phases' self._identity.category_entry_name = lambda: str(self.id.value) diff --git a/src/easydiffraction/experiments/experiment/__init__.py b/src/easydiffraction/experiments/experiment/__init__.py index a2a39eea..dee12f85 100644 --- a/src/easydiffraction/experiments/experiment/__init__.py +++ b/src/easydiffraction/experiments/experiment/__init__.py @@ -4,7 +4,8 @@ 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.bragg_sc import CwlScExperiment +from easydiffraction.experiments.experiment.bragg_sc import TofScExperiment from easydiffraction.experiments.experiment.total_pd import TotalPdExperiment __all__ = [ @@ -12,5 +13,6 @@ 'PdExperimentBase', 'BraggPdExperiment', 'TotalPdExperiment', - 'BraggScExperiment', + 'CwlScExperiment', + 'TofScExperiment', ] diff --git a/src/easydiffraction/experiments/experiment/base.py b/src/easydiffraction/experiments/experiment/base.py index 35ed7b1d..27a75c37 100644 --- a/src/easydiffraction/experiments/experiment/base.py +++ b/src/easydiffraction/experiments/experiment/base.py @@ -11,6 +11,9 @@ from easydiffraction.core.datablock import DatablockItem from easydiffraction.experiments.categories.data.factory import DataFactory from easydiffraction.experiments.categories.excluded_regions import ExcludedRegions +from easydiffraction.experiments.categories.extinction import Extinction +from easydiffraction.experiments.categories.instrument.factory import InstrumentFactory +from easydiffraction.experiments.categories.linked_crystal import LinkedCrystal from easydiffraction.experiments.categories.linked_phases import LinkedPhases from easydiffraction.experiments.categories.peak.factory import PeakFactory from easydiffraction.experiments.categories.peak.factory import PeakProfileTypeEnum @@ -95,6 +98,58 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: raise NotImplementedError() +class ScExperimentBase(ExperimentBase): + """Base class for all single crystal experiments.""" + + def __init__( + self, + *, + name: str, + type: ExperimentType, + ) -> None: + super().__init__(name=name, type=type) + + self._linked_crystal: LinkedCrystal = LinkedCrystal() + self._extinction: Extinction = Extinction() + self._instrument = InstrumentFactory.create( + scattering_type=self.type.scattering_type.value, + beam_mode=self.type.beam_mode.value, + sample_form=self.type.sample_form.value, + ) + self._data = DataFactory.create( + sample_form=self.type.sample_form.value, + beam_mode=self.type.beam_mode.value, + scattering_type=self.type.scattering_type.value, + ) + + @abstractmethod + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + """Load single crystal data from an ASCII file. + + Args: + data_path: Path to data file with columns compatible with + the beam mode. + """ + pass + + @property + def linked_crystal(self): + """Linked crystal model for this experiment.""" + return self._linked_crystal + + @property + def extinction(self): + return self._extinction + + @property + def instrument(self): + return self._instrument + + @property + def data(self): + return self._data + + class PdExperimentBase(ExperimentBase): """Base class for all powder experiments.""" @@ -108,22 +163,20 @@ def __init__( self._linked_phases: LinkedPhases = LinkedPhases() self._excluded_regions: ExcludedRegions = ExcludedRegions() - 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._data = DataFactory.create( sample_form=self.type.sample_form.value, beam_mode=self.type.beam_mode.value, scattering_type=self.type.scattering_type.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, + ) def _get_valid_linked_phases( self, @@ -179,6 +232,10 @@ def excluded_regions(self): """Collection of excluded regions for the x-grid.""" return self._excluded_regions + @property + def data(self): + return self._data + @property def peak(self) -> str: """Peak category object with profile parameters and mixins.""" @@ -193,10 +250,6 @@ def peak(self, value): """ self._peak = value - @property - def data(self): - return self._data - @property def peak_profile_type(self): """Currently selected peak profile type enum.""" diff --git a/src/easydiffraction/experiments/experiment/bragg_pd.py b/src/easydiffraction/experiments/experiment/bragg_pd.py index 3821419a..b6d5466c 100644 --- a/src/easydiffraction/experiments/experiment/bragg_pd.py +++ b/src/easydiffraction/experiments/experiment/bragg_pd.py @@ -9,8 +9,8 @@ from easydiffraction.experiments.categories.background.enums import BackgroundTypeEnum from easydiffraction.experiments.categories.background.factory import BackgroundFactory +from easydiffraction.experiments.categories.instrument.factory import InstrumentFactory from easydiffraction.experiments.experiment.base import PdExperimentBase -from easydiffraction.experiments.experiment.instrument_mixin import InstrumentMixin from easydiffraction.utils.logging import console from easydiffraction.utils.logging import log from easydiffraction.utils.utils import render_table @@ -19,10 +19,9 @@ 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. +class BraggPdExperiment(PdExperimentBase): + """Standard (Bragg) Powder Diffraction experiment class with + specific attributes. """ def __init__( @@ -33,28 +32,24 @@ def __init__( ) -> None: super().__init__(name=name, type=type) + self._instrument = InstrumentFactory.create( + scattering_type=self.type.scattering_type.value, + beam_mode=self.type.beam_mode.value, + sample_form=self.type.sample_form.value, + ) 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 data category. The file format is space/column separated with 2 or 3 columns: ``x y [sy]``. If ``sy`` is missing, it is approximated as - ``sqrt(y)`` with small values clamped to ``1.0``. + ``sqrt(y)``. + + If ``sy`` has values smaller than ``0.0001``, they are replaced + with ``1.0``. """ try: data = np.loadtxt(data_path) @@ -78,16 +73,21 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: sy: np.ndarray = data[:, 2] if data.shape[1] > 2 else np.sqrt(y) # Replace values smaller than 0.0001 with 1.0 + # TODO: Not used if loading from cif file? sy = np.where(sy < 0.0001, 1.0, sy) # Set the experiment data - self.data._set_x(x) - self.data._set_meas(y) - self.data._set_meas_su(sy) + self.data._create_items_set_xcoord_and_id(x) + self.data._set_intensity_meas(y) + self.data._set_intensity_meas_su(sy) console.paragraph('Data loaded successfully') console.print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}") + @property + def instrument(self): + return self._instrument + @property def background_type(self): """Current background type enum value.""" @@ -113,6 +113,14 @@ def background_type(self, new_type): console.paragraph(f"Background type for experiment '{self.name}' changed to") console.print(new_type) + @property + def background(self): + return self._background + + @background.setter + def background(self, value): + self._background = value + def show_supported_background_types(self): """Print a table of supported background types.""" columns_headers = ['Background type', 'Description'] diff --git a/src/easydiffraction/experiments/experiment/bragg_sc.py b/src/easydiffraction/experiments/experiment/bragg_sc.py index 6b95d559..60ade068 100644 --- a/src/easydiffraction/experiments/experiment/bragg_sc.py +++ b/src/easydiffraction/experiments/experiment/bragg_sc.py @@ -1,19 +1,24 @@ # SPDX-FileCopyrightText: 2021-2026 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 +import numpy as np + +from easydiffraction.experiments.experiment.base import ScExperimentBase +from easydiffraction.utils.logging import console +from easydiffraction.utils.logging import log if TYPE_CHECKING: from easydiffraction.experiments.categories.experiment_type import ExperimentType -class BraggScExperiment(ExperimentBase): - """Single crystal experiment class with specific attributes.""" +class CwlScExperiment(ScExperimentBase): + """Standard (Bragg) constant wavelength single srystal experiment + class with specific attributes. + """ def __init__( self, @@ -22,7 +27,99 @@ def __init__( 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.') + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + """Load measured data from an ASCII file into the data category. + + The file format is space/column separated with 5 columns: + ``h k l Iobs sIobs``. + """ + try: + data = np.loadtxt(data_path) + except Exception as e: + log.error( + f'Failed to read data from {data_path}: {e}', + exc_type=IOError, + ) + return + + if data.shape[1] < 5: + log.error( + 'Data file must have at least 5 columns: h, k, l, Iobs, sIobs.', + exc_type=ValueError, + ) + return + + # Extract Miller indices h, k, l + indices_h: np.ndarray = data[:, 0].astype(int) + indices_k: np.ndarray = data[:, 1].astype(int) + indices_l: np.ndarray = data[:, 2].astype(int) + + # Extract intensities and their standard uncertainties + integrated_intensities: np.ndarray = data[:, 3] + integrated_intensities_su: np.ndarray = data[:, 4] + + # Set the experiment data + self.data._create_items_set_hkl_and_id(indices_h, indices_k, indices_l) + self.data._set_intensity_meas(integrated_intensities) + self.data._set_intensity_meas_su(integrated_intensities_su) + + console.paragraph('Data loaded successfully') + console.print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(indices_h)}") + + +class TofScExperiment(ScExperimentBase): + """Standard (Bragg) time-of-flight single srystal experiment class + with specific attributes. + """ + + def __init__( + self, + *, + name: str, + type: ExperimentType, + ) -> None: + super().__init__(name=name, type=type) + + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + """Load measured data from an ASCII file into the data category. + + The file format is space/column separated with 6 columns: + ``h k l Iobs sIobs wavelength``. + """ + try: + data = np.loadtxt(data_path) + except Exception as e: + log.error( + f'Failed to read data from {data_path}: {e}', + exc_type=IOError, + ) + return + + if data.shape[1] < 6: + log.error( + 'Data file must have at least 6 columns: h, k, l, Iobs, sIobs, wavelength.', + exc_type=ValueError, + ) + return + + # Extract Miller indices h, k, l + indices_h: np.ndarray = data[:, 0].astype(int) + indices_k: np.ndarray = data[:, 1].astype(int) + indices_l: np.ndarray = data[:, 2].astype(int) + + # Extract intensities and their standard uncertainties + integrated_intensities: np.ndarray = data[:, 3] + integrated_intensities_su: np.ndarray = data[:, 4] + + # Extract wavelength values + wavelength: np.ndarray = data[:, 5] + + # Set the experiment data + self.data._create_items_set_hkl_and_id(indices_h, indices_k, indices_l) + self.data._set_intensity_meas(integrated_intensities) + self.data._set_intensity_meas_su(integrated_intensities_su) + self.data._set_wavelength(wavelength) + + console.paragraph('Data loaded successfully') + console.print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(indices_h)}") diff --git a/src/easydiffraction/experiments/experiment/factory.py b/src/easydiffraction/experiments/experiment/factory.py index 0db5e347..b1edc64e 100644 --- a/src/easydiffraction/experiments/experiment/factory.py +++ b/src/easydiffraction/experiments/experiment/factory.py @@ -8,7 +8,8 @@ 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 CwlScExperiment +from easydiffraction.experiments.experiment import TofScExperiment from easydiffraction.experiments.experiment import TotalPdExperiment from easydiffraction.experiments.experiment.enums import BeamModeEnum from easydiffraction.experiments.experiment.enums import RadiationProbeEnum @@ -29,25 +30,53 @@ 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': ['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'], + 'optional': [ + 'sample_form', + 'beam_mode', + 'radiation_probe', + 'scattering_type', + ], }, ] _SUPPORTED = { ScatteringTypeEnum.BRAGG: { - SampleFormEnum.POWDER: BraggPdExperiment, - SampleFormEnum.SINGLE_CRYSTAL: BraggScExperiment, + SampleFormEnum.POWDER: { + BeamModeEnum.CONSTANT_WAVELENGTH: BraggPdExperiment, + BeamModeEnum.TIME_OF_FLIGHT: BraggPdExperiment, + }, + SampleFormEnum.SINGLE_CRYSTAL: { + BeamModeEnum.CONSTANT_WAVELENGTH: CwlScExperiment, + BeamModeEnum.TIME_OF_FLIGHT: TofScExperiment, + }, }, ScatteringTypeEnum.TOTAL: { - SampleFormEnum.POWDER: TotalPdExperiment, + SampleFormEnum.POWDER: { + BeamModeEnum.CONSTANT_WAVELENGTH: TotalPdExperiment, + BeamModeEnum.TIME_OF_FLIGHT: TotalPdExperiment, + }, }, } @@ -84,7 +113,8 @@ def _create_from_gemmi_block( # TODO: make helper method to create experiment from type scattering_type = expt_type.scattering_type.value sample_form = expt_type.sample_form.value - expt_class = cls._SUPPORTED[scattering_type][sample_form] + beam_mode = expt_type.beam_mode.value + expt_class = cls._SUPPORTED[scattering_type][sample_form][beam_mode] expt_obj = expt_class(name=name, type=expt_type) # Read all categories from CIF block @@ -124,7 +154,8 @@ def _create_from_data_path(cls, kwargs): 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] + beam_mode = expt_type.beam_mode.value + expt_class = cls._SUPPORTED[scattering_type][sample_form][beam_mode] expt_name = kwargs['name'] expt_obj = expt_class(name=expt_name, type=expt_type) data_path = kwargs['data_path'] @@ -141,7 +172,8 @@ def _create_without_data(cls, kwargs): 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] + beam_mode = expt_type.beam_mode.value + expt_class = cls._SUPPORTED[scattering_type][sample_form][beam_mode] expt_name = kwargs['name'] expt_obj = expt_class(name=expt_name, type=expt_type) return expt_obj diff --git a/src/easydiffraction/experiments/experiment/instrument_mixin.py b/src/easydiffraction/experiments/experiment/instrument_mixin.py deleted file mode 100644 index c83c07bc..00000000 --- a/src/easydiffraction/experiments/experiment/instrument_mixin.py +++ /dev/null @@ -1,46 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2026 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 index 2f2f5f89..6262bd62 100644 --- a/src/easydiffraction/experiments/experiment/total_pd.py +++ b/src/easydiffraction/experiments/experiment/total_pd.py @@ -51,9 +51,9 @@ def _load_ascii_data_to_experiment(self, data_path): y = data[:, 1] sy = data[:, 2] if data.shape[1] > 2 else np.full_like(y, fill_value=default_sy) - self.data._set_x(x) - self.data._set_meas(y) - self.data._set_meas_su(sy) + self.data._create_items_set_xcoord_and_id(x) + self.data._set_g_r_meas(y) + self.data._set_g_r_meas_su(sy) console.paragraph('Data loaded successfully') console.print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}") diff --git a/src/easydiffraction/io/cif/serialize.py b/src/easydiffraction/io/cif/serialize.py index 877f6ab1..b6c9dae5 100644 --- a/src/easydiffraction/io/cif/serialize.py +++ b/src/easydiffraction/io/cif/serialize.py @@ -25,9 +25,14 @@ def format_value(value) -> str: """Format a single CIF value, quoting strings with whitespace, and format floats with global precision. + + .. note:: + The precision must be high enough so that the minimizer's + finite-difference Jacobian probes (typically ~1e-8 relative) + survive the float→string→float round-trip through CIF. """ - width = 8 - precision = 4 + width = 12 + precision = 8 # Converting diff --git a/src/easydiffraction/project/project.py b/src/easydiffraction/project/project.py index 980f680b..5d0a53ca 100644 --- a/src/easydiffraction/project/project.py +++ b/src/easydiffraction/project/project.py @@ -234,7 +234,7 @@ def plot_meas( expt_name, x_min=None, x_max=None, - d_spacing=False, + x=None, ): self._update_categories(expt_name) experiment = self.experiments[expt_name] @@ -245,7 +245,7 @@ def plot_meas( experiment.type, x_min=x_min, x_max=x_max, - d_spacing=d_spacing, + x=x, ) def plot_calc( @@ -253,7 +253,7 @@ def plot_calc( expt_name, x_min=None, x_max=None, - d_spacing=False, + x=None, ): self._update_categories(expt_name) experiment = self.experiments[expt_name] @@ -264,7 +264,7 @@ def plot_calc( experiment.type, x_min=x_min, x_max=x_max, - d_spacing=d_spacing, + x=x, ) def plot_meas_vs_calc( @@ -273,7 +273,7 @@ def plot_meas_vs_calc( x_min=None, x_max=None, show_residual=False, - d_spacing=False, + x=None, ): self._update_categories(expt_name) experiment = self.experiments[expt_name] @@ -285,5 +285,5 @@ def plot_meas_vs_calc( x_min=x_min, x_max=x_max, show_residual=show_residual, - d_spacing=d_spacing, + x=x, ) diff --git a/src/easydiffraction/utils/utils.py b/src/easydiffraction/utils/utils.py index 2b6f9c50..82118302 100644 --- a/src/easydiffraction/utils/utils.py +++ b/src/easydiffraction/utils/utils.py @@ -3,11 +3,11 @@ from __future__ import annotations +import functools import json import pathlib import re import urllib.request -from functools import lru_cache from importlib.metadata import PackageNotFoundError from importlib.metadata import version from typing import List @@ -65,7 +65,6 @@ def _normalize_known_hash(value: str | None) -> str | None: return value -@lru_cache(maxsize=1) def _fetch_data_index() -> dict: """Fetch & cache the diffraction data index.json and return it as dict. @@ -91,7 +90,7 @@ def _fetch_data_index() -> dict: return json.load(f) -@lru_cache(maxsize=1) +@functools.lru_cache(maxsize=1) def _fetch_tutorials_index() -> dict: """Fetch & cache the tutorials index.json from gh-pages and return it as dict. @@ -614,6 +613,24 @@ def twotheta_to_d(twotheta, wavelength): return d +def sin_theta_over_lambda_to_d_spacing(sin_theta_over_lambda): + """Convert sin(theta)/lambda to d-spacing. + + Parameters: + sin_theta_over_lambda (float or np.ndarray): sin(theta)/lambda + in 1/Å. + + Returns: + d (float or np.ndarray): d-spacing in Å. + """ + # Avoid division by zero + with np.errstate(divide='ignore', invalid='ignore'): + d = 1 / (2 * sin_theta_over_lambda) + # Set non-positive inputs to NaN + d = np.where(sin_theta_over_lambda > 0, d, np.nan) + return d + + def get_value_from_xye_header(file_path, key): """Extracts a floating point value from the first line of the file, corresponding to the given key. diff --git a/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py b/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py index bba3b2d2..f8d7fd6d 100644 --- a/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py +++ b/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py @@ -217,15 +217,15 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None: background = expt.background background.add(id='10', x=10, y=174.3) - background.add(id='20',x=20, y=159.8) - background.add(id='30',x=30, y=167.9) - background.add(id='50',x=50, y=166.1) - background.add(id='70',x=70, y=172.3) - background.add(id='90',x=90, y=171.1) - background.add(id='110',x=110, y=172.4) - background.add(id='130',x=130, y=182.5) - background.add(id='150',x=150, y=173.0) - background.add(id='165',x=165, y=171.1) + background.add(id='20', x=20, y=159.8) + background.add(id='30', x=30, y=167.9) + background.add(id='50', x=50, y=166.1) + background.add(id='70', x=70, y=172.3) + background.add(id='90', x=90, y=171.1) + background.add(id='110', x=110, y=172.4) + background.add(id='130', x=130, y=182.5) + background.add(id='150', x=150, y=173.0) + background.add(id='165', x=165, y=171.1) expt.linked_phases.add(id='lbco', scale=9.0976) @@ -279,12 +279,8 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None: # 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(label='occ_La', param_uid=atom_sites['La'].occupancy.uid) + project.analysis.aliases.add(label='occ_Ba', param_uid=atom_sites['Ba'].occupancy.uid) # Set constraints project.analysis.constraints.add(lhs_alias='biso_Ba', rhs_expr='biso_La') diff --git a/tests/integration/fitting/test_powder-diffraction_time-of-flight.py b/tests/integration/fitting/test_powder-diffraction_time-of-flight.py index 129fb7ff..0117a90b 100644 --- a/tests/integration/fitting/test_powder-diffraction_time-of-flight.py +++ b/tests/integration/fitting/test_powder-diffraction_time-of-flight.py @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause -import os import tempfile from numpy.testing import assert_almost_equal diff --git a/tests/integration/fitting/test_single-crystal-diffraction.py b/tests/integration/fitting/test_single-crystal-diffraction.py new file mode 100644 index 00000000..da7da7eb --- /dev/null +++ b/tests/integration/fitting/test_single-crystal-diffraction.py @@ -0,0 +1,90 @@ +# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import tempfile + +import pytest + +import easydiffraction as ed + +TEMP_DIR = tempfile.gettempdir() + + +@pytest.mark.fast +def test_single_fit_neut_sc_cwl_tbti() -> None: + project = ed.Project() + + # Set sample model + model_path = ed.download_data(id=20, destination=TEMP_DIR) + project.sample_models.add(cif_path=model_path) + + # Set experiment + data_path = ed.download_data(id=19, destination=TEMP_DIR) + project.experiments.add( + name='heidi', + data_path=data_path, + sample_form='single crystal', + beam_mode='constant wavelength', + radiation_probe='neutron', + scattering_type='bragg', + ) + experiment = project.experiments['heidi'] + experiment.linked_crystal.id = 'tbti' + experiment.linked_crystal.scale = 3 + experiment.instrument.setup_wavelength = 0.793 + experiment.extinction.mosaicity = 29820 + experiment.extinction.radius = 27 + + # Select fitting parameters (experiment only) + # Sample model parameters are selected in the loaded CIF file + experiment.linked_crystal.scale.free = True + experiment.extinction.radius.free = True + + # Perform fit + project.analysis.fit() + + # Compare fit quality + chi2 = project.analysis.fit_results.reduced_chi_square + assert chi2 == pytest.approx(expected=12.9, abs=0.1) + + +@pytest.mark.fast +def test_single_fit_neut_sc_tof_taurine() -> None: + project = ed.Project() + + # Set sample model + model_path = ed.download_data(id=21, destination=TEMP_DIR) + project.sample_models.add(cif_path=model_path) + + # Set experiment + data_path = ed.download_data(id=22, destination=TEMP_DIR) + project.experiments.add( + name='senju', + data_path=data_path, + sample_form='single crystal', + beam_mode='time-of-flight', + radiation_probe='neutron', + scattering_type='bragg', + ) + experiment = project.experiments['senju'] + experiment.linked_crystal.id = 'taurine' + experiment.linked_crystal.scale = 1.4 + experiment.extinction.mosaicity = 1000.0 + experiment.extinction.radius = 2.0 + + # Select fitting parameters (experiment only) + # Sample model parameters are selected in the loaded CIF file + experiment.linked_crystal.scale.free = True + experiment.extinction.radius.free = True + + # Perform fit + project.analysis.fit() + + # Compare fit quality + chi2 = project.analysis.fit_results.reduced_chi_square + assert chi2 == pytest.approx(expected=23.6, abs=0.1) + + +if __name__ == '__main__': + test_single_fit_neut_sc_cwl_tbti() + test_single_fit_neut_sc_tof_taurine() diff --git a/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py b/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py index 2af9f167..540c61c7 100644 --- a/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py +++ b/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py @@ -31,9 +31,9 @@ def test_get_reliability_inputs_collects_arrays_with_default_su(): # 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]) + self.intensity_meas = np.array([1.0, 2.0]) + self.intensity_meas_su = None # triggers default ones + self.intensity_calc = np.array([1.1, 1.9]) class Expt: def __init__(self): diff --git a/tests/unit/easydiffraction/core/test_parameters.py b/tests/unit/easydiffraction/core/test_parameters.py index d16603b8..3183fb81 100644 --- a/tests/unit/easydiffraction/core/test_parameters.py +++ b/tests/unit/easydiffraction/core/test_parameters.py @@ -63,7 +63,7 @@ def test_parameter_string_repr_and_as_cif_and_flags(): assert '± 0.1' in s and 'A' in s and '(free=True)' in s # CIF line is ` ` - assert p.as_cif == '_param.a 2.5000' + assert p.as_cif == '_param.a 2.50000000' # CifHandler uid is owner's unique_name (parameter name here) assert p._cif_handler.uid == p.unique_name == 'a' diff --git a/tests/unit/easydiffraction/display/plotters/test_ascii.py b/tests/unit/easydiffraction/display/plotters/test_ascii.py index 1ad07bd3..616bc60d 100644 --- a/tests/unit/easydiffraction/display/plotters/test_ascii.py +++ b/tests/unit/easydiffraction/display/plotters/test_ascii.py @@ -18,6 +18,33 @@ def test_ascii_plotter_plot_minimal(capsys): 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) + p.plot_powder(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 + + +def test_ascii_plotter_plot_single_crystal(capsys): + from easydiffraction.display.plotters.ascii import AsciiPlotter + + x_calc = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + y_meas = np.array([1.1, 1.9, 3.2, 3.8, 5.1]) + y_meas_su = np.array([0.1, 0.1, 0.1, 0.1, 0.1]) + + p = AsciiPlotter() + p.plot_single_crystal( + x_calc=x_calc, + y_meas=y_meas, + y_meas_su=y_meas_su, + axes_labels=['F²calc', 'F²meas'], + title='SC Test', + height=10, + ) + out = capsys.readouterr().out + # Verify title and axes labels appear + assert 'SC Test' in out + assert 'F²calc' in out + assert 'F²meas' in out + # Verify scatter points are plotted (● character) + assert '●' in out + # Verify diagonal reference line (· character) + assert '·' in out diff --git a/tests/unit/easydiffraction/display/plotters/test_base.py b/tests/unit/easydiffraction/display/plotters/test_base.py index ecc241c6..1b81f7c8 100644 --- a/tests/unit/easydiffraction/display/plotters/test_base.py +++ b/tests/unit/easydiffraction/display/plotters/test_base.py @@ -32,8 +32,14 @@ def test_default_engine_switches_with_notebook(monkeypatch): def test_default_axes_labels_keys_present(): import easydiffraction.display.plotters.base as pb - from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import SampleFormEnum 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 + # Powder Bragg + assert (SampleFormEnum.POWDER, ScatteringTypeEnum.BRAGG, pb.XAxisType.TWO_THETA) in pb.DEFAULT_AXES_LABELS + assert (SampleFormEnum.POWDER, ScatteringTypeEnum.BRAGG, pb.XAxisType.TIME_OF_FLIGHT) in pb.DEFAULT_AXES_LABELS + assert (SampleFormEnum.POWDER, ScatteringTypeEnum.BRAGG, pb.XAxisType.D_SPACING) in pb.DEFAULT_AXES_LABELS + # Single crystal Bragg + assert (SampleFormEnum.SINGLE_CRYSTAL, ScatteringTypeEnum.BRAGG, pb.XAxisType.INTENSITY_CALC) in pb.DEFAULT_AXES_LABELS + assert (SampleFormEnum.SINGLE_CRYSTAL, ScatteringTypeEnum.BRAGG, pb.XAxisType.D_SPACING) in pb.DEFAULT_AXES_LABELS + assert (SampleFormEnum.SINGLE_CRYSTAL, ScatteringTypeEnum.BRAGG, pb.XAxisType.SIN_THETA_OVER_LAMBDA) in pb.DEFAULT_AXES_LABELS diff --git a/tests/unit/easydiffraction/display/plotters/test_plotly.py b/tests/unit/easydiffraction/display/plotters/test_plotly.py index f0ea6a61..06539f2b 100644 --- a/tests/unit/easydiffraction/display/plotters/test_plotly.py +++ b/tests/unit/easydiffraction/display/plotters/test_plotly.py @@ -66,15 +66,15 @@ def __init__(self, html): plotter = pp.PlotlyPlotter() - # Exercise _get_trace + # Exercise _get_powder_trace x = [0, 1, 2] y = [1, 2, 3] - trace = plotter._get_trace(x, y, label='calc') + trace = plotter._get_powder_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( + # Exercise plot_powder (non-PyCharm, display path) + plotter.plot_powder( x, y_series=[y], labels=['calc'], @@ -85,3 +85,89 @@ def __init__(self, html): # One HTML display call expected assert dummy_display_calls['count'] == 1 or shown['count'] == 1 + + +def test_plotly_single_crystal_trace_and_plot(monkeypatch): + import easydiffraction.display.plotters.plotly as pp + + # Arrange: force non-PyCharm branch + monkeypatch.setattr(pp, 'in_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 + + 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_single_crystal_trace + x_calc = [1.0, 2.0, 3.0] + y_meas = [1.1, 1.9, 3.2] + y_meas_su = [0.1, 0.1, 0.1] + trace = plotter._get_single_crystal_trace(x_calc, y_meas, y_meas_su) + assert hasattr(trace, 'kwargs') + assert trace.kwargs['x'] == x_calc + assert trace.kwargs['y'] == y_meas + assert trace.kwargs['mode'] == 'markers' + assert 'error_y' in trace.kwargs + + # Exercise _get_diagonal_shape + shape = plotter._get_diagonal_shape() + assert shape['type'] == 'line' + assert shape['xref'] == 'paper' + assert shape['yref'] == 'paper' + + # Exercise plot_single_crystal + plotter.plot_single_crystal( + x_calc=x_calc, + y_meas=y_meas, + y_meas_su=y_meas_su, + axes_labels=['F²calc', 'F²meas'], + title='SC Test', + height=None, + ) + # One display call expected + assert dummy_display_calls['count'] == 1 or shown['count'] == 1 diff --git a/tests/unit/easydiffraction/display/test_plotting.py b/tests/unit/easydiffraction/display/test_plotting.py index 8210f481..a27c7e47 100644 --- a/tests/unit/easydiffraction/display/test_plotting.py +++ b/tests/unit/easydiffraction/display/test_plotting.py @@ -54,51 +54,51 @@ def test_plotter_factory_supported_and_unsupported(): def test_plotter_error_paths_and_filtering(capsys): from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import SampleFormEnum from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum from easydiffraction.display.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 + def __init__(self, two_theta=None, intensity_meas=None, intensity_calc=None, d_spacing=None): + self.two_theta = two_theta + self.intensity_meas = intensity_meas + self.intensity_calc = intensity_calc + self.d_spacing = d_spacing if d_spacing is not None else two_theta class ExptType: def __init__(self): + self.sample_form = type('SF', (), {'value': SampleFormEnum.POWDER}) self.scattering_type = type('S', (), {'value': ScatteringTypeEnum.BRAGG}) self.beam_mode = type('B', (), {'value': BeamModeEnum.CONSTANT_WAVELENGTH}) p = Plotter() # Error paths (now log errors via console; messages are printed) - p.plot_meas(Ptn(x=None, meas=None), 'E', ExptType()) + p.plot_meas(Ptn(two_theta=None, intensity_meas=None), 'E', ExptType()) out = capsys.readouterr().out - assert 'No data available for experiment E' in out + assert 'No two_theta data available for experiment E' in out - p.plot_meas(Ptn(x=[1], meas=None), 'E', ExptType()) + p.plot_meas(Ptn(two_theta=[1], intensity_meas=None), 'E', ExptType()) out = capsys.readouterr().out assert 'No measured data available for experiment E' in out - p.plot_calc(Ptn(x=None, calc=None), 'E', ExptType()) + p.plot_calc(Ptn(two_theta=None, intensity_calc=None), 'E', ExptType()) out = capsys.readouterr().out - assert 'No data available for experiment E' in out + assert 'No two_theta data available for experiment E' in out - p.plot_calc(Ptn(x=[1], calc=None), 'E', ExptType()) + p.plot_calc(Ptn(two_theta=[1], intensity_calc=None), 'E', ExptType()) out = capsys.readouterr().out assert 'No calculated data available for experiment E' in out - p.plot_meas_vs_calc(Ptn(x=None), 'E', ExptType()) + p.plot_meas_vs_calc(Ptn(two_theta=None, intensity_meas=None, intensity_calc=None), 'E', ExptType()) out = capsys.readouterr().out - assert 'No data available for experiment E' in out - p.plot_meas_vs_calc(Ptn(x=[1], meas=None, calc=[1]), 'E', ExptType()) + assert 'No measured data available for experiment E' in out + p.plot_meas_vs_calc(Ptn(two_theta=[1], intensity_meas=None, intensity_calc=[1]), 'E', ExptType()) out = capsys.readouterr().out assert 'No measured data available for experiment E' in out - p.plot_meas_vs_calc(Ptn(x=[1], meas=[1], calc=None), 'E', ExptType()) + p.plot_meas_vs_calc(Ptn(two_theta=[1], intensity_meas=[1], intensity_calc=None), 'E', ExptType()) out = capsys.readouterr().out assert 'No calculated data available for experiment E' in out - # TODO: Update assertions with new logging-based error handling - # in the above line and elsewhere as needed. # Filtering import numpy as np @@ -114,26 +114,28 @@ def test_plotter_routes_to_ascii_plotter(monkeypatch): import easydiffraction.display.plotters.ascii as ascii_mod from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import SampleFormEnum from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum from easydiffraction.display.plotting import Plotter called = {} - def fake_plot(self, x, y_series, labels, axes_labels, title, height=None): + def fake_plot_powder(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) + monkeypatch.setattr(ascii_mod.AsciiPlotter, 'plot_powder', fake_plot_powder) 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 + self.two_theta = np.array([0.0, 1.0]) + self.intensity_meas = np.array([1.0, 2.0]) + self.d_spacing = self.two_theta class ExptType: def __init__(self): + self.sample_form = type('SF', (), {'value': SampleFormEnum.POWDER}) self.scattering_type = type('S', (), {'value': ScatteringTypeEnum.BRAGG}) self.beam_mode = type('B', (), {'value': BeamModeEnum.CONSTANT_WAVELENGTH}) diff --git a/tests/unit/easydiffraction/experiments/categories/background/test_chebyshev.py b/tests/unit/easydiffraction/experiments/categories/background/test_chebyshev.py index d2de6aa7..07a61a94 100644 --- a/tests/unit/easydiffraction/experiments/categories/background/test_chebyshev.py +++ b/tests/unit/easydiffraction/experiments/categories/background/test_chebyshev.py @@ -14,7 +14,7 @@ def test_chebyshev_background_calculate_and_cif(): # Create mock parent with data x = np.linspace(0.0, 1.0, 5) mock_data = SimpleNamespace(x=x, _bkg=None) - mock_data._set_bkg = lambda y: setattr(mock_data, '_bkg', y) + mock_data._set_intensity_bkg = lambda y: setattr(mock_data, '_bkg', y) mock_parent = SimpleNamespace(data=mock_data) cb = ChebyshevPolynomialBackground() diff --git a/tests/unit/easydiffraction/experiments/categories/background/test_line_segment.py b/tests/unit/easydiffraction/experiments/categories/background/test_line_segment.py index 483873db..23067f3c 100644 --- a/tests/unit/easydiffraction/experiments/categories/background/test_line_segment.py +++ b/tests/unit/easydiffraction/experiments/categories/background/test_line_segment.py @@ -14,7 +14,7 @@ def test_line_segment_background_calculate_and_cif(): # Create mock parent with data x = np.array([0.0, 1.0, 2.0]) mock_data = SimpleNamespace(x=x, _bkg=None) - mock_data._set_bkg = lambda y: setattr(mock_data, '_bkg', y) + mock_data._set_intensity_bkg = lambda y: setattr(mock_data, '_bkg', y) mock_parent = SimpleNamespace(data=mock_data) bkg = LineSegmentBackground() diff --git a/tests/unit/easydiffraction/experiments/categories/instrument/test_cwl.py b/tests/unit/easydiffraction/experiments/categories/instrument/test_cwl.py index dcefa62e..2dc7bd6c 100644 --- a/tests/unit/easydiffraction/experiments/categories/instrument/test_cwl.py +++ b/tests/unit/easydiffraction/experiments/categories/instrument/test_cwl.py @@ -1,11 +1,11 @@ # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause -from easydiffraction.experiments.categories.instrument.cwl import CwlInstrument +from easydiffraction.experiments.categories.instrument.cwl import CwlPdInstrument def test_cwl_instrument_parameters_settable(): - instr = CwlInstrument() + instr = CwlPdInstrument() instr.setup_wavelength = 2.0 instr.calib_twotheta_offset = 0.1 assert instr.setup_wavelength.value == 2.0 diff --git a/tests/unit/easydiffraction/experiments/categories/instrument/test_factory.py b/tests/unit/easydiffraction/experiments/categories/instrument/test_factory.py index 79e97077..0c6066b0 100644 --- a/tests/unit/easydiffraction/experiments/categories/instrument/test_factory.py +++ b/tests/unit/easydiffraction/experiments/categories/instrument/test_factory.py @@ -14,13 +14,13 @@ def test_instrument_factory_default_and_errors(): return inst = InstrumentFactory.create() # defaults - assert inst.__class__.__name__ in {'CwlInstrument', 'TofInstrument'} + assert inst.__class__.__name__ in {'CwlPdInstrument', 'CwlScInstrument', 'TofPdInstrument', 'TofScInstrument'} # Valid combinations inst2 = InstrumentFactory.create(ScatteringTypeEnum.BRAGG, BeamModeEnum.CONSTANT_WAVELENGTH) - assert inst2.__class__.__name__ == 'CwlInstrument' + assert inst2.__class__.__name__ == 'CwlPdInstrument' inst3 = InstrumentFactory.create(ScatteringTypeEnum.BRAGG, BeamModeEnum.TIME_OF_FLIGHT) - assert inst3.__class__.__name__ == 'TofInstrument' + assert inst3.__class__.__name__ == 'TofPdInstrument' # Invalid scattering type class FakeST: diff --git a/tests/unit/easydiffraction/experiments/categories/instrument/test_tof.py b/tests/unit/easydiffraction/experiments/categories/instrument/test_tof.py index 2d4e48b1..339bcded 100644 --- a/tests/unit/easydiffraction/experiments/categories/instrument/test_tof.py +++ b/tests/unit/easydiffraction/experiments/categories/instrument/test_tof.py @@ -5,9 +5,9 @@ def test_tof_instrument_defaults_and_setters_and_parameters_and_cif(): - from easydiffraction.experiments.categories.instrument.tof import TofInstrument + from easydiffraction.experiments.categories.instrument.tof import TofPdInstrument - inst = TofInstrument() + inst = TofPdInstrument() # Defaults assert np.isclose(inst.setup_twotheta_bank.value, 150.0) diff --git a/tests/unit/easydiffraction/experiments/categories/test_excluded_regions.py b/tests/unit/easydiffraction/experiments/categories/test_excluded_regions.py index 7aa3e833..bf363e7f 100644 --- a/tests/unit/easydiffraction/experiments/categories/test_excluded_regions.py +++ b/tests/unit/easydiffraction/experiments/categories/test_excluded_regions.py @@ -14,7 +14,7 @@ def test_excluded_regions_add_updates_datastore_and_cif(): 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( - all_x=full_x, # ExcludedRegions._update uses all_x not full_x + unfiltered_x=full_x, full_x=full_x, full_meas=full_meas, full_meas_su=full_meas_su, diff --git a/tests/unit/easydiffraction/experiments/experiment/test_bragg_pd.py b/tests/unit/easydiffraction/experiments/experiment/test_bragg_pd.py index f070d4fd..a0dfa3f2 100644 --- a/tests/unit/easydiffraction/experiments/experiment/test_bragg_pd.py +++ b/tests/unit/easydiffraction/experiments/experiment/test_bragg_pd.py @@ -53,7 +53,7 @@ def test_load_ascii_data_rounds_and_defaults_sy(tmp_path: pytest.TempPathFactory # 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.data.meas_su, expected_sy) + assert np.allclose(expt.data.intensity_meas_su, expected_sy) # Check that data array shapes match assert len(expt.data.x) == len(x) @@ -64,7 +64,7 @@ def test_load_ascii_data_rounds_and_defaults_sy(tmp_path: pytest.TempPathFactory 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.data.meas_su, expected_sy3) + assert np.allclose(expt.data.intensity_meas_su, expected_sy3) # Case 3: invalid shape -> currently triggers an exception (IndexError on shape[1]) pinv = tmp_path / 'invalid.dat' diff --git a/tests/unit/easydiffraction/experiments/experiment/test_bragg_sc.py b/tests/unit/easydiffraction/experiments/experiment/test_bragg_sc.py index bfbb3e0e..af9a65f7 100644 --- a/tests/unit/easydiffraction/experiments/experiment/test_bragg_sc.py +++ b/tests/unit/easydiffraction/experiments/experiment/test_bragg_sc.py @@ -4,7 +4,7 @@ import pytest from easydiffraction.experiments.categories.experiment_type import ExperimentType -from easydiffraction.experiments.experiment.bragg_sc import BraggScExperiment +from easydiffraction.experiments.experiment.bragg_sc import CwlScExperiment from easydiffraction.experiments.experiment.enums import BeamModeEnum from easydiffraction.experiments.experiment.enums import RadiationProbeEnum from easydiffraction.experiments.experiment.enums import SampleFormEnum @@ -21,7 +21,7 @@ def _mk_type_sc_bragg(): ) -class _ConcreteBraggSc(BraggScExperiment): +class _ConcreteCwlSc(CwlScExperiment): def _load_ascii_data_to_experiment(self, data_path: str) -> None: # Not used in this test pass @@ -30,6 +30,7 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: 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() + expt = _ConcreteCwlSc(name='sc1', type=_mk_type_sc_bragg()) + # Verify that experiment was created successfully with expected properties + assert expt.name == 'sc1' + assert expt.type is not None diff --git a/tests/unit/easydiffraction/experiments/experiment/test_instrument_mixin.py b/tests/unit/easydiffraction/experiments/experiment/test_instrument_mixin.py deleted file mode 100644 index 89975b7d..00000000 --- a/tests/unit/easydiffraction/experiments/experiment/test_instrument_mixin.py +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2026 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 index 5a4fe416..8f10c692 100644 --- a/tests/unit/easydiffraction/experiments/experiment/test_total_pd.py +++ b/tests/unit/easydiffraction/experiments/experiment/test_total_pd.py @@ -47,5 +47,5 @@ def test_load_ascii_data_pdf(tmp_path: pytest.TempPathFactory): # With diffpy available, load should succeed expt._load_ascii_data_to_experiment(str(f)) assert np.allclose(expt.data.x, data[:, 0]) - assert np.allclose(expt.data.meas, data[:, 1]) - assert np.allclose(expt.data.meas_su, data[:, 2]) + assert np.allclose(expt.data.intensity_meas, data[:, 1]) + assert np.allclose(expt.data.intensity_meas_su, data[:, 2]) diff --git a/tests/unit/easydiffraction/io/cif/test_serialize.py b/tests/unit/easydiffraction/io/cif/test_serialize.py index 2cf82f31..d85126e8 100644 --- a/tests/unit/easydiffraction/io/cif/test_serialize.py +++ b/tests/unit/easydiffraction/io/cif/test_serialize.py @@ -12,8 +12,8 @@ def test_module_import(): 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' + assert MUT.format_value('a b') == ' "a b"' + assert MUT.format_value('ab') == ' ab' def test_param_to_cif_minimal(): @@ -26,7 +26,7 @@ def __init__(self): self.value = 3 p = P() - assert MUT.param_to_cif(p) == '_x.y 3.0000' + assert MUT.param_to_cif(p) == '_x.y 3.00000000' def test_category_collection_to_cif_empty_and_one_row(): diff --git a/tests/unit/easydiffraction/io/cif/test_serialize_more.py b/tests/unit/easydiffraction/io/cif/test_serialize_more.py index 3c197a75..c36a01ab 100644 --- a/tests/unit/easydiffraction/io/cif/test_serialize_more.py +++ b/tests/unit/easydiffraction/io/cif/test_serialize_more.py @@ -112,7 +112,7 @@ def as_cif(self): assert '_k' in out_with and '1' in out_with out_without = MUT.experiment_to_cif(Exp('')) - assert out_without.startswith('data_expA') and out_without.endswith('1.0000') + assert out_without.startswith('data_expA') and out_without.endswith('1.00000000') def test_analysis_to_cif_renders_all_sections(): diff --git a/tutorials/ed-13.py b/tutorials/ed-13.py index e2bf1110..1411ba42 100644 --- a/tutorials/ed-13.py +++ b/tutorials/ed-13.py @@ -670,7 +670,7 @@ # setting the `d_spacing` parameter to `True`. # %% -project_1.plot_meas_vs_calc(expt_name='sim_si', d_spacing=True) +project_1.plot_meas_vs_calc(expt_name='sim_si', x='d_spacing') # %% [markdown] # As you can see, the calculated diffraction pattern now matches the @@ -1208,7 +1208,7 @@ # **Solution:** # %% tags=["solution", "hide-input"] -project_2.plot_meas_vs_calc(expt_name='sim_lbco', d_spacing=True) +project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing') # %% [markdown] # #### Exercise 5.6: Refine the Peak Profile Parameters @@ -1225,7 +1225,7 @@ # perfectly describe the peak at about 1.38 Å, as can be seen below: # %% -project_2.plot_meas_vs_calc(expt_name='sim_lbco', d_spacing=True, x_min=1.35, x_max=1.40) +project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing', x_min=1.35, x_max=1.40) # %% [markdown] # The peak profile parameters are determined based on both the @@ -1260,7 +1260,7 @@ project_2.analysis.fit() project_2.analysis.show_fit_results() -project_2.plot_meas_vs_calc(expt_name='sim_lbco', d_spacing=True, x_min=1.35, x_max=1.40) +project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing', x_min=1.35, x_max=1.40) # %% [markdown] # #### Exercise 5.7: Find Undefined Features @@ -1283,7 +1283,7 @@ # **Solution:** # %% tags=["solution", "hide-input"] -project_2.plot_meas_vs_calc(expt_name='sim_lbco', x_min=1.53, x_max=1.7, d_spacing=True) +project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing', x_min=1.53, x_max=1.7) # %% [markdown] # #### Exercise 5.8: Identify the Cause of the Unexplained Peaks @@ -1348,8 +1348,8 @@ # confirm this hypothesis. # %% tags=["solution", "hide-input"] -project_1.plot_meas_vs_calc(expt_name='sim_si', x_min=1, x_max=1.7, d_spacing=True) -project_2.plot_meas_vs_calc(expt_name='sim_lbco', x_min=1, x_max=1.7, d_spacing=True) +project_1.plot_meas_vs_calc(expt_name='sim_si', x='d_spacing', x_min=1, x_max=1.7) +project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing', x_min=1, x_max=1.7) # %% [markdown] # #### Exercise 5.10: Create a Second Sample Model – Si as Impurity diff --git a/tutorials/ed-14.py b/tutorials/ed-14.py new file mode 100644 index 00000000..65d8e98c --- /dev/null +++ b/tutorials/ed-14.py @@ -0,0 +1,109 @@ +# %% [markdown] +# # Structure Refinement: Tb2TiO7, HEiDi +# +# Crystal structure refinement of Tb2TiO7 using single crystal neutron +# diffraction data from HEiDi at FRM II. + +# %% [markdown] +# ## Import Library + +# %% +import easydiffraction as ed + +# %% [markdown] +# ## Step 1: Define Project + +# %% +# Create minimal project without name and description +project = ed.Project() + +# %% [markdown] +# ## Step 2: Define Sample Model + +# %% +# Download CIF file from repository +model_path = ed.download_data(id=20, destination='data') + +# %% +project.sample_models.add(cif_path=model_path) + +# %% +project.sample_models.show_names() + +# %% +sample_model = project.sample_models['tbti'] + +# %% +sample_model.atom_sites['Tb'].b_iso.value = 0.0 +sample_model.atom_sites['Ti'].b_iso.value = 0.0 +sample_model.atom_sites['O1'].b_iso.value = 0.0 +sample_model.atom_sites['O2'].b_iso.value = 0.0 + +# %% +sample_model.show_as_cif() + +# %% [markdown] +# ## Step 3: Define Experiment + +# %% +data_path = ed.download_data(id=19, destination='data') + +# %% +project.experiments.add( + name='heidi', + data_path=data_path, + sample_form='single crystal', + beam_mode='constant wavelength', + radiation_probe='neutron', +) + +# %% +experiment = project.experiments['heidi'] # TODO: + +# %% +experiment.linked_crystal.id = 'tbti' +experiment.linked_crystal.scale = 1.0 + +# %% +experiment.instrument.setup_wavelength = 0.793 + +# %% +experiment.extinction.mosaicity = 29820 +experiment.extinction.radius = 30 + +# %% [markdown] +# ## Step 4: Perform Analysis + +# %% +project.plot_meas_vs_calc(expt_name='heidi') + +# %% +experiment.linked_crystal.scale.free = True +experiment.extinction.radius.free = True + +# %% +experiment.show_as_cif() + +# %% +# Start refinement. All parameters, which have standard uncertainties +# in the input CIF files, are refined by default. +project.analysis.fit() + +# %% +# Show fit results summary +project.analysis.show_fit_results() + +# %% +experiment.show_as_cif() + +# %% +project.experiments.show_names() + +# %% +project.plot_meas_vs_calc(expt_name='heidi') + +# %% [markdown] +# ## Step 5: Show Project Summary + +# %% +project.summary.show_report() diff --git a/tutorials/ed-15.py b/tutorials/ed-15.py new file mode 100644 index 00000000..c3e178ff --- /dev/null +++ b/tutorials/ed-15.py @@ -0,0 +1,100 @@ +# %% [markdown] +# # Structure Refinement: Taurine, SENJU +# +# Crystal structure refinement of Taurine using time-of-flight single +# crystal neutron diffraction data from SENJU at J-PARC. + +# %% [markdown] +# ## Import Library + +# %% +import easydiffraction as ed + +# %% [markdown] +# ## Step 1: Define Project + +# %% +# Create minimal project without name and description +project = ed.Project() + +# %% [markdown] +# ## Step 2: Define Sample Model + +# %% +# Download CIF file from repository +model_path = ed.download_data(id=21, destination='data') + +# %% +project.sample_models.add(cif_path=model_path) + +# %% +project.sample_models.show_names() + +# %% +sample_model = project.sample_models['taurine'] + +# %% +# sample_model.show_as_cif() + +# %% [markdown] +# ## Step 3: Define Experiment + +# %% +data_path = ed.download_data(id=22, destination='data') + +# %% +project.experiments.add( + name='senju', + data_path=data_path, + sample_form='single crystal', + beam_mode='time-of-flight', + radiation_probe='neutron', +) + +# %% +experiment = project.experiments['senju'] + +# %% +experiment.linked_crystal.id = 'taurine' +experiment.linked_crystal.scale = 1.0 + +# %% +experiment.extinction.mosaicity = 1000.0 +experiment.extinction.radius = 100.0 + +# %% [markdown] +# ## Step 4: Perform Analysis + +# %% +project.plot_meas_vs_calc(expt_name='senju') + +# %% +experiment.linked_crystal.scale.free = True +experiment.extinction.radius.free = True + +# %% +# experiment.show_as_cif() + +# %% +# Start refinement. All parameters, which have standard uncertainties +# in the input CIF files, are refined by default. +project.analysis.fit() + +# %% +# Show fit results summary +project.analysis.show_fit_results() + +# %% +# experiment.show_as_cif() + +# %% +project.experiments.show_names() + +# %% +project.plot_meas_vs_calc(expt_name='senju') + +# %% [markdown] +# ## Step 5: Show Project Summary + +# %% +project.summary.show_report() diff --git a/tutorials/index.json b/tutorials/index.json index 983499fd..14808266 100644 --- a/tutorials/index.json +++ b/tutorials/index.json @@ -89,5 +89,12 @@ "title": "DMSC Summer School 2025: Powder Diffraction Analysis", "description": "Comprehensive workshop tutorial covering Rietveld refinement of Si and La0.5Ba0.5CoO3 using simulated powder diffraction data", "level": "workshop" + }, + "14": { + "url": "https://easyscience.github.io/diffraction-lib/{version}/tutorials/ed-14/ed-14.ipynb", + "original_name": "", + "title": "Crystal Structure: Tb2TiO7, HEiDi", + "description": "Crystal structure refinement of Tb2TiO7 using single crystal neutron diffraction data from HEiDi at FRM II", + "level": "intermediate" } } From 25e84b754bc871e5f15c0d94755d6435efa6511b Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Wed, 25 Mar 2026 17:37:52 +0100 Subject: [PATCH 2/5] Restructure datablocks, unify factory system, and standardize switchable-category API (#128) * Refactor validation imports and remove unused DataTypes references * Refactor value specification to use 'data_type' instead of 'type_' in AttributeSpec * Refactor content_validator to validator in AttributeSpec * Refactor Cell class constructor to use default parameters and simplify attribute setting * Refactor AtomSite and SpaceGroup initializers to remove parameters and improve clarity * Refactor constructors in multiple classes to remove parameters and use property setters * Refactor add method to accept only keyword arguments for improved clarity * Refactor initialization of descriptors in multiple classes for consistency * Remove value initialization from validation class constructor * Refactor parameter value specification to remove DataTypes dependency * Refactor parameter initialization and property methods in mixin files * Refactor: streamline CIF handler initialization and add public property comments * Refactor ExperimentType initialization to use property setters * Refactor ExperimentType initialization to use property setters * Add temporary test script * Initialize parent class in guard.py constructor * Refactor peak-profile mixins to remove unused broadening and asymmetry methods * Refactor broadening parameter comments and remove unused initializations in TofPeak * Refactor parameter definitions to streamline unit assignments in various modules * Refactor CIF handler initialization for clarity and consistency * Refactor constructors in PdDataPoint mixins to remove kwargs * Refactor parameters module to variable module; update imports across multiple files * Refactor import statements for consistency and clarity * Refactor sample models to structures in project and related files * Refactor terminology from "sample" to "structure" in documentation and code comments * More refactoring from sample models to structures * Refactor factory methods, etc. * Refactor method names from `add_from_scratch` to `create` for consistency across collections * Rebuild classes structure * Refactor validation module by removing unused type checking decorator * Refactor PeakProfileTypeEnum to consider auto-extraction of peak profile info * Add revised design for all factories * Improve revised design for all factories * Add copilot instructions for EasyDiffraction project * Update all init files * Refactor metadata handling * Refactor type hints for optional parameters in collection.py * Add per-file ignores for __init__.py in Ruff configuration * Add TOF instrument and peak profile classes with compatibility and calculator support * Clarify usage of keyword arguments in copilot instructions * Add metadata dataclasses for factory-created classes * Refactor setter methods for improved readability and consistency * Refactor formatting of beam_mode and calculators in Chebyshev class * Update copilot instructions to clarify beta project guidelines * Update copilot instructions to clarify refactoring guidelines * Add architecture documentation for EasyDiffraction library * Refactor calculator management for joint and sequential fitting in experiments * Refactor code blocks in architecture.md to specify shell syntax highlighting * Document current and potential architectural issues in the codebase * Document current and potential issues in architecture * Document limitations of `FactoryBase` regarding constructor-variant registrations * Refactor calculator and minimizer factory methods for consistency and clarity * Refactor architecture documentation for improved clarity and consistency * Refactor logging and docstring formatting in analysis and factory modules * Refactor calculator and minimizer factory to use tag strings; update tests accordingly * Refactor core factory structure to eliminate duplication and unify metadata handling * Add initial experiment setup and analysis workflow for hrpt project * Update easydiffraction version and SHA-256 hash in pixi.lock * Refactor architecture documentation to clarify factory structures and display methods * Remove unused exports from __init__.py to streamline module interface * Refactor string quotes in test.py for consistency and readability * unify CollectionBase key resolution, add __contains__ and remove() * Make ExperimentType immutable after creation * Make peak and background read-only public properties * Document editable vs read-only property convention with _set_ private methods * Standardize switchable-category naming convention * Apply formatting * Refactor Analysis class to instantiate calculator in __init__ and remove class-level attribute * Remove duplicated symmetry methods from Structure * Route constraint updates through validated setter * Override _key_for in CategoryCollection and DatablockCollection * Warn when switching background or peak profile type * Document why minimisers bypass the value setter * Replace 'lmfit (leastsq)' with 'lmfit' in tests, tutorials, and docs * Consolidate revised-design-v5.md into architecture.md * Apply formatting * Update copilot instructions for linting and testing workflow * Enable dirty-flag guard via _set_value_from_minimizer * Add factory.py and metadata.py to package structure documentation * Consolidate all issues into issues.md and update copilot instructions * Move calculator from global Analysis to per-experiment * Add Bragg+PDF joint tutorial, multi-experiment docs section, and test * Temporarily disable Bragg+PDF joint fit until test is improved * Add universal factories for Extinction and LinkedCrystal * Add universal factories for all remaining categories * Update tutorial #14 * Add switchable-category API to all factory-created categories * Add switchable-category API for instrument and data on experiments * Filter show_supported_instrument_types by experiment context * Add target-audience and reliability instructions to copilot config * Convert fit_mode to CategoryItem * Convert fit_mode to factory-based category with enum comparison * Add flat category structure rule to architecture.md * Consolidate architecture docs and fix stale references * Restructure help() to show Parameters, Properties, and Methods tables * Auto-populate Analysis.help() from class introspection * Refine eager-imports rule to document lazy-import exceptions * Update tutorial structure in mkdocs.yml for clarity and consistency * Update issues list * Add instruction to run tutorial tests after changes * Refactor background type naming for consistency * Delete test.py * Fix tutorial failures and stale calculator cache with excluded regions * Update issues * Update tutorial names and copyright year --- .github/copilot-instructions.md | 136 + docs/api-reference/datablocks/experiment.md | 1 + docs/api-reference/datablocks/structure.md | 1 + docs/api-reference/experiments.md | 1 - docs/api-reference/index.md | 9 +- docs/api-reference/sample_models.md | 1 - docs/architecture/architecture.md | 1034 ++++++ docs/architecture/issues_closed.md | 60 + docs/architecture/issues_open.md | 339 ++ docs/architecture/package-structure-full.md | 302 +- docs/architecture/package-structure-short.md | 131 +- docs/mkdocs.yml | 19 +- docs/tutorials/index.md | 46 +- docs/user-guide/analysis-workflow/analysis.md | 86 +- .../analysis-workflow/experiment.md | 37 +- docs/user-guide/analysis-workflow/index.md | 6 +- docs/user-guide/analysis-workflow/model.md | 73 +- docs/user-guide/analysis-workflow/project.md | 16 +- docs/user-guide/concept.md | 6 +- docs/user-guide/data-format.md | 6 +- docs/user-guide/first-steps.md | 44 +- docs/user-guide/parameters.md | 25 +- pixi.lock | 4 +- pyproject.toml | 5 +- src/easydiffraction/__init__.py | 19 +- src/easydiffraction/analysis/analysis.py | 375 +- .../analysis/calculators/__init__.py | 4 + .../analysis/calculators/base.py | 18 +- .../analysis/calculators/crysfml.py | 77 +- .../analysis/calculators/cryspy.py | 109 +- .../analysis/calculators/factory.py | 113 +- .../analysis/calculators/pdffit.py | 29 +- .../analysis/categories/aliases/__init__.py | 5 + .../{aliases.py => aliases/default.py} | 68 +- .../analysis/categories/aliases/factory.py | 15 + .../categories/constraints/__init__.py | 5 + .../default.py} | 70 +- .../categories/constraints/factory.py | 15 + .../analysis/categories/fit_mode/__init__.py | 6 + .../analysis/categories/fit_mode/enums.py | 24 + .../analysis/categories/fit_mode/factory.py | 15 + .../analysis/categories/fit_mode/fit_mode.py | 61 + .../joint_fit_experiments/__init__.py | 5 + .../default.py} | 66 +- .../joint_fit_experiments/factory.py | 17 + .../analysis/fit_helpers/metrics.py | 12 +- src/easydiffraction/analysis/fitting.py | 44 +- .../analysis/minimizers/__init__.py | 3 + .../analysis/minimizers/base.py | 8 +- .../analysis/minimizers/dfols.py | 12 +- .../analysis/minimizers/factory.py | 127 +- .../analysis/minimizers/lmfit.py | 12 +- src/easydiffraction/core/category.py | 154 +- src/easydiffraction/core/collection.py | 51 +- src/easydiffraction/core/datablock.py | 83 +- src/easydiffraction/core/factory.py | 243 +- src/easydiffraction/core/guard.py | 79 + src/easydiffraction/core/metadata.py | 107 + .../core/{singletons.py => singleton.py} | 13 +- src/easydiffraction/core/validation.py | 102 +- .../core/{parameters.py => variable.py} | 100 +- .../{experiments => datablocks}/__init__.py | 0 .../experiment}/__init__.py | 0 .../experiment/categories}/__init__.py | 0 .../categories/background/__init__.py | 9 + .../experiment}/categories/background/base.py | 0 .../categories/background/chebyshev.py | 75 +- .../categories/background/enums.py | 2 +- .../categories/background/factory.py | 14 + .../categories/background/line_segment.py | 57 +- .../experiment/categories/data/__init__.py | 7 + .../experiment}/categories/data/bragg_pd.py | 152 +- .../experiment}/categories/data/bragg_sc.py | 149 +- .../experiment/categories/data/factory.py | 33 + .../experiment}/categories/data/total_pd.py | 77 +- .../categories/excluded_regions/__init__.py | 9 + .../categories/excluded_regions/default.py} | 67 +- .../categories/excluded_regions/factory.py | 15 + .../categories/experiment_type/__init__.py | 4 + .../categories/experiment_type/default.py} | 128 +- .../categories/experiment_type/factory.py | 15 + .../categories/extinction/__init__.py | 4 + .../categories/extinction/factory.py | 15 + .../categories/extinction/shelx.py} | 39 +- .../categories/instrument/__init__.py | 7 + .../experiment}/categories/instrument/base.py | 0 .../experiment}/categories/instrument/cwl.py | 49 +- .../categories/instrument/factory.py | 30 + .../experiment}/categories/instrument/tof.py | 102 +- .../categories/linked_crystal/__init__.py | 4 + .../categories/linked_crystal/default.py} | 49 +- .../categories/linked_crystal/factory.py | 15 + .../categories/linked_phases/__init__.py | 5 + .../categories/linked_phases/default.py} | 57 +- .../categories/linked_phases/factory.py | 15 + .../experiment/categories/peak/__init__.py | 10 + .../experiment}/categories/peak/base.py | 0 .../experiment/categories/peak/cwl.py | 85 + .../experiment/categories/peak/cwl_mixins.py | 249 ++ .../experiment/categories/peak/factory.py | 26 + .../experiment/categories/peak/tof.py | 84 + .../experiment/categories/peak/tof_mixins.py | 218 ++ .../experiment/categories/peak/total.py | 36 + .../categories/peak/total_mixins.py | 137 + .../datablocks/experiment/collection.py | 125 + .../datablocks/experiment/item/__init__.py | 9 + .../datablocks/experiment/item/base.py | 672 ++++ .../datablocks/experiment/item/bragg_pd.py | 203 ++ .../experiment/item}/bragg_sc.py | 32 +- .../experiment/item}/enums.py | 14 + .../datablocks/experiment/item/factory.py | 236 ++ .../experiment/item}/total_pd.py | 21 +- .../structure}/__init__.py | 0 .../structure/categories}/__init__.py | 0 .../categories/atom_sites/__init__.py | 5 + .../categories/atom_sites/default.py | 398 +++ .../categories/atom_sites/factory.py | 15 + .../structure/categories/cell}/__init__.py | 2 + .../structure/categories/cell/default.py | 255 ++ .../structure/categories/cell/factory.py | 15 + .../categories/space_group}/__init__.py | 2 + .../categories/space_group/default.py | 167 + .../categories/space_group/factory.py | 15 + .../datablocks/structure/collection.py | 82 + .../structure/item}/__init__.py | 0 .../datablocks/structure/item/base.py | 233 ++ .../datablocks/structure/item/factory.py | 116 + src/easydiffraction/display/base.py | 2 +- src/easydiffraction/display/plotters/base.py | 6 +- .../categories/background/factory.py | 66 - .../experiments/categories/data/factory.py | 83 - .../categories/instrument/factory.py | 95 - .../experiments/categories/peak/cwl.py | 45 - .../experiments/categories/peak/cwl_mixins.py | 332 -- .../experiments/categories/peak/factory.py | 130 - .../experiments/categories/peak/tof.py | 44 - .../experiments/categories/peak/tof_mixins.py | 293 -- .../experiments/categories/peak/total.py | 17 - .../categories/peak/total_mixins.py | 181 - .../experiments/experiment/__init__.py | 18 - .../experiments/experiment/base.py | 317 -- .../experiments/experiment/bragg_pd.py | 142 - .../experiments/experiment/factory.py | 213 -- .../experiments/experiments.py | 134 - src/easydiffraction/io/cif/serialize.py | 13 +- src/easydiffraction/project/project.py | 56 +- .../sample_models/categories/atom_sites.py | 334 -- .../sample_models/categories/cell.py | 188 - .../sample_models/categories/space_group.py | 107 - .../sample_models/sample_model/base.py | 183 - .../sample_models/sample_model/factory.py | 103 - .../sample_models/sample_models.py | 87 - src/easydiffraction/summary/summary.py | 5 +- src/easydiffraction/utils/__init__.py | 5 - tests/integration/fitting/test_multi.py | 236 ++ .../test_pair-distribution-function.py | 73 +- ..._powder-diffraction_constant-wavelength.py | 141 +- .../test_powder-diffraction_joint-fit.py | 79 +- .../test_powder-diffraction_multiphase.py | 143 - .../test_powder-diffraction_time-of-flight.py | 54 +- .../test_single-crystal-diffraction.py | 16 +- .../dream/test_analyze_reduced_data.py | 36 +- .../analysis/calculators/test_crysfml.py | 2 +- .../analysis/calculators/test_cryspy.py | 4 +- .../analysis/calculators/test_factory.py | 43 +- .../analysis/calculators/test_pdffit.py | 6 +- .../analysis/categories/test_aliases.py | 6 +- .../analysis/categories/test_constraints.py | 6 +- .../categories/test_joint_fit_experiments.py | 6 +- .../analysis/fit_helpers/test_metrics.py | 4 +- .../analysis/minimizers/test_base.py | 8 +- .../analysis/minimizers/test_dfols.py | 13 +- .../analysis/minimizers/test_factory.py | 25 +- .../analysis/minimizers/test_lmfit.py | 3 + .../easydiffraction/analysis/test_analysis.py | 113 +- .../analysis/test_analysis_access_params.py | 10 +- .../analysis/test_analysis_show_empty.py | 2 +- .../easydiffraction/analysis/test_fitting.py | 4 +- .../easydiffraction/core/test_category.py | 52 +- .../easydiffraction/core/test_collection.py | 91 + .../easydiffraction/core/test_datablock.py | 75 +- .../unit/easydiffraction/core/test_factory.py | 28 +- tests/unit/easydiffraction/core/test_guard.py | 50 + .../easydiffraction/core/test_parameters.py | 29 +- .../easydiffraction/core/test_singletons.py | 2 +- .../easydiffraction/core/test_validation.py | 10 +- .../categories/background/test_base.py | 26 +- .../categories/background/test_chebyshev.py | 6 +- .../categories/background/test_enums.py | 17 + .../categories/background/test_factory.py | 20 + .../background/test_line_segment.py | 6 +- .../categories/data/test_bragg_pd.py | 145 + .../categories/data/test_bragg_sc.py | 93 + .../categories/data/test_factory.py | 89 + .../categories/data/test_total_pd.py | 92 + .../categories/instrument/test_base.py | 2 +- .../categories/instrument/test_cwl.py | 2 +- .../categories/instrument/test_factory.py | 35 + .../categories/instrument/test_tof.py | 2 +- .../experiment}/categories/peak/test_base.py | 2 +- .../experiment}/categories/peak/test_cwl.py | 6 +- .../categories/peak/test_cwl_mixins.py | 8 +- .../categories/peak/test_factory.py | 54 + .../experiment}/categories/peak/test_tof.py | 6 +- .../categories/peak/test_tof_mixins.py | 10 +- .../experiment}/categories/peak/test_total.py | 2 +- .../categories/peak/test_total_mixins.py | 2 +- .../categories/test_excluded_regions.py | 4 +- .../categories/test_experiment_type.py | 36 + .../experiment/categories/test_extinction.py | 78 + .../categories/test_linked_crystal.py | 90 + .../categories/test_linked_phases.py | 10 +- .../datablocks/experiment/item/test_base.py | 37 + .../experiment/item}/test_bragg_pd.py | 36 +- .../experiment/item}/test_bragg_sc.py | 25 +- .../experiment/item}/test_enums.py | 6 +- .../experiment/item/test_factory.py | 28 + .../experiment/item}/test_total_pd.py | 24 +- .../experiment/test_collection.py} | 12 +- .../structure}/categories/test_space_group.py | 2 +- .../structure/item}/test_base.py | 8 +- .../datablocks/structure/item/test_factory.py | 9 + .../structure/test_collection.py} | 2 +- .../display/plotters/test_base.py | 4 +- .../easydiffraction/display/test_plotting.py | 12 +- .../categories/background/test_enums.py | 11 - .../categories/background/test_factory.py | 24 - .../categories/instrument/test_factory.py | 37 - .../categories/peak/test_factory.py | 58 - .../categories/test_experiment_type.py | 36 - .../experiments/experiment/test_base.py | 38 - .../experiments/experiment/test_factory.py | 35 - .../easydiffraction/io/cif/test_serialize.py | 2 +- .../io/cif/test_serialize_more.py | 14 +- .../easydiffraction/project/test_project.py | 13 + .../test_project_load_and_summary_wrap.py | 9 +- .../project/test_project_save.py | 2 +- .../categories/test_atom_sites.py | 28 - .../sample_models/categories/test_cell.py | 43 - .../sample_model/test_factory.py | 16 - .../easydiffraction/summary/test_summary.py | 3 +- .../summary/test_summary_details.py | 5 +- tests/unit/easydiffraction/test___init__.py | 2 +- tmp/__validator.py | 87 + tmp/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py | 26 +- ...truct_pd-neut-tof_multiphase-BSFTO-HRPT.py | 6 - tmp/short7.py | 1 - tutorials/data/ed-3.xye | 3099 +++++++++++++++++ tutorials/ed-1.py | 12 +- tutorials/ed-10.py | 21 +- tutorials/ed-11.py | 23 +- tutorials/ed-12.py | 25 +- tutorials/ed-13.py | 190 +- tutorials/ed-14.py | 22 +- tutorials/ed-15.py | 14 +- tutorials/ed-16.py | 259 ++ tutorials/ed-2.py | 54 +- tutorials/ed-3.py | 124 +- tutorials/ed-4.py | 74 +- tutorials/ed-5.py | 136 +- tutorials/ed-6.py | 92 +- tutorials/ed-7.py | 48 +- tutorials/ed-8.py | 82 +- tutorials/ed-9.py | 106 +- tutorials/index.json | 18 +- 265 files changed, 13467 insertions(+), 6365 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 docs/api-reference/datablocks/experiment.md create mode 100644 docs/api-reference/datablocks/structure.md delete mode 100644 docs/api-reference/experiments.md delete mode 100644 docs/api-reference/sample_models.md create mode 100644 docs/architecture/architecture.md create mode 100644 docs/architecture/issues_closed.md create mode 100644 docs/architecture/issues_open.md create mode 100644 src/easydiffraction/analysis/categories/aliases/__init__.py rename src/easydiffraction/analysis/categories/{aliases.py => aliases/default.py} (58%) create mode 100644 src/easydiffraction/analysis/categories/aliases/factory.py create mode 100644 src/easydiffraction/analysis/categories/constraints/__init__.py rename src/easydiffraction/analysis/categories/{constraints.py => constraints/default.py} (58%) create mode 100644 src/easydiffraction/analysis/categories/constraints/factory.py create mode 100644 src/easydiffraction/analysis/categories/fit_mode/__init__.py create mode 100644 src/easydiffraction/analysis/categories/fit_mode/enums.py create mode 100644 src/easydiffraction/analysis/categories/fit_mode/factory.py create mode 100644 src/easydiffraction/analysis/categories/fit_mode/fit_mode.py create mode 100644 src/easydiffraction/analysis/categories/joint_fit_experiments/__init__.py rename src/easydiffraction/analysis/categories/{joint_fit_experiments.py => joint_fit_experiments/default.py} (61%) create mode 100644 src/easydiffraction/analysis/categories/joint_fit_experiments/factory.py create mode 100644 src/easydiffraction/core/metadata.py rename src/easydiffraction/core/{singletons.py => singleton.py} (94%) rename src/easydiffraction/core/{parameters.py => variable.py} (80%) rename src/easydiffraction/{experiments => datablocks}/__init__.py (100%) rename src/easydiffraction/{experiments/categories => datablocks/experiment}/__init__.py (100%) rename src/easydiffraction/{experiments/categories/background => datablocks/experiment/categories}/__init__.py (100%) create mode 100644 src/easydiffraction/datablocks/experiment/categories/background/__init__.py rename src/easydiffraction/{experiments => datablocks/experiment}/categories/background/base.py (100%) rename src/easydiffraction/{experiments => datablocks/experiment}/categories/background/chebyshev.py (69%) rename src/easydiffraction/{experiments => datablocks/experiment}/categories/background/enums.py (95%) create mode 100644 src/easydiffraction/datablocks/experiment/categories/background/factory.py rename src/easydiffraction/{experiments => datablocks/experiment}/categories/background/line_segment.py (74%) create mode 100644 src/easydiffraction/datablocks/experiment/categories/data/__init__.py rename src/easydiffraction/{experiments => datablocks/experiment}/categories/data/bragg_pd.py (79%) rename src/easydiffraction/{experiments => datablocks/experiment}/categories/data/bragg_sc.py (76%) create mode 100644 src/easydiffraction/datablocks/experiment/categories/data/factory.py rename src/easydiffraction/{experiments => datablocks/experiment}/categories/data/total_pd.py (79%) create mode 100644 src/easydiffraction/datablocks/experiment/categories/excluded_regions/__init__.py rename src/easydiffraction/{experiments/categories/excluded_regions.py => datablocks/experiment/categories/excluded_regions/default.py} (76%) create mode 100644 src/easydiffraction/datablocks/experiment/categories/excluded_regions/factory.py create mode 100644 src/easydiffraction/datablocks/experiment/categories/experiment_type/__init__.py rename src/easydiffraction/{experiments/categories/experiment_type.py => datablocks/experiment/categories/experiment_type/default.py} (51%) create mode 100644 src/easydiffraction/datablocks/experiment/categories/experiment_type/factory.py create mode 100644 src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py create mode 100644 src/easydiffraction/datablocks/experiment/categories/extinction/factory.py rename src/easydiffraction/{experiments/categories/extinction.py => datablocks/experiment/categories/extinction/shelx.py} (59%) create mode 100644 src/easydiffraction/datablocks/experiment/categories/instrument/__init__.py rename src/easydiffraction/{experiments => datablocks/experiment}/categories/instrument/base.py (100%) rename src/easydiffraction/{experiments => datablocks/experiment}/categories/instrument/cwl.py (53%) create mode 100644 src/easydiffraction/datablocks/experiment/categories/instrument/factory.py rename src/easydiffraction/{experiments => datablocks/experiment}/categories/instrument/tof.py (56%) create mode 100644 src/easydiffraction/datablocks/experiment/categories/linked_crystal/__init__.py rename src/easydiffraction/{experiments/categories/linked_crystal.py => datablocks/experiment/categories/linked_crystal/default.py} (54%) create mode 100644 src/easydiffraction/datablocks/experiment/categories/linked_crystal/factory.py create mode 100644 src/easydiffraction/datablocks/experiment/categories/linked_phases/__init__.py rename src/easydiffraction/{experiments/categories/linked_phases.py => datablocks/experiment/categories/linked_phases/default.py} (60%) create mode 100644 src/easydiffraction/datablocks/experiment/categories/linked_phases/factory.py create mode 100644 src/easydiffraction/datablocks/experiment/categories/peak/__init__.py rename src/easydiffraction/{experiments => datablocks/experiment}/categories/peak/base.py (100%) create mode 100644 src/easydiffraction/datablocks/experiment/categories/peak/cwl.py create mode 100644 src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py create mode 100644 src/easydiffraction/datablocks/experiment/categories/peak/factory.py create mode 100644 src/easydiffraction/datablocks/experiment/categories/peak/tof.py create mode 100644 src/easydiffraction/datablocks/experiment/categories/peak/tof_mixins.py create mode 100644 src/easydiffraction/datablocks/experiment/categories/peak/total.py create mode 100644 src/easydiffraction/datablocks/experiment/categories/peak/total_mixins.py create mode 100644 src/easydiffraction/datablocks/experiment/collection.py create mode 100644 src/easydiffraction/datablocks/experiment/item/__init__.py create mode 100644 src/easydiffraction/datablocks/experiment/item/base.py create mode 100644 src/easydiffraction/datablocks/experiment/item/bragg_pd.py rename src/easydiffraction/{experiments/experiment => datablocks/experiment/item}/bragg_sc.py (75%) rename src/easydiffraction/{experiments/experiment => datablocks/experiment/item}/enums.py (88%) create mode 100644 src/easydiffraction/datablocks/experiment/item/factory.py rename src/easydiffraction/{experiments/experiment => datablocks/experiment/item}/total_pd.py (64%) rename src/easydiffraction/{experiments/categories/instrument => datablocks/structure}/__init__.py (100%) rename src/easydiffraction/{experiments/categories/peak => datablocks/structure/categories}/__init__.py (100%) create mode 100644 src/easydiffraction/datablocks/structure/categories/atom_sites/__init__.py create mode 100644 src/easydiffraction/datablocks/structure/categories/atom_sites/default.py create mode 100644 src/easydiffraction/datablocks/structure/categories/atom_sites/factory.py rename src/easydiffraction/{sample_models/categories => datablocks/structure/categories/cell}/__init__.py (65%) create mode 100644 src/easydiffraction/datablocks/structure/categories/cell/default.py create mode 100644 src/easydiffraction/datablocks/structure/categories/cell/factory.py rename src/easydiffraction/{sample_models/sample_model => datablocks/structure/categories/space_group}/__init__.py (61%) create mode 100644 src/easydiffraction/datablocks/structure/categories/space_group/default.py create mode 100644 src/easydiffraction/datablocks/structure/categories/space_group/factory.py create mode 100644 src/easydiffraction/datablocks/structure/collection.py rename src/easydiffraction/{sample_models => datablocks/structure/item}/__init__.py (100%) create mode 100644 src/easydiffraction/datablocks/structure/item/base.py create mode 100644 src/easydiffraction/datablocks/structure/item/factory.py delete mode 100644 src/easydiffraction/experiments/categories/background/factory.py delete mode 100644 src/easydiffraction/experiments/categories/data/factory.py delete mode 100644 src/easydiffraction/experiments/categories/instrument/factory.py delete mode 100644 src/easydiffraction/experiments/categories/peak/cwl.py delete mode 100644 src/easydiffraction/experiments/categories/peak/cwl_mixins.py delete mode 100644 src/easydiffraction/experiments/categories/peak/factory.py delete mode 100644 src/easydiffraction/experiments/categories/peak/tof.py delete mode 100644 src/easydiffraction/experiments/categories/peak/tof_mixins.py delete mode 100644 src/easydiffraction/experiments/categories/peak/total.py delete mode 100644 src/easydiffraction/experiments/categories/peak/total_mixins.py delete mode 100644 src/easydiffraction/experiments/experiment/__init__.py delete mode 100644 src/easydiffraction/experiments/experiment/base.py delete mode 100644 src/easydiffraction/experiments/experiment/bragg_pd.py delete mode 100644 src/easydiffraction/experiments/experiment/factory.py delete mode 100644 src/easydiffraction/experiments/experiments.py delete mode 100644 src/easydiffraction/sample_models/categories/atom_sites.py delete mode 100644 src/easydiffraction/sample_models/categories/cell.py delete mode 100644 src/easydiffraction/sample_models/categories/space_group.py delete mode 100644 src/easydiffraction/sample_models/sample_model/base.py delete mode 100644 src/easydiffraction/sample_models/sample_model/factory.py delete mode 100644 src/easydiffraction/sample_models/sample_models.py create mode 100644 tests/integration/fitting/test_multi.py delete mode 100644 tests/integration/fitting/test_powder-diffraction_multiphase.py rename tests/unit/easydiffraction/{experiments => datablocks/experiment}/categories/background/test_base.py (75%) rename tests/unit/easydiffraction/{experiments => datablocks/experiment}/categories/background/test_chebyshev.py (85%) create mode 100644 tests/unit/easydiffraction/datablocks/experiment/categories/background/test_enums.py create mode 100644 tests/unit/easydiffraction/datablocks/experiment/categories/background/test_factory.py rename tests/unit/easydiffraction/{experiments => datablocks/experiment}/categories/background/test_line_segment.py (86%) create mode 100644 tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_pd.py create mode 100644 tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_sc.py create mode 100644 tests/unit/easydiffraction/datablocks/experiment/categories/data/test_factory.py create mode 100644 tests/unit/easydiffraction/datablocks/experiment/categories/data/test_total_pd.py rename tests/unit/easydiffraction/{experiments => datablocks/experiment}/categories/instrument/test_base.py (79%) rename tests/unit/easydiffraction/{experiments => datablocks/experiment}/categories/instrument/test_cwl.py (81%) create mode 100644 tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_factory.py rename tests/unit/easydiffraction/{experiments => datablocks/experiment}/categories/instrument/test_tof.py (94%) rename tests/unit/easydiffraction/{experiments => datablocks/experiment}/categories/peak/test_base.py (81%) rename tests/unit/easydiffraction/{experiments => datablocks/experiment}/categories/peak/test_cwl.py (79%) rename tests/unit/easydiffraction/{experiments => datablocks/experiment}/categories/peak/test_cwl_mixins.py (74%) create mode 100644 tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_factory.py rename tests/unit/easydiffraction/{experiments => datablocks/experiment}/categories/peak/test_tof.py (72%) rename tests/unit/easydiffraction/{experiments => datablocks/experiment}/categories/peak/test_tof_mixins.py (71%) rename tests/unit/easydiffraction/{experiments => datablocks/experiment}/categories/peak/test_total.py (91%) rename tests/unit/easydiffraction/{experiments => datablocks/experiment}/categories/peak/test_total_mixins.py (79%) rename tests/unit/easydiffraction/{experiments => datablocks/experiment}/categories/test_excluded_regions.py (92%) create mode 100644 tests/unit/easydiffraction/datablocks/experiment/categories/test_experiment_type.py create mode 100644 tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py create mode 100644 tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_crystal.py rename tests/unit/easydiffraction/{experiments => datablocks/experiment}/categories/test_linked_phases.py (60%) create mode 100644 tests/unit/easydiffraction/datablocks/experiment/item/test_base.py rename tests/unit/easydiffraction/{experiments/experiment => datablocks/experiment/item}/test_bragg_pd.py (65%) rename tests/unit/easydiffraction/{experiments/experiment => datablocks/experiment/item}/test_bragg_sc.py (51%) rename tests/unit/easydiffraction/{experiments/experiment => datablocks/experiment/item}/test_enums.py (73%) create mode 100644 tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py rename tests/unit/easydiffraction/{experiments/experiment => datablocks/experiment/item}/test_total_pd.py (61%) rename tests/unit/easydiffraction/{experiments/test_experiments.py => datablocks/experiment/test_collection.py} (74%) rename tests/unit/easydiffraction/{sample_models => datablocks/structure}/categories/test_space_group.py (88%) rename tests/unit/easydiffraction/{sample_models/sample_model => datablocks/structure/item}/test_base.py (50%) create mode 100644 tests/unit/easydiffraction/datablocks/structure/item/test_factory.py rename tests/unit/easydiffraction/{sample_models/test_sample_models.py => datablocks/structure/test_collection.py} (70%) delete mode 100644 tests/unit/easydiffraction/experiments/categories/background/test_enums.py delete mode 100644 tests/unit/easydiffraction/experiments/categories/background/test_factory.py delete mode 100644 tests/unit/easydiffraction/experiments/categories/instrument/test_factory.py delete mode 100644 tests/unit/easydiffraction/experiments/categories/peak/test_factory.py delete mode 100644 tests/unit/easydiffraction/experiments/categories/test_experiment_type.py delete mode 100644 tests/unit/easydiffraction/experiments/experiment/test_base.py delete mode 100644 tests/unit/easydiffraction/experiments/experiment/test_factory.py delete mode 100644 tests/unit/easydiffraction/sample_models/categories/test_atom_sites.py delete mode 100644 tests/unit/easydiffraction/sample_models/categories/test_cell.py delete mode 100644 tests/unit/easydiffraction/sample_models/sample_model/test_factory.py create mode 100644 tmp/__validator.py create mode 100644 tutorials/data/ed-3.xye create mode 100644 tutorials/ed-16.py diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..58e7d29d --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,136 @@ +# Copilot Instructions for EasyDiffraction + +## Project Context + +- Python library for crystallographic diffraction analysis, such as refinement + of the structural model against experimental data. +- Support for + - sample_form: powder and single crystal + - beam_mode: time-of-flight and constant wavelength + - radiation_probe: neutron and x-ray + - scattering_type: bragg and total scattering +- Calculations are done using external calculation libraries: + - `cryspy` for Bragg diffraction + - `crysfml` for Bragg diffraction + - `pdffit2` for Total scattering +- Follow CIF naming conventions where possible. In some places, we deviate for + better API design, but we try to keep the spirit of the CIF names. +- Reusing the concept of datablocks and categories from CIF. We have + `DatablockItem` (structure or experiment) and `DatablockCollection` + (collection of structures or experiments), as well as `CategoryItem` (single + categories in CIF) and `CategoryCollection` (loop categories in CIF). +- Metadata via frozen dataclasses: `TypeInfo`, `Compatibility`, + `CalculatorSupport`. +- The API is designed for scientists who use EasyDiffraction as a final product + in a user-friendly, intuitive way. The target users are not software + developers and may have little or no Python experience. The design is not + oriented toward developers building their own tooling on top of the library, + although experienced developers will find their own way. Prioritize + discoverability, clear error messages, and safe defaults so that + non-programmers are not stuck by standard API conventions. +- This project must be developed to be as error-free as possible, with the same + rigour applied to critical software (e.g. nuclear-plant control systems). + Every code path must be tested, edge cases must be handled explicitly, and + silent failures are not acceptable. + +## Code Style + +- Use snake_case for functions and variables, PascalCase for classes, and + UPPER_SNAKE_CASE for constants. +- Use `from __future__ import annotations` in every module. +- Type-annotate all public function signatures. +- Docstrings on all public classes and methods (Google style). +- Prefer flat over nested, explicit over clever. +- Write straightforward code; do not add defensive checks for unlikely edge + cases. +- Prefer composition over deep inheritance. +- One class per file when the class is substantial; group small related classes. +- Avoid `**kwargs`; use explicit keyword arguments for clarity, autocomplete, + and typo detection. +- Do not use string-based dispatch (e.g. `getattr(self, f'_{name}')`) to route + to attributes or methods. Instead, write explicit named methods (e.g. + `_set_sample_form`, `_set_beam_mode`). This keeps the code greppable, + autocomplete-friendly, and type-safe. +- Public parameters and descriptors are either **editable** (property with both + getter and setter) or **read-only** (property with getter only). If internal + code needs to mutate a read-only property, add a private `_set_` method + instead of exposing a public setter. + +## Architecture + +- Eager imports at the top of the module by default. Use lazy imports (inside a + method body) only when necessary to break circular dependencies or to keep + `core/` free of heavy utility imports on rarely-called paths (e.g. `help()`). +- No `pkgutil` / `importlib` auto-discovery patterns. +- No background/daemon threads. +- No monkey-patching or runtime class mutation. +- Do not use `__all__` in modules; instead, rely on explicit imports in + `__init__.py` to control the public API. +- Do not use redundant `import X as X` aliases in `__init__.py`. Use plain + `from module import X`. +- Concrete classes use `@Factory.register` decorators. To trigger registration, + each package's `__init__.py` must explicitly import every concrete class (e.g. + `from .chebyshev import ChebyshevPolynomialBackground`). When adding a new + concrete class, always add its import to the corresponding `__init__.py`. +- Switchable categories (those whose implementation can be swapped at runtime + via a factory) follow a fixed naming convention on the owner (experiment, + structure, or analysis): `` (read-only property), `_type` + (getter + setter), `show_supported__types()`, + `show_current__type()`. The owner class owns the type setter and the + show methods; the show methods delegate to `Factory.show_supported(...)` + passing context. Every factory-created category must have this full API, even + if only one implementation exists today. +- Categories are flat siblings within their owner (datablock or analysis). A + category must never be a child of another category of a different type. + Categories can reference each other via IDs, but not via parent-child nesting. +- Every finite, closed set of values (factory tags, experiment axes, category + descriptors with enumerated choices) must use a `(str, Enum)` class. Internal + code compares against enum members, never raw strings. +- Keep `core/` free of domain logic — only base classes and utilities. +- Don't introduce a new abstraction until there is a concrete second use case. +- Don't add dependencies without asking. + +## Changes + +- Before implementing any structural or design change (new categories, new + factories, switchable-category wiring, new datablocks, CIF serialisation + changes), read `docs/architecture/architecture.md` to understand the current + design choices and conventions. Follow the documented patterns (factory + registration, switchable-category naming, metadata classification, etc.) to + stay consistent with the rest of the codebase. For localised bug fixes or test + updates, the rules in this file are sufficient. +- The project is in beta; do not keep legacy code or add deprecation warnings. + Instead, update tests and tutorials to follow the current API. +- Minimal diffs: don't rewrite working code just to reformat it. +- Never remove or replace existing functionality as part of a new change without + explicit confirmation. If a refactor would drop features, options, or + configurations, highlight every removal and wait for approval. +- Fix only what's asked; flag adjacent issues as comments, don't fix them + silently. +- Don't add new features or refactor existing code unless explicitly asked. +- Do not remove TODOs or comments unless the change fully resolves them. +- When renaming, grep the entire project (code, tests, tutorials, docs). +- Every change should be atomic and self-contained, small enough to be described + by a single commit message. Make one change, suggest the commit message, then + stop and wait for confirmation before starting the next change. +- When in doubt, ask for clarification before making changes. + +## Workflow + +- All open issues, design questions, and planned improvements are tracked in + `docs/architecture/issues_open.md`, ordered by priority. When an issue is + fully implemented, move it from that file to + `docs/architecture/issues_closed.md`. When the resolution affects the + architecture, update the relevant sections of + `docs/architecture/architecture.md`. +- After changes, run linting and formatting fixes with `pixi run fix`. Do not + check what was auto-fixed, just accept the fixes and move on. +- After changes, run unit tests with `pixi run unit-tests`. +- After changes, run integration tests with `pixi run integration-tests`. +- After changes, run tutorial tests with `pixi run script-tests`. +- Suggest a concise commit message (as a code block) after each change (less + than 72 characters, imperative mood, without prefixing with the type of + change). E.g.: + - Add ChebyshevPolynomialBackground class + - Implement background_type setter on Experiment + - Standardize switchable-category naming convention diff --git a/docs/api-reference/datablocks/experiment.md b/docs/api-reference/datablocks/experiment.md new file mode 100644 index 00000000..b0d19a6a --- /dev/null +++ b/docs/api-reference/datablocks/experiment.md @@ -0,0 +1 @@ +::: easydiffraction.datablocks.experiment diff --git a/docs/api-reference/datablocks/structure.md b/docs/api-reference/datablocks/structure.md new file mode 100644 index 00000000..43f752ff --- /dev/null +++ b/docs/api-reference/datablocks/structure.md @@ -0,0 +1 @@ +::: easydiffraction.datablocks.structure diff --git a/docs/api-reference/experiments.md b/docs/api-reference/experiments.md deleted file mode 100644 index 2eb1bd91..00000000 --- a/docs/api-reference/experiments.md +++ /dev/null @@ -1 +0,0 @@ -::: easydiffraction.experiments diff --git a/docs/api-reference/index.md b/docs/api-reference/index.md index 25418279..6a6f8be4 100644 --- a/docs/api-reference/index.md +++ b/docs/api-reference/index.md @@ -13,12 +13,13 @@ available in EasyDiffraction: space groups, and symmetry operations. - [utils](utils.md) – Miscellaneous utility functions for formatting, decorators, and general helpers. +- datablocks + - [experiments](datablocks/experiment.md) – Manages experimental setups and + instrument parameters, as well as the associated diffraction data. + - [structures](datablocks/structure.md) – Defines structures, such as + crystallographic structures, and manages their properties. - [display](display.md) – Tools for plotting data and rendering tables. - [project](project.md) – Defines the project and manages its state. -- [sample_models](sample_models.md) – Defines sample models, such as - crystallographic structures, and manages their properties. -- [experiments](experiments.md) – Manages experimental setups and instrument - parameters, as well as the associated diffraction data. - [analysis](analysis.md) – Provides tools for analyzing diffraction data, including fitting and minimization. - [summary](summary.md) – Provides a summary of the project. diff --git a/docs/api-reference/sample_models.md b/docs/api-reference/sample_models.md deleted file mode 100644 index 43c54901..00000000 --- a/docs/api-reference/sample_models.md +++ /dev/null @@ -1 +0,0 @@ -::: easydiffraction.sample_models diff --git a/docs/architecture/architecture.md b/docs/architecture/architecture.md new file mode 100644 index 00000000..3770f3ea --- /dev/null +++ b/docs/architecture/architecture.md @@ -0,0 +1,1034 @@ +# EasyDiffraction Architecture + +**Version:** 1.0 +**Date:** 2026-03-24 +**Status:** Living document — updated as the project evolves + +--- + +## 1. Overview + +EasyDiffraction is a Python library for crystallographic diffraction analysis +(Rietveld refinement, pair-distribution-function fitting, etc.). It models the +domain using **CIF-inspired abstractions** — datablocks, categories, and +parameters — while providing a high-level, user-friendly API through a single +`Project` façade. + +### 1.1 Supported Experiment Dimensions + +Every experiment is fully described by four orthogonal axes: + +| Axis | Options | Enum | +| --------------- | ----------------------------------- | -------------------- | +| Sample form | powder, single crystal | `SampleFormEnum` | +| Scattering type | Bragg, total (PDF) | `ScatteringTypeEnum` | +| Beam mode | constant wavelength, time-of-flight | `BeamModeEnum` | +| Radiation probe | neutron, X-ray | `RadiationProbeEnum` | + +> **Planned extensions:** 1D / 2D data dimensionality, polarised / unpolarised +> neutron beam. + +### 1.2 Calculation Engines + +External libraries perform the heavy computation: + +| Engine | Scope | +| --------- | ----------------- | +| `cryspy` | Bragg diffraction | +| `crysfml` | Bragg diffraction | +| `pdffit2` | Total scattering | + +--- + +## 2. Core Abstractions + +All core types live in `core/` which contains **only** base classes and +utilities — no domain logic. + +### 2.1 Object Hierarchy + +```shell +GuardedBase # Controlled attribute access, parent linkage, identity +├── CategoryItem # Single CIF category row (e.g. Cell, Peak, Instrument) +├── CollectionBase # Ordered name→item container +│ ├── CategoryCollection # CIF loop (e.g. AtomSites, Background, Data) +│ └── DatablockCollection # Top-level container (e.g. Structures, Experiments) +└── DatablockItem # CIF data block (e.g. Structure, Experiment) +``` + +`CollectionBase` provides a unified dict-like API over an ordered item list with +name-based indexing. All key operations — `__getitem__`, `__setitem__`, +`__delitem__`, `__contains__`, `remove()` — resolve keys through a single +`_key_for(item)` method that returns `category_entry_name` for category items or +`datablock_entry_name` for datablock items. Subclasses `CategoryCollection` and +`DatablockCollection` inherit this consistently. + +### 2.2 GuardedBase — Controlled Attribute Access + +`GuardedBase` is the root ABC. It enforces that only **declared `@property` +attributes** are accessible publicly: + +- **`__getattr__`** rejects any attribute not declared as a `@property` on the + class hierarchy. Shows diagnostics with closest-match suggestions on typos. +- **`__setattr__`** distinguishes: + - **Private** (`_`-prefixed) — always allowed, no diagnostics. + - **Read-only public** (property without setter) — blocked with a clear error. + - **Writable public** (property with setter) — goes through the property + setter, which is where validation happens. + - **Unknown** — blocked with diagnostics showing allowed writable attrs. +- **Parent linkage** — when a `GuardedBase` child is assigned to another, the + child's `_parent` is set automatically, forming an implicit ownership tree. +- **Identity** — every instance gets an `_identity: Identity` object for lazy + CIF-style name resolution (`datablock_entry_name`, `category_code`, + `category_entry_name`) by walking the `_parent` chain. + +**Key design rule:** if a parameter has a public setter, it is writable for the +user. If only a getter — it is read-only. If internal code needs to set it, a +private method (underscore prefix) is used. See § 2.2.1 below for the full +pattern. + +#### 2.2.1 Public Property Convention — Editable vs Read-Only + +Every public parameter or descriptor exposed on a `GuardedBase` subclass follows +one of two patterns: + +| Kind | Getter | Setter | Internal mutation | +| ------------- | ------ | ------ | ---------------------------------- | +| **Editable** | yes | yes | Via the public setter | +| **Read-only** | yes | no | Via a private `_set_` method | + +**Editable property** — the user can both read and write the value. The setter +runs through `GuardedBase.__setattr__` and into the property setter, where +validation happens: + +```python +@property +def name(self) -> str: + """Human-readable name of the experiment.""" + return self._name + + +@name.setter +def name(self, new: str) -> None: + self._name = new +``` + +**Read-only property** — the user can read but cannot assign. Any attempt to set +the attribute is blocked by `GuardedBase.__setattr__` with a clear error +message. If _internal_ code (factory builders, CIF loaders, etc.) needs to set +the value, it calls a private `_set_` method instead of exposing a public +setter: + +```python +@property +def sample_form(self) -> StringDescriptor: + """Sample form descriptor (read-only for the user).""" + return self._sample_form + + +def _set_sample_form(self, value: str) -> None: + """Internal setter used by factory/CIF code during construction.""" + self._sample_form.value = value +``` + +**Why this matters:** + +- `GuardedBase.__setattr__` uses the presence of a setter to decide writability. + Adding a setter "just for internal use" would open the attribute to users. +- Private `_set_` methods keep the public API surface minimal and + intention-clear, while remaining greppable and type-safe. +- The pattern avoids string-based dispatch — every mutator has an explicit named + method. + +### 2.3 CategoryItem and CategoryCollection + +| Aspect | `CategoryItem` | `CategoryCollection` | +| --------------- | ---------------------------------- | ----------------------------------------- | +| CIF analogy | Single category row | Loop (table) of rows | +| Examples | Cell, SpaceGroup, Instrument, Peak | AtomSites, Background, Data, LinkedPhases | +| Parameters | All `GenericDescriptorBase` attrs | Aggregated from all child items | +| Serialisation | `as_cif` / `from_cif` | `as_cif` / `from_cif` | +| Update hook | `_update(called_by_minimizer=)` | `_update(called_by_minimizer=)` | +| Update priority | `_update_priority` (default 10) | `_update_priority` (default 10) | +| Display | `show()` on concrete subclasses | `show()` on concrete subclasses | +| Building items | N/A | `add(item)`, `create(**kwargs)` | + +**Update priority:** lower values run first. This ensures correct execution +order within a datablock (e.g. background before data). + +### 2.4 DatablockItem and DatablockCollection + +| Aspect | `DatablockItem` | `DatablockCollection` | +| ------------------ | ------------------------------------------- | ------------------------------ | +| CIF analogy | A single `data_` block | Collection of data blocks | +| Examples | Structure, BraggPdExperiment | Structures, Experiments | +| Category discovery | Scans `vars(self)` for categories | N/A | +| Update cascade | `_update_categories()` — sorted by priority | N/A | +| Parameters | Aggregated from all categories | Aggregated from all datablocks | +| Fittable params | N/A | Non-constrained `Parameter`s | +| Free params | N/A | Fittable + `free == True` | +| Dirty flag | `_need_categories_update` | N/A | + +When any `Parameter.value` is set, it propagates +`_need_categories_update = True` up to the owning `DatablockItem`. Serialisation +(`as_cif`) and plotting trigger `_update_categories()` if the flag is set. + +### 2.5 Variable System — Parameters and Descriptors + +```shell +GuardedBase +└── GenericDescriptorBase # name, value (validated via AttributeSpec), description + ├── GenericStringDescriptor # _value_type = DataTypes.STRING + └── GenericNumericDescriptor # _value_type = DataTypes.NUMERIC, + units + └── GenericParameter # + free, uncertainty, fit_min, fit_max, constrained, uid +``` + +CIF-bound concrete classes add a `CifHandler` for serialisation: + +| Class | Base | Use case | +| ------------------- | -------------------------- | ---------------------------- | +| `StringDescriptor` | `GenericStringDescriptor` | Read-only or writable text | +| `NumericDescriptor` | `GenericNumericDescriptor` | Read-only or writable number | +| `Parameter` | `GenericParameter` | Fittable numeric value | + +**Initialisation rule:** all Parameters/Descriptors are initialised with their +default values from `value_spec` (an `AttributeSpec`) **without any validation** +— we trust internal definitions. Changes go through public property setters, +which run both type and value validation. + +**Mixin safety:** Parameter/Descriptor classes must not have init arguments so +they can be used as mixins safely (e.g. `PdTofDataPointMixin`). + +### 2.6 Validation + +`AttributeSpec` bundles `default`, `data_type`, `validator`, `allow_none`. +Validators include: + +| Validator | Purpose | +| --------------------- | -------------------------------------- | +| `TypeValidator` | Checks Python type against `DataTypes` | +| `RangeValidator` | `ge`, `le`, `gt`, `lt` bounds checking | +| `MembershipValidator` | Value must be in an allowed set | +| `RegexValidator` | Value must match a pattern | + +--- + +## 3. Experiment System + +### 3.1 Experiment Type + +An experiment's type is defined by the four enum axes and is **immutable after +creation**. This avoids the complexity of transforming all internal state when +the experiment type changes. The type is stored in an `ExperimentType` category +with four `StringDescriptor`s validated by `MembershipValidator`s. Public +properties are read-only; factory and CIF-loading code use private setters +(`_set_sample_form`, `_set_beam_mode`, `_set_radiation_probe`, +`_set_scattering_type`) during construction only. + +### 3.2 Experiment Hierarchy + +```shell +DatablockItem +└── ExperimentBase # name, type: ExperimentType, as_cif + ├── PdExperimentBase # + linked_phases, excluded_regions, peak, data + │ ├── BraggPdExperiment # + instrument, background (both via factories) + │ └── TotalPdExperiment # (no extra categories yet) + └── ScExperimentBase # + linked_crystal, extinction, instrument, data + ├── CwlScExperiment + └── TofScExperiment +``` + +Each concrete experiment class carries: + +- `type_info: TypeInfo` — tag and description for factory lookup +- `compatibility: Compatibility` — which enum axis values it supports + +### 3.3 Category Ownership + +Every experiment owns its categories as private attributes with public read-only +or read-write properties: + +```python +# Read-only — user cannot replace the object, only modify its contents +experiment.linked_phases # CategoryCollection +experiment.excluded_regions # CategoryCollection +experiment.instrument # CategoryItem +experiment.peak # CategoryItem +experiment.data # CategoryCollection + +# Type-switchable — recreates the underlying object +experiment.background_type = 'chebyshev' # triggers BackgroundFactory.create(...) +experiment.peak_profile_type = 'thompson-cox-hastings' # triggers PeakFactory.create(...) +experiment.extinction_type = 'shelx' # triggers ExtinctionFactory.create(...) +experiment.linked_crystal_type = 'default' # triggers LinkedCrystalFactory.create(...) +experiment.excluded_regions_type = 'default' # triggers ExcludedRegionsFactory.create(...) +experiment.linked_phases_type = 'default' # triggers LinkedPhasesFactory.create(...) +``` + +**Type switching pattern:** `expt.background_type = 'chebyshev'` rather than +`expt.background.type = 'chebyshev'`. This keeps the API at the experiment level +and makes it clear that the entire category object is being replaced. + +--- + +## 4. Structure System + +### 4.1 Structure Hierarchy + +```shell +DatablockItem +└── Structure # name, cell, space_group, atom_sites +``` + +A `Structure` contains three categories: + +- `Cell` — unit cell parameters (`CategoryItem`) +- `SpaceGroup` — symmetry information (`CategoryItem`) +- `AtomSites` — atomic positions collection (`CategoryCollection`) + +Symmetry constraints (cell metric, atomic coordinates, ADPs) are applied via the +`crystallography` module during `_update_categories()`. + +--- + +## 5. Factory System + +### 5.1 FactoryBase + +All factories inherit from `FactoryBase`, which provides: + +| Feature | Method / Attribute | Description | +| ------------------ | ---------------------------- | ------------------------------------------------- | +| Registration | `@Factory.register` | Class decorator, appends to `_registry` | +| Supported map | `_supported_map()` | `{tag: class}` from all registered classes | +| Creation | `create(tag)` | Instantiate by tag string | +| Default resolution | `default_tag(**conditions)` | Largest-subset matching on `_default_rules` | +| Context creation | `create_default_for(**cond)` | Resolve tag → create | +| Filtered query | `supported_for(**filters)` | Filter by `Compatibility` and `CalculatorSupport` | +| Display | `show_supported(**filters)` | Pretty-print table of type + description | +| Tag listing | `supported_tags()` | List of all registered tags | + +Each `__init_subclass__` gives every factory its own independent `_registry` and +`_default_rules`. + +### 5.2 Default Rules + +`_default_rules` maps frozensets of `(axis_name, enum_value)` tuples to tag +strings (preferably enum values for type safety): + +```python +class PeakFactory(FactoryBase): + _default_rules = { + frozenset({ + ('scattering_type', ScatteringTypeEnum.BRAGG), + ('beam_mode', BeamModeEnum.CONSTANT_WAVELENGTH), + }): PeakProfileTypeEnum.PSEUDO_VOIGT, + frozenset({ + ('scattering_type', ScatteringTypeEnum.BRAGG), + ('beam_mode', BeamModeEnum.TIME_OF_FLIGHT), + }): PeakProfileTypeEnum.PSEUDO_VOIGT_IKEDA_CARPENTER, + frozenset({ + ('scattering_type', ScatteringTypeEnum.TOTAL), + }): PeakProfileTypeEnum.GAUSSIAN_DAMPED_SINC, + } +``` + +Resolution uses **largest-subset matching**: the rule whose frozenset is the +biggest subset of the given conditions wins. `frozenset()` acts as a universal +fallback. + +### 5.3 Metadata on Registered Classes + +Every `@Factory.register`-ed class carries three frozen dataclass attributes: + +```python +@PeakFactory.register +class CwlPseudoVoigt(PeakBase, CwlBroadeningMixin): + type_info = TypeInfo( + tag='pseudo-voigt', + description='Pseudo-Voigt profile', + ) + compatibility = Compatibility( + scattering_type=frozenset({ScatteringTypeEnum.BRAGG}), + beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH}), + ) + calculator_support = CalculatorSupport( + calculators=frozenset({CalculatorEnum.CRYSPY, CalculatorEnum.CRYSFML}), + ) +``` + +| Metadata | Purpose | +| ------------------- | ------------------------------------------------------- | +| `TypeInfo` | Stable tag for lookup/serialisation + human description | +| `Compatibility` | Which enum axis values this class works with | +| `CalculatorSupport` | Which calculation engines support this class | + +### 5.4 Registration Trigger + +Concrete classes use `@Factory.register` decorators. To trigger registration, +each package's `__init__.py` must **explicitly import** every concrete class: + +```python +# datablocks/experiment/categories/background/__init__.py +from .chebyshev import ChebyshevPolynomialBackground +from .line_segment import LineSegmentBackground +``` + +### 5.5 All Factories + +| Factory | Domain | Tags resolve to | +| ---------------------------- | ---------------------- | ----------------------------------------------------------- | +| `BackgroundFactory` | Background categories | `LineSegmentBackground`, `ChebyshevPolynomialBackground` | +| `PeakFactory` | Peak profiles | `CwlPseudoVoigt`, `TofPseudoVoigtIkedaCarpenter`, … | +| `InstrumentFactory` | Instruments | `CwlPdInstrument`, `TofPdInstrument`, … | +| `DataFactory` | Data collections | `PdCwlData`, `PdTofData`, `ReflnData`, `TotalData` | +| `ExtinctionFactory` | Extinction models | `ShelxExtinction` | +| `LinkedCrystalFactory` | Linked-crystal refs | `LinkedCrystal` | +| `ExcludedRegionsFactory` | Excluded regions | `ExcludedRegions` | +| `LinkedPhasesFactory` | Linked phases | `LinkedPhases` | +| `ExperimentTypeFactory` | Experiment descriptors | `ExperimentType` | +| `CellFactory` | Unit cells | `Cell` | +| `SpaceGroupFactory` | Space groups | `SpaceGroup` | +| `AtomSitesFactory` | Atom sites | `AtomSites` | +| `AliasesFactory` | Parameter aliases | `Aliases` | +| `ConstraintsFactory` | Parameter constraints | `Constraints` | +| `FitModeFactory` | Fit-mode category | `FitMode` | +| `JointFitExperimentsFactory` | Joint-fit weights | `JointFitExperiments` | +| `CalculatorFactory` | Calculation engines | `CryspyCalculator`, `CrysfmlCalculator`, `PdffitCalculator` | +| `MinimizerFactory` | Minimisers | `LmfitMinimizer`, `DfolsMinimizer`, … | + +> **Note:** `ExperimentFactory` and `StructureFactory` are _builder_ factories +> with `from_cif_path`, `from_cif_str`, `from_data_path`, and `from_scratch` +> classmethods. `ExperimentFactory` inherits `FactoryBase` and uses `@register` +> on all four concrete experiment classes; `_resolve_class` looks up the +> registered class via `default_tag()` + `_supported_map()`. `StructureFactory` +> is a plain class without `FactoryBase` inheritance (only one structure type +> exists today). + +### 5.6 Tag Naming Convention + +Tags are the user-facing identifiers for selecting types. They must be: + +- **Consistent** — use the same abbreviations everywhere. +- **Hyphen-separated** — all lowercase, words joined by hyphens. +- **Semantically ordered** — from general to specific. +- **Unique within a factory** — but may overlap across factories. + +#### Standard Abbreviations + +| Concept | Abbreviation | Never use | +| ------------------- | ------------ | --------------------------- | +| Powder | `pd` | `powder` | +| Single crystal | `sc` | `single-crystal` | +| Constant wavelength | `cwl` | `cw`, `constant-wavelength` | +| Time-of-flight | `tof` | `time-of-flight` | +| Bragg (scattering) | `bragg` | | +| Total (scattering) | `total` | | + +#### Complete Tag Registry + +**Background tags** + +| Tag | Class | +| -------------- | ------------------------------- | +| `line-segment` | `LineSegmentBackground` | +| `chebyshev` | `ChebyshevPolynomialBackground` | + +**Peak tags** + +| Tag | Class | +| ---------------------------------- | ------------------------------ | +| `pseudo-voigt` | `CwlPseudoVoigt` | +| `split-pseudo-voigt` | `CwlSplitPseudoVoigt` | +| `thompson-cox-hastings` | `CwlThompsonCoxHastings` | +| `tof-pseudo-voigt` | `TofPseudoVoigt` | +| `tof-pseudo-voigt-ikeda-carpenter` | `TofPseudoVoigtIkedaCarpenter` | +| `tof-pseudo-voigt-back-to-back` | `TofPseudoVoigtBackToBack` | +| `gaussian-damped-sinc` | `TotalGaussianDampedSinc` | + +**Instrument tags** + +| Tag | Class | +| -------- | ----------------- | +| `cwl-pd` | `CwlPdInstrument` | +| `cwl-sc` | `CwlScInstrument` | +| `tof-pd` | `TofPdInstrument` | +| `tof-sc` | `TofScInstrument` | + +**Data tags** + +| Tag | Class | +| -------------- | ----------- | +| `bragg-pd-cwl` | `PdCwlData` | +| `bragg-pd-tof` | `PdTofData` | +| `bragg-sc` | `ReflnData` | +| `total-pd` | `TotalData` | + +**Extinction tags** + +| Tag | Class | +| ------- | ----------------- | +| `shelx` | `ShelxExtinction` | + +**Linked-crystal tags** + +| Tag | Class | +| --------- | --------------- | +| `default` | `LinkedCrystal` | + +**Experiment tags** + +| Tag | Class | +| -------------- | ------------------- | +| `bragg-pd` | `BraggPdExperiment` | +| `total-pd` | `TotalPdExperiment` | +| `bragg-sc-cwl` | `CwlScExperiment` | +| `bragg-sc-tof` | `TofScExperiment` | + +**Calculator tags** + +| Tag | Class | +| --------- | ------------------- | +| `cryspy` | `CryspyCalculator` | +| `crysfml` | `CrysfmlCalculator` | +| `pdffit` | `PdffitCalculator` | + +**Minimizer tags** + +| Tag | Class | +| ----------------------- | ----------------------------------------- | +| `lmfit` | `LmfitMinimizer` | +| `lmfit (leastsq)` | `LmfitMinimizer` (method=`leastsq`) | +| `lmfit (least_squares)` | `LmfitMinimizer` (method=`least_squares`) | +| `dfols` | `DfolsMinimizer` | + +> **Note:** minimizer variant tags (`lmfit (leastsq)`, `lmfit (least_squares)`) +> are planned but not yet re-implemented after the `FactoryBase` migration. See +> `issues_open.md` for details. + +### 5.7 Metadata Classification — Which Classes Get What + +#### The Rule + +> **If a concrete class is created by a factory, it gets `type_info`, +> `compatibility`, and `calculator_support`.** +> +> **If a `CategoryItem` only exists as a child row inside a +> `CategoryCollection`, it does NOT get these attributes — the collection +> does.** + +#### Rationale + +A `LineSegment` item (a single background control point) is never selected, +created, or queried by a factory. It is always instantiated internally by its +parent `LineSegmentBackground` collection. The meaningful unit of selection is +the _collection_, not the item. The user picks "line-segment background" (the +collection type), not individual line-segment points. + +#### Singleton CategoryItems — factory-created (get all three) + +| Class | Factory | +| ------------------------------ | ----------------------- | +| `CwlPdInstrument` | `InstrumentFactory` | +| `CwlScInstrument` | `InstrumentFactory` | +| `TofPdInstrument` | `InstrumentFactory` | +| `TofScInstrument` | `InstrumentFactory` | +| `CwlPseudoVoigt` | `PeakFactory` | +| `CwlSplitPseudoVoigt` | `PeakFactory` | +| `CwlThompsonCoxHastings` | `PeakFactory` | +| `TofPseudoVoigt` | `PeakFactory` | +| `TofPseudoVoigtIkedaCarpenter` | `PeakFactory` | +| `TofPseudoVoigtBackToBack` | `PeakFactory` | +| `TotalGaussianDampedSinc` | `PeakFactory` | +| `ShelxExtinction` | `ExtinctionFactory` | +| `LinkedCrystal` | `LinkedCrystalFactory` | +| `Cell` | `CellFactory` | +| `SpaceGroup` | `SpaceGroupFactory` | +| `ExperimentType` | `ExperimentTypeFactory` | +| `FitMode` | `FitModeFactory` | + +#### CategoryCollections — factory-created (get all three) + +| Class | Factory | +| ------------------------------- | ---------------------------- | +| `LineSegmentBackground` | `BackgroundFactory` | +| `ChebyshevPolynomialBackground` | `BackgroundFactory` | +| `PdCwlData` | `DataFactory` | +| `PdTofData` | `DataFactory` | +| `TotalData` | `DataFactory` | +| `ReflnData` | `DataFactory` | +| `ExcludedRegions` | `ExcludedRegionsFactory` | +| `LinkedPhases` | `LinkedPhasesFactory` | +| `AtomSites` | `AtomSitesFactory` | +| `Aliases` | `AliasesFactory` | +| `Constraints` | `ConstraintsFactory` | +| `JointFitExperiments` | `JointFitExperimentsFactory` | + +#### CategoryItems that are ONLY children of collections (NO metadata) + +| Class | Parent collection | +| -------------------- | ------------------------------- | +| `LineSegment` | `LineSegmentBackground` | +| `PolynomialTerm` | `ChebyshevPolynomialBackground` | +| `AtomSite` | `AtomSites` | +| `PdCwlDataPoint` | `PdCwlData` | +| `PdTofDataPoint` | `PdTofData` | +| `TotalDataPoint` | `TotalData` | +| `Refln` | `ReflnData` | +| `LinkedPhase` | `LinkedPhases` | +| `ExcludedRegion` | `ExcludedRegions` | +| `Alias` | `Aliases` | +| `Constraint` | `Constraints` | +| `JointFitExperiment` | `JointFitExperiments` | + +#### Non-category classes — factory-created (get `type_info` only) + +| Class | Factory | Notes | +| ------------------- | ------------------- | -------------------------------------------------------- | +| `CryspyCalculator` | `CalculatorFactory` | No `compatibility` — limitations expressed on categories | +| `CrysfmlCalculator` | `CalculatorFactory` | (same) | +| `PdffitCalculator` | `CalculatorFactory` | (same) | +| `LmfitMinimizer` | `MinimizerFactory` | `type_info` only | +| `DfolsMinimizer` | `MinimizerFactory` | (same) | +| `BraggPdExperiment` | `ExperimentFactory` | `type_info` + `compatibility` (no `calculator_support`) | +| `TotalPdExperiment` | `ExperimentFactory` | (same) | +| `CwlScExperiment` | `ExperimentFactory` | (same) | +| `TofScExperiment` | `ExperimentFactory` | (same) | + +--- + +## 6. Analysis + +### 6.1 Calculator + +The calculator performs the actual diffraction computation. It is attached +per-experiment on the `ExperimentBase` object. Each experiment auto-resolves its +calculator on first access based on the data category's `calculator_support` +metadata and `CalculatorFactory._default_rules`. The `CalculatorFactory` filters +its registry by `engine_imported` (whether the third-party library is available +in the environment). + +The experiment exposes the standard switchable-category API: + +- `calculator` — read-only property (lazy, auto-resolved on first access) +- `calculator_type` — getter + setter +- `show_supported_calculator_types()` — filtered by data category support +- `show_current_calculator_type()` + +### 6.2 Minimiser + +The minimiser drives the optimisation loop. `MinimizerFactory` creates instances +by tag (e.g. `'lmfit'`, `'dfols'`). + +### 6.3 Fitter + +`Fitter` wraps a minimiser instance and orchestrates the fitting workflow: + +1. Collect `free_parameters` from structures + experiments. +2. Record start values. +3. Build an objective function that calls the calculator. +4. Delegate to `minimizer.fit()`. +5. Sync results (values + uncertainties) back to parameters. + +### 6.4 Analysis Object + +`Analysis` is bound to a `Project` and provides the high-level API: + +- Minimiser selection: `current_minimizer`, `show_available_minimizers()` +- Fit mode: `fit_mode` (`CategoryItem` with a `mode` descriptor validated by + `FitModeEnum`); `'single'` fits each experiment independently, `'joint'` fits + all simultaneously with weights from `joint_fit_experiments`. +- Joint-fit weights: `joint_fit_experiments` (`CategoryCollection` of + per-experiment weight entries); sibling of `fit_mode`, not a child. +- Parameter tables: `show_all_params()`, `show_fittable_params()`, + `show_free_params()`, `how_to_access_parameters()` +- Fitting: `fit()`, `show_fit_results()` +- Aliases and constraints (switchable categories with `aliases_type`, + `constraints_type`, `fit_mode_type`, `joint_fit_experiments_type`) + +--- + +## 7. Project — The Top-Level Façade + +`Project` is the single entry point for the user: + +```python +import easydiffraction as ed + +project = ed.Project(name='my_project') +``` + +It owns and coordinates all components: + +| Property | Type | Description | +| --------------------- | ------------- | ---------------------------------------- | +| `project.info` | `ProjectInfo` | Metadata: name, title, description, path | +| `project.structures` | `Structures` | Collection of structure datablocks | +| `project.experiments` | `Experiments` | Collection of experiment datablocks | +| `project.analysis` | `Analysis` | Calculator, minimiser, fitting | +| `project.summary` | `Summary` | Report generation | +| `project.plotter` | `Plotter` | Visualisation | + +### 7.1 Data Flow + +``` +Parameter.value set + → AttributeSpec validation (type + value) + → _need_categories_update = True (on parent DatablockItem) + +Plot / CIF export / fit objective evaluation + → _update_categories() + → categories sorted by _update_priority + → each category._update() + → background: interpolate/evaluate → write to data + → calculator: compute pattern → write to data + → _need_categories_update = False +``` + +### 7.2 Persistence + +Projects are saved as a directory of CIF files: + +```shell +project_dir/ +├── project.cif # ProjectInfo +├── analysis.cif # Analysis settings +├── summary.cif # Summary report +├── structures/ +│ └── lbco.cif # One file per structure +└── experiments/ + └── hrpt.cif # One file per experiment +``` + +--- + +## 8. User-Facing API Patterns + +All examples below are drawn from the actual tutorials (`tutorials/`). + +### 8.1 Project Setup + +```python +import easydiffraction as ed + +project = ed.Project(name='lbco_hrpt') +project.info.title = 'La0.5Ba0.5CoO3 at HRPT@PSI' +project.save_as(dir_path='lbco_hrpt', temporary=True) +``` + +### 8.2 Define Structures + +```python +# Create a structure datablock +project.structures.create(name='lbco') + +# Set space group and unit cell +project.structures['lbco'].space_group.name_h_m = 'P m -3 m' +project.structures['lbco'].cell.length_a = 3.88 + +# Add atom sites +project.structures['lbco'].atom_sites.create( + label='La', + type_symbol='La', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.5, + occupancy=0.5, +) + +# Show as CIF +project.structures['lbco'].show_as_cif() +``` + +### 8.3 Define Experiments + +```python +# Download data and create experiment from a data file +data_path = ed.download_data(id=3, destination='data') +project.experiments.add_from_data_path( + name='hrpt', + data_path=data_path, + sample_form='powder', + beam_mode='constant wavelength', + radiation_probe='neutron', +) + +# Set instrument parameters +project.experiments['hrpt'].instrument.setup_wavelength = 1.494 +project.experiments['hrpt'].instrument.calib_twotheta_offset = 0.6 + +# Browse and select peak profile type +project.experiments['hrpt'].show_supported_peak_profile_types() +project.experiments['hrpt'].peak_profile_type = 'pseudo-voigt' + +# Set peak profile parameters +project.experiments['hrpt'].peak.broad_gauss_u = 0.1 +project.experiments['hrpt'].peak.broad_gauss_v = -0.1 + +# Browse and select background type +project.experiments['hrpt'].show_supported_background_types() +project.experiments['hrpt'].background_type = 'line-segment' + +# Add background points +project.experiments['hrpt'].background.create(id='10', x=10, y=170) +project.experiments['hrpt'].background.create(id='50', x=50, y=170) + +# Link structure to experiment +project.experiments['hrpt'].linked_phases.create(id='lbco', scale=10.0) +``` + +### 8.4 Analysis and Fitting + +```python +# Calculator is auto-resolved per experiment; override if needed +project.experiments['hrpt'].show_supported_calculator_types() +project.experiments['hrpt'].calculator_type = 'cryspy' +project.analysis.current_minimizer = 'lmfit' + +# Plot before fitting +project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) + +# Select free parameters +project.structures['lbco'].cell.length_a.free = True +project.experiments['hrpt'].linked_phases['lbco'].scale.free = True +project.experiments['hrpt'].instrument.calib_twotheta_offset.free = True +project.experiments['hrpt'].background['10'].y.free = True + +# Inspect free parameters +project.analysis.show_free_params() + +# Fit and show results +project.analysis.fit() +project.analysis.show_fit_results() + +# Plot after fitting +project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) + +# Save +project.save() +``` + +### 8.5 TOF Experiment (tutorial ed-7) + +```python +expt = ExperimentFactory.from_data_path( + name='sepd', + data_path=data_path, + beam_mode='time-of-flight', +) +expt.instrument.calib_d_to_tof_offset = 0.0 +expt.instrument.calib_d_to_tof_linear = 7476.91 +expt.peak_profile_type = 'pseudo-voigt * ikeda-carpenter' +expt.peak.broad_gauss_sigma_0 = 3.0 +``` + +### 8.6 Total Scattering / PDF (tutorial ed-12) + +```python +project.experiments.add_from_data_path( + name='xray_pdf', + data_path=data_path, + sample_form='powder', + beam_mode='constant wavelength', + radiation_probe='xray', + scattering_type='total', +) +project.experiments['xray_pdf'].peak_profile_type = 'gaussian-damped-sinc' +# Calculator is auto-resolved to 'pdffit' for total scattering experiments +``` + +--- + +## 9. Design Principles + +### 9.1 Naming and CIF Conventions + +- Follow CIF naming conventions where possible. Deviate for better API design + when necessary, but keep the spirit of CIF names. +- Reuse the concept of datablocks and categories from CIF. +- `DatablockItem` = one CIF `data_` block, `DatablockCollection` = set of + blocks. +- `CategoryItem` = one CIF category, `CategoryCollection` = CIF loop. + +### 9.2 Immutability of Experiment Type + +The experiment type (the four enum axes) can only be set at creation time. It +cannot be changed afterwards. This avoids the complexity of maintaining +different state transformations when switching between fundamentally different +experiment configurations. + +### 9.3 Category Type Switching + +In contrast to experiment type, categories that have multiple implementations +(peak profiles, backgrounds, instruments) can be switched at runtime by the +user. The API pattern uses a type property on the **experiment**, not on the +category itself: + +```python +# ✅ Correct — type property on the experiment +expt.background_type = 'chebyshev' + +# ❌ Not used — type property on the category +expt.background.type = 'chebyshev' +``` + +This makes it clear that the entire category object is being replaced and +simplifies maintenance. + +### 9.4 Switchable-Category Convention + +Categories whose concrete implementation can be swapped at runtime (background, +peak profile, etc.) are called **switchable categories**. **Every category must +be factory-based** — even if only one implementation exists today. This ensures +a uniform API, consistent discoverability, and makes adding a second +implementation trivial. + +| Facet | Naming pattern | Example | +| --------------- | -------------------------------------------- | ------------------------------------------------ | +| Current object | `` property (read-only) | `expt.background`, `expt.peak` | +| Active type tag | `_type` property (getter + setter) | `expt.background_type`, `expt.peak_profile_type` | +| Show supported | `show_supported__types()` | `expt.show_supported_background_types()` | +| Show current | `show_current__type()` | `expt.show_current_peak_profile_type()` | + +The convention applies universally: + +- **Experiment:** `calculator_type`, `background_type`, `peak_profile_type`, + `extinction_type`, `linked_crystal_type`, `excluded_regions_type`, + `linked_phases_type`, `instrument_type`, `data_type`. +- **Structure:** `cell_type`, `space_group_type`, `atom_sites_type`. +- **Analysis:** `aliases_type`, `constraints_type`, `fit_mode_type`, + `joint_fit_experiments_type`. + +**Design decisions:** + +- The **experiment owns** the `_type` setter because switching replaces the + entire category object (`self._background = BackgroundFactory.create(...)`). +- The **experiment owns** the `show_*` methods because they are one-liners that + delegate to `Factory.show_supported(...)` and can pass experiment-specific + context (e.g. `scattering_type`, `beam_mode` for peak filtering). +- Concrete category subclasses provide a public `show()` method for displaying + the current content (not on the base `CategoryItem`/`CategoryCollection`). + +### 9.5 Discoverable Supported Options + +The user can always discover what is supported for the current experiment: + +```python +expt.show_supported_peak_profile_types() +expt.show_supported_background_types() +expt.show_supported_calculator_types() +expt.show_supported_extinction_types() +expt.show_supported_linked_crystal_types() +expt.show_supported_excluded_regions_types() +expt.show_supported_linked_phases_types() +expt.show_supported_instrument_types() +expt.show_supported_data_types() +struct.show_supported_cell_types() +struct.show_supported_space_group_types() +struct.show_supported_atom_sites_types() +project.analysis.show_supported_aliases_types() +project.analysis.show_supported_constraints_types() +project.analysis.show_supported_fit_mode_types() +project.analysis.show_supported_joint_fit_experiments_types() +project.analysis.show_available_minimizers() +``` + +Available calculators are filtered by `engine_imported` (whether the library is +installed) and by the experiment's data category `calculator_support` metadata. + +### 9.6 Enums for Finite Value Sets + +Every attribute, descriptor, or configuration option that accepts a **finite, +closed set of values** must be represented by a `(str, Enum)` class. This +applies to: + +- Factory tags (§5.6) — e.g. `PeakProfileTypeEnum`, `CalculatorEnum`. +- Experiment-axis values — e.g. `SampleFormEnum`, `BeamModeEnum`. +- Category descriptors with enumerated choices — e.g. fit mode + (`FitModeEnum.SINGLE`, `FitModeEnum.JOINT`). + +The enum serves as the **single source of truth** for valid values, their +user-facing string representations, and their descriptions. Benefits: + +- **Autocomplete and typo safety** — IDEs list valid members; misspellings are + caught at assignment time. +- **Greppable** — searching for `FitModeEnum.JOINT` finds every code path that + handles joint fitting. +- **Type-safe dispatch** — `if mode == FitModeEnum.JOINT:` is checked by type + checkers; `if mode == 'joint':` is not. +- **Consistent validation** — use `MembershipValidator` with the enum members + instead of `RegexValidator` with hand-written patterns. + +**Rule:** internal code must compare against enum members, never raw strings. +User-facing setters accept either the enum member or its string value (because +`str(EnumMember) == EnumMember.value` for `(str, Enum)`), but internal dispatch +always uses the enum: + +```python +# ✅ Correct — compare with enum +if self._fit_mode.mode.value == FitModeEnum.JOINT: + +# ❌ Wrong — compare with raw string +if self._fit_mode.mode.value == 'joint': +``` + +### 9.7 Flat Category Structure — No Nested Categories + +Following CIF conventions, categories are **flat siblings** within their owner +(datablock or analysis object). A category must never be a child of another +category of a different type. Categories can reference each other via IDs, but +the ownership hierarchy is always: + +``` +Owner (DatablockItem / Analysis) +├── CategoryA (CategoryItem or CategoryCollection) +├── CategoryB (CategoryItem or CategoryCollection) +└── CategoryC (CategoryItem or CategoryCollection) +``` + +Never: + +``` +Owner +└── CategoryA + └── CategoryB ← WRONG: CategoryB is a child of CategoryA +``` + +**Example — `fit_mode` and `joint_fit_experiments`:** `fit_mode` is a +`CategoryItem` holding the active strategy (`'single'` or `'joint'`). +`joint_fit_experiments` is a separate `CategoryCollection` holding +per-experiment weights. Both are direct children of `Analysis`, not nested: + +```python +# ✅ Correct — sibling categories on Analysis +project.analysis.fit_mode.mode = 'joint' +project.analysis.joint_fit_experiments['npd'].weight = 0.7 + +# ❌ Wrong — joint_fit_experiments as a child of fit_mode +project.analysis.fit_mode.joint_fit_experiments['npd'].weight = 0.7 +``` + +In CIF output, sibling categories appear as independent blocks: + +``` +_analysis.fit_mode joint + +loop_ +_joint_fit_experiment.id +_joint_fit_experiment.weight +npd 0.7 +xrd 0.3 +``` + +--- + +## 10. Issues + +- **Open:** [`issues_open.md`](issues_open.md) — prioritised backlog. +- **Closed:** [`issues_closed.md`](issues_closed.md) — resolved items for + reference. + +When a resolution affects the architecture described above, the relevant +sections of this document are updated accordingly. diff --git a/docs/architecture/issues_closed.md b/docs/architecture/issues_closed.md new file mode 100644 index 00000000..bc8ea19b --- /dev/null +++ b/docs/architecture/issues_closed.md @@ -0,0 +1,60 @@ +# EasyDiffraction — Closed Issues + +Issues that have been fully resolved. Kept for historical reference. + +--- + +## Dirty-Flag Guard Was Disabled + +**Resolution:** added `_set_value_from_minimizer()` on `GenericDescriptorBase` +that writes `_value` directly (no validation) but sets the dirty flag on the +parent `DatablockItem`. Both `LmfitMinimizer` and `DfolsMinimizer` now use it. +The guard in `DatablockItem._update_categories()` is enabled and skips redundant +updates on the user-facing path (CIF export, plotting). During fitting the guard +is bypassed (`called_by_minimizer=True`) because experiment calculations depend +on structure parameters owned by a different `DatablockItem`. + +--- + +## Move Calculator from Global to Per-Experiment + +**Resolution:** removed the global calculator from `Analysis`. Each experiment +now owns its calculator, auto-resolved on first access from +`CalculatorFactory._default_rules` (maps `scattering_type` → default tag) and +filtered by the data category's `calculator_support` metadata (e.g. `PdCwlData` +→ `{CRYSPY}`, `TotalData` → `{PDFFIT}`). Calculator classes no longer carry +`compatibility` attributes — limitations are expressed on categories. The +experiment exposes the standard switchable-category API: `calculator` +(read-only, lazy), `calculator_type` (getter + setter), +`show_supported_calculator_types()`, `show_current_calculator_type()`. +Tutorials, tests, and docs updated. + +--- + +## Add Universal Factories for All Categories + +**Resolution:** converted every category to use the `FactoryBase` pattern. Each +former single-file category is now a package with `factory.py` (trivial +`FactoryBase` subclass), `default.py` (concrete class with `@register` + +`type_info`), and `__init__.py` (re-exports preserving import compatibility). + +Experiment categories: `Extinction` → `ShelxExtinction` / `ExtinctionFactory` +(tag `shelx`), `LinkedCrystal` / `LinkedCrystalFactory` (tag `default`), +`ExcludedRegions` / `ExcludedRegionsFactory`, `LinkedPhases` / +`LinkedPhasesFactory`, `ExperimentType` / `ExperimentTypeFactory`. + +Structure categories: `Cell` / `CellFactory`, `SpaceGroup` / +`SpaceGroupFactory`, `AtomSites` / `AtomSitesFactory`. + +Analysis categories: `Aliases` / `AliasesFactory`, `Constraints` / +`ConstraintsFactory`, `JointFitExperiments` / `JointFitExperimentsFactory`. + +`ShelxExtinction` and `LinkedCrystal` get the full switchable-category API on +`ScExperimentBase` (`extinction_type`, `linked_crystal_type` getter+setter, +`show_supported_*_types()`, `show_current_*_type()`). `ExcludedRegions` and +`LinkedPhases` get the same API on `PdExperimentBase`. `Cell`, `SpaceGroup`, and +`AtomSites` get it on `Structure`. `Aliases` and `Constraints` get it on +`Analysis`. Architecture §3.3, §5.5, §5.7, §9.4, §9.5 updated. Copilot +instructions updated with universal switchable-category scope and +architecture-first workflow rule. Unit tests extended with factory tests for +extinction and linked-crystal. diff --git a/docs/architecture/issues_open.md b/docs/architecture/issues_open.md new file mode 100644 index 00000000..d8bd37a2 --- /dev/null +++ b/docs/architecture/issues_open.md @@ -0,0 +1,339 @@ +# EasyDiffraction — Open Issues + +Prioritised list of issues, improvements, and design questions to address. Items +are ordered by a combination of user impact, blocking potential, and +implementation readiness. When an item is fully implemented, remove it from this +file and update `architecture.md` if needed. + +**Legend:** 🔴 High · 🟡 Medium · 🟢 Low + +--- + +## 1. 🔴 Implement `Project.load()` + +**Type:** Completeness + +`save()` serialises all components to CIF files but `load()` is a stub that +raises `NotImplementedError`. Users cannot round-trip a project. + +**Why first:** this is the highest-severity gap. Without it the save +functionality is only half useful — CIF files are written but cannot be read +back. Tutorials that demonstrate save/load are blocked. + +**Fix:** implement `load()` that reads CIF files from the project directory and +reconstructs structures, experiments, and analysis settings. + +**Depends on:** nothing (standalone). + +--- + +## 2. 🟡 Restore Minimiser Variant Support + +**Type:** Feature loss + Design limitation + +After the `FactoryBase` migration only `'lmfit'` and `'dfols'` remain as +registered tags. The ability to select a specific lmfit algorithm (e.g. +`'lmfit (leastsq)'`, `'lmfit (least_squares)'`) raises a `ValueError`. + +The root cause is that `FactoryBase` assumes one class ↔ one tag; registering +the same class twice with different constructor arguments is not supported. + +**Fix:** decide on an approach (thin subclasses, extended registry, or two-level +selection) and implement. Thin subclasses is the quickest. + +**Planned tags:** + +| Tag | Description | +| ----------------------- | ------------------------------------------------------------------------ | +| `lmfit` | LMFIT library using the default Levenberg-Marquardt least squares method | +| `lmfit (leastsq)` | LMFIT library with Levenberg-Marquardt least squares method | +| `lmfit (least_squares)` | LMFIT library with SciPy's trust region reflective algorithm | +| `dfols` | DFO-LS library for derivative-free least-squares optimization | + +**Trade-offs:** + +| Approach | Pros | Cons | +| -------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| **A. Thin subclasses** (one per variant) | Works today; each variant gets full metadata; no `FactoryBase` changes | Class proliferation; boilerplate | +| **B. Extend registry to store `(class, kwargs)` tuples** | No extra classes; factory handles variants natively | `_supported_map` changes shape; `TypeInfo` moves from class attribute to registration-time data | +| **C. Two-level selection** (`engine` + `algorithm`) | Clean separation; engine maps to class, algorithm is a constructor arg | More complex API (`current_minimizer = ('lmfit', 'least_squares')`); needs new `FactoryBase` protocol | + +**Depends on:** nothing (standalone, but should be decided before more factories +adopt variants). + +--- + +## 3. 🟡 Rebuild Joint-Fit Weights on Every Fit + +**Type:** Fragility + +`joint_fit_experiments` is created once when `fit_mode` becomes `'joint'`. If +experiments are added, removed, or renamed afterwards, the weight collection is +stale. Joint fitting can fail with missing keys or run with incorrect weights. + +**Fix:** rebuild or validate `joint_fit_experiments` at the start of every joint +fit. At minimum, `fit()` should assert that the weight keys exactly match +`project.experiments.names`. + +**Depends on:** nothing. + +--- + +## 4. 🔴 Refresh Constraint State Before Automatic Updates and Fitting + +**Type:** Correctness + +`ConstraintsHandler` is only synchronised from `analysis.aliases` and +`analysis.constraints` when the user explicitly calls +`project.analysis.apply_constraints()`. The normal fit / serialisation path +calls `constraints_handler.apply()` directly, so newly added or edited aliases +and constraints can be ignored until that manual sync step happens. + +**Why high:** this produces silently incorrect results. A user can define +constraints, run a fit, and believe they were applied when the active singleton +still contains stale state from a previous run or no state at all. + +**Fix:** before any automatic constraint application, always refresh the +singleton from the current `Aliases` and `Constraints` collections. The sync +should happen inside `Analysis._update_categories()` or inside the constraints +category itself, not only in a user-facing helper method. + +**Depends on:** nothing. + +--- + +## 5. 🟡 Make `Analysis` a `DatablockItem` + +**Type:** Consistency + +`Analysis` owns categories (`Aliases`, `Constraints`, `JointFitExperiments`) but +does not extend `DatablockItem`. Its ad-hoc `_update_categories()` iterates over +a hard-coded list and does not participate in standard category discovery, +parameter enumeration, or CIF serialisation. + +**Fix:** make `Analysis` extend `DatablockItem`, or extract a shared +`_update_categories()` protocol. + +**Depends on:** benefits from issue 1 (load/save) being designed first. + +--- + +## 6. 🔴 Restrict `data_type` Switching to Compatible Types and Preserve Data Safety + +**Type:** Correctness + Data safety + +`Experiment.data_type` currently validates against all registered data tags +rather than only those compatible with the experiment's `sample_form` / +`scattering_type` / `beam_mode`. This allows users to switch an experiment to an +incompatible data collection class. The setter also replaces the existing data +object with a fresh empty instance, discarding loaded data without warning. + +**Why high:** the current API can create internally inconsistent experiments and +silently lose measured data, which is especially dangerous for notebook and +tutorial workflows. + +**Fix:** filter supported data types through `DataFactory.supported_for(...)` +using the current experiment context, and warn or block when a switch would +discard existing data. If runtime data-type switching is not a real user need, +consider making `data` effectively fixed after experiment creation. + +**Depends on:** nothing. + +--- + +## 7. 🟡 Eliminate Dummy `Experiments` Wrapper in Single-Fit Mode + +**Type:** Fragility + +Single-fit mode creates a throw-away `Experiments` collection per experiment, +manually forces `_parent` via `object.__setattr__`, and passes it to `Fitter`. +This bypasses `GuardedBase` parent tracking and is fragile. + +**Fix:** make `Fitter.fit()` accept a list of experiment objects (or a single +experiment) instead of requiring an `Experiments` collection. Or add a +`fit_single(experiment)` method. + +**Depends on:** nothing, but simpler after issue 5 (Analysis refactor) clarifies +the fitting orchestration. + +--- + +## 8. 🟡 Add Explicit `create()` Signatures on Collections + +**Type:** API safety + +`CategoryCollection.create(**kwargs)` accepts arbitrary keyword arguments and +applies them via `setattr`. Typos are silently dropped (GuardedBase logs a +warning but does not raise), so items are created with incorrect defaults. + +**Fix:** concrete collection subclasses (e.g. `AtomSites`, `Background`) should +override `create()` with explicit parameters for IDE autocomplete and typo +detection. The base `create(**kwargs)` remains as an internal implementation +detail. + +**Depends on:** nothing. + +--- + +## 9. 🟢 Add Future Enum Extensions + +**Type:** Design improvement + +The four current experiment axes will be extended with at least two more: + +| New axis | Options | Enum (proposed) | +| ------------------- | ---------------------- | ------------------------ | +| Data dimensionality | 1D, 2D | `DataDimensionalityEnum` | +| Beam polarisation | unpolarised, polarised | `PolarisationEnum` | + +These should follow the same `str, Enum` pattern and integrate into +`Compatibility` (new `FrozenSet` fields), `_default_rules`, and `ExperimentType` +(new `StringDescriptor`s with `MembershipValidator`s). + +**Migration path:** existing `Compatibility` objects that don't specify the new +fields use `frozenset()` (empty = "any"), so all existing classes remain +compatible without changes. + +**Depends on:** nothing. + +--- + +## 10. 🟢 Unify Project-Level Update Orchestration + +**Type:** Maintainability + +`Project._update_categories(expt_name)` hard-codes the update order (structures +→ analysis → one experiment). The `_update_priority` system exists on categories +but is not used across datablocks. The `expt_name` parameter means only one +experiment is updated per call, inconsistent with joint-fit workflows. + +**Fix:** consider a project-level `_update_priority` on datablocks, or at +minimum document the required update order. For joint fitting, all experiments +should be updateable in a single call. + +**Depends on:** benefits from issue 5 (Analysis as DatablockItem) and issue 7 +(fitter refactor). + +--- + +## 11. 🟢 Document Category `_update` Contract + +**Type:** Maintainability + +`_update()` is an optional override with a no-op default. A clearer contract +would help contributors: + +- **Active categories** (those that compute something, e.g. `Background`, + `Data`) should have an explicit `_update()` implementation. +- **Passive categories** (those that only store parameters, e.g. `Cell`, + `SpaceGroup`) keep the no-op default. + +The distinction is already implicit in the code; making it explicit in +documentation (and possibly via a naming convention or flag) would reduce +confusion for new contributors. + +**Depends on:** nothing. + +--- + +## 12. 🟢 Add CIF Round-Trip Integration Test + +**Type:** Quality + +Ensuring every parameter survives a `save()` → `load()` cycle is critical for +reproducibility. A systematic integration test that creates a project, populates +all categories, saves, reloads, and compares all parameter values would +strengthen confidence in the serialisation layer. + +**Depends on:** issue 1 (`Project.load()` implementation). + +--- + +## 13. 🟢 Suppress Redundant Dirty-Flag Sets in Symmetry Constraints + +**Type:** Performance + +Symmetry constraint application (cell metric, atomic coordinates, ADPs) goes +through the public `value` setter for each parameter, setting the dirty flag +repeatedly during what is logically a single batch operation. + +No correctness issue — the dirty-flag guard handles this correctly. The +redundant sets are a minor inefficiency that only matters if profiling shows it +is a bottleneck. + +**Fix:** introduce a private `_set_value_no_notify()` method on +`GenericDescriptorBase` for internal batch operations, or a context manager / +flag on the owning datablock to suppress notifications during a batch. + +**Depends on:** nothing, but low priority. + +--- + +## 14. 🟢 Finer-Grained Parameter Change Tracking + +**Type:** Performance + +The current dirty-flag approach (`_need_categories_update` on `DatablockItem`) +triggers a full update of all categories when any parameter changes. This is +simple and correct. If performance becomes a concern with many categories, a +more granular approach could track which specific categories are dirty. Only +implement when profiling proves it is needed. + +**Depends on:** nothing, but low priority. + +--- + +## 15. 🟡 Validate Joint-Fit Weights Before Residual Normalisation + +**Type:** Correctness + +Joint-fit weights currently allow invalid numeric values such as negatives or an +all-zero set. The residual code then normalises by the total weight and applies +`sqrt(weight)`, which can produce division-by-zero or `nan` residuals. + +**Fix:** require weights to be strictly positive, or at minimum validate that +all weights are non-negative and their total is greater than zero before +normalisation. This should fail with a clear user-facing error instead of +letting invalid floating-point values propagate into the minimiser. + +**Depends on:** related to issue 3, but independent. + +--- + +## 16. 🟡 Persist Per-Experiment `calculator_type` + +**Type:** Completeness + +The current architecture moved calculator selection to the experiment level via +`calculator_type`, but this selection is not written to CIF during `save()` / +`show_as_cif()`. Reloading or exporting a project therefore loses explicit +calculator choices and falls back to auto-resolution. + +**Fix:** serialise `calculator_type` as part of the experiment or analysis +state, and make sure `load()` restores it. The saved project should represent +the exact active calculator configuration, not just a re-derivable default. + +**Depends on:** issue 1 (`Project.load()` implementation). + +--- + +## Summary + +| # | Issue | Severity | Type | +| --- | ------------------------------------------ | -------- | ----------------------- | +| 1 | Implement `Project.load()` | 🔴 High | Completeness | +| 2 | Restore minimiser variants | 🟡 Med | Feature loss | +| 3 | Rebuild joint-fit weights | 🟡 Med | Fragility | +| 4 | Refresh constraint state before auto-apply | 🔴 High | Correctness | +| 5 | `Analysis` as `DatablockItem` | 🟡 Med | Consistency | +| 6 | Restrict `data_type` switching | 🔴 High | Correctness/Data safety | +| 7 | Eliminate dummy `Experiments` | 🟡 Med | Fragility | +| 8 | Explicit `create()` signatures | 🟡 Med | API safety | +| 9 | Future enum extensions | 🟢 Low | Design | +| 10 | Unify update orchestration | 🟢 Low | Maintainability | +| 11 | Document `_update` contract | 🟢 Low | Maintainability | +| 12 | CIF round-trip integration test | 🟢 Low | Quality | +| 13 | Suppress redundant dirty-flag sets | 🟢 Low | Performance | +| 14 | Finer-grained change tracking | 🟢 Low | Performance | +| 15 | Validate joint-fit weights | 🟡 Med | Correctness | +| 16 | Persist per-experiment `calculator_type` | 🟡 Med | Completeness | diff --git a/docs/architecture/package-structure-full.md b/docs/architecture/package-structure-full.md index e24eadab..74662449 100644 --- a/docs/architecture/package-structure-full.md +++ b/docs/architecture/package-structure-full.md @@ -67,37 +67,176 @@ │ │ └── 🏷️ class GuardedBase │ ├── 📄 identity.py │ │ └── 🏷️ class Identity -│ ├── 📄 parameters.py -│ │ ├── 🏷️ class GenericDescriptorBase -│ │ ├── 🏷️ class GenericStringDescriptor -│ │ ├── 🏷️ class GenericNumericDescriptor -│ │ ├── 🏷️ class GenericParameter -│ │ ├── 🏷️ class StringDescriptor -│ │ ├── 🏷️ class NumericDescriptor -│ │ └── 🏷️ class Parameter -│ ├── 📄 singletons.py +│ ├── 📄 metadata.py +│ │ ├── 🏷️ class TypeInfo +│ │ ├── 🏷️ class Compatibility +│ │ └── 🏷️ class CalculatorSupport +│ ├── 📄 singleton.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 +│ ├── 📄 validation.py +│ │ ├── 🏷️ class DataTypeHints +│ │ ├── 🏷️ class DataTypes +│ │ ├── 🏷️ class ValidationStage +│ │ ├── 🏷️ class ValidatorBase +│ │ ├── 🏷️ class TypeValidator +│ │ ├── 🏷️ class RangeValidator +│ │ ├── 🏷️ class MembershipValidator +│ │ ├── 🏷️ class RegexValidator +│ │ └── 🏷️ class AttributeSpec +│ └── 📄 variable.py +│ ├── 🏷️ class GenericDescriptorBase +│ ├── 🏷️ class GenericStringDescriptor +│ ├── 🏷️ class GenericNumericDescriptor +│ ├── 🏷️ class GenericParameter +│ ├── 🏷️ class StringDescriptor +│ ├── 🏷️ class NumericDescriptor +│ └── 🏷️ class Parameter ├── 📁 crystallography │ ├── 📄 __init__.py │ ├── 📄 crystallography.py │ └── 📄 space_groups.py +├── 📁 datablocks +│ ├── 📁 experiment +│ │ ├── 📁 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 +│ │ │ ├── 📁 data +│ │ │ │ ├── 📄 __init__.py +│ │ │ │ ├── 📄 bragg_pd.py +│ │ │ │ │ ├── 🏷️ class PdDataPointBaseMixin +│ │ │ │ │ ├── 🏷️ class PdCwlDataPointMixin +│ │ │ │ │ ├── 🏷️ class PdTofDataPointMixin +│ │ │ │ │ ├── 🏷️ class PdCwlDataPoint +│ │ │ │ │ ├── 🏷️ class PdTofDataPoint +│ │ │ │ │ ├── 🏷️ class PdDataBase +│ │ │ │ │ ├── 🏷️ class PdCwlData +│ │ │ │ │ └── 🏷️ class PdTofData +│ │ │ │ ├── 📄 bragg_sc.py +│ │ │ │ │ ├── 🏷️ class Refln +│ │ │ │ │ └── 🏷️ class ReflnData +│ │ │ │ ├── 📄 factory.py +│ │ │ │ │ └── 🏷️ class DataFactory +│ │ │ │ └── 📄 total_pd.py +│ │ │ │ ├── 🏷️ class TotalDataPoint +│ │ │ │ ├── 🏷️ class TotalDataBase +│ │ │ │ └── 🏷️ class TotalData +│ │ │ ├── 📁 instrument +│ │ │ │ ├── 📄 __init__.py +│ │ │ │ ├── 📄 base.py +│ │ │ │ │ └── 🏷️ class InstrumentBase +│ │ │ │ ├── 📄 cwl.py +│ │ │ │ │ ├── 🏷️ class CwlInstrumentBase +│ │ │ │ │ ├── 🏷️ class CwlScInstrument +│ │ │ │ │ └── 🏷️ class CwlPdInstrument +│ │ │ │ ├── 📄 factory.py +│ │ │ │ │ └── 🏷️ class InstrumentFactory +│ │ │ │ └── 📄 tof.py +│ │ │ │ ├── 🏷️ class TofScInstrument +│ │ │ │ └── 🏷️ class TofPdInstrument +│ │ │ ├── 📁 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 +│ │ │ ├── 📄 extinction.py +│ │ │ │ └── 🏷️ class Extinction +│ │ │ ├── 📄 linked_crystal.py +│ │ │ │ └── 🏷️ class LinkedCrystal +│ │ │ └── 📄 linked_phases.py +│ │ │ ├── 🏷️ class LinkedPhase +│ │ │ └── 🏷️ class LinkedPhases +│ │ ├── 📁 item +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ │ ├── 🏷️ class ExperimentBase +│ │ │ │ ├── 🏷️ class ScExperimentBase +│ │ │ │ └── 🏷️ class PdExperimentBase +│ │ │ ├── 📄 bragg_pd.py +│ │ │ │ └── 🏷️ class BraggPdExperiment +│ │ │ ├── 📄 bragg_sc.py +│ │ │ │ ├── 🏷️ class CwlScExperiment +│ │ │ │ └── 🏷️ class TofScExperiment +│ │ │ ├── 📄 enums.py +│ │ │ │ ├── 🏷️ class SampleFormEnum +│ │ │ │ ├── 🏷️ class ScatteringTypeEnum +│ │ │ │ ├── 🏷️ class RadiationProbeEnum +│ │ │ │ ├── 🏷️ class BeamModeEnum +│ │ │ │ ├── 🏷️ class CalculatorEnum +│ │ │ │ └── 🏷️ class PeakProfileTypeEnum +│ │ │ ├── 📄 factory.py +│ │ │ │ └── 🏷️ class ExperimentFactory +│ │ │ └── 📄 total_pd.py +│ │ │ └── 🏷️ class TotalPdExperiment +│ │ ├── 📄 __init__.py +│ │ └── 📄 collection.py +│ │ └── 🏷️ class Experiments +│ ├── 📁 structure +│ │ ├── 📁 categories +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 atom_sites.py +│ │ │ │ ├── 🏷️ class AtomSite +│ │ │ │ └── 🏷️ class AtomSites +│ │ │ ├── 📄 cell.py +│ │ │ │ └── 🏷️ class Cell +│ │ │ └── 📄 space_group.py +│ │ │ └── 🏷️ class SpaceGroup +│ │ ├── 📁 item +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ │ └── 🏷️ class Structure +│ │ │ └── 📄 factory.py +│ │ │ └── 🏷️ class StructureFactory +│ │ ├── 📄 __init__.py +│ │ └── 📄 collection.py +│ │ └── 🏷️ class Structures +│ └── 📄 __init__.py ├── 📁 display │ ├── 📁 plotters │ │ ├── 📄 __init__.py │ │ ├── 📄 ascii.py │ │ │ └── 🏷️ class AsciiPlotter │ │ ├── 📄 base.py +│ │ │ ├── 🏷️ class XAxisType │ │ │ └── 🏷️ class PlotterBase │ │ └── 📄 plotly.py │ │ └── 🏷️ class PlotlyPlotter @@ -123,108 +262,6 @@ │ │ └── 🏷️ class TableRendererFactory │ └── 📄 utils.py │ └── 🏷️ class JupyterScrollManager -├── 📁 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 -│ │ ├── 📁 data -│ │ │ ├── 📄 bragg_pd.py -│ │ │ │ ├── 🏷️ class PdDataPointBaseMixin -│ │ │ │ ├── 🏷️ class PdCwlDataPointMixin -│ │ │ │ ├── 🏷️ class PdTofDataPointMixin -│ │ │ │ ├── 🏷️ class PdCwlDataPoint -│ │ │ │ ├── 🏷️ class PdTofDataPoint -│ │ │ │ ├── 🏷️ class PdDataBase -│ │ │ │ ├── 🏷️ class PdCwlData -│ │ │ │ └── 🏷️ class PdTofData -│ │ │ ├── 📄 bragg_sc.py -│ │ │ │ └── 🏷️ class Refln -│ │ │ ├── 📄 factory.py -│ │ │ │ └── 🏷️ class DataFactory -│ │ │ └── 📄 total.py -│ │ │ ├── 🏷️ class TotalDataPoint -│ │ │ ├── 🏷️ class TotalDataBase -│ │ │ └── 🏷️ class TotalData -│ │ ├── 📁 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 -│ ├── 📁 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 │ │ ├── 📄 __init__.py @@ -239,30 +276,17 @@ │ │ └── 🏷️ 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 +│ ├── 📁 _vendored +│ │ ├── 📁 jupyter_dark_detect +│ │ │ ├── 📄 __init__.py +│ │ │ └── 📄 detector.py +│ │ ├── 📄 __init__.py +│ │ └── 📄 theme_detect.py │ ├── 📄 __init__.py │ ├── 📄 environment.py │ ├── 📄 logging.py diff --git a/docs/architecture/package-structure-short.md b/docs/architecture/package-structure-short.md index 7f9d5af0..efe89066 100644 --- a/docs/architecture/package-structure-short.md +++ b/docs/architecture/package-structure-short.md @@ -38,13 +38,75 @@ │ ├── 📄 factory.py │ ├── 📄 guard.py │ ├── 📄 identity.py -│ ├── 📄 parameters.py -│ ├── 📄 singletons.py -│ └── 📄 validation.py +│ ├── 📄 metadata.py +│ ├── 📄 singleton.py +│ ├── 📄 validation.py +│ └── 📄 variable.py ├── 📁 crystallography │ ├── 📄 __init__.py │ ├── 📄 crystallography.py │ └── 📄 space_groups.py +├── 📁 datablocks +│ ├── 📁 experiment +│ │ ├── 📁 categories +│ │ │ ├── 📁 background +│ │ │ │ ├── 📄 __init__.py +│ │ │ │ ├── 📄 base.py +│ │ │ │ ├── 📄 chebyshev.py +│ │ │ │ ├── 📄 enums.py +│ │ │ │ ├── 📄 factory.py +│ │ │ │ └── 📄 line_segment.py +│ │ │ ├── 📁 data +│ │ │ │ ├── 📄 __init__.py +│ │ │ │ ├── 📄 bragg_pd.py +│ │ │ │ ├── 📄 bragg_sc.py +│ │ │ │ ├── 📄 factory.py +│ │ │ │ └── 📄 total_pd.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 +│ │ │ ├── 📄 extinction.py +│ │ │ ├── 📄 linked_crystal.py +│ │ │ └── 📄 linked_phases.py +│ │ ├── 📁 item +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ ├── 📄 bragg_pd.py +│ │ │ ├── 📄 bragg_sc.py +│ │ │ ├── 📄 enums.py +│ │ │ ├── 📄 factory.py +│ │ │ └── 📄 total_pd.py +│ │ ├── 📄 __init__.py +│ │ └── 📄 collection.py +│ ├── 📁 structure +│ │ ├── 📁 categories +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 atom_sites.py +│ │ │ ├── 📄 cell.py +│ │ │ └── 📄 space_group.py +│ │ ├── 📁 item +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ └── 📄 factory.py +│ │ ├── 📄 __init__.py +│ │ └── 📄 collection.py +│ └── 📄 __init__.py ├── 📁 display │ ├── 📁 plotters │ │ ├── 📄 __init__.py @@ -61,51 +123,6 @@ │ ├── 📄 plotting.py │ ├── 📄 tables.py │ └── 📄 utils.py -├── 📁 experiments -│ ├── 📁 categories -│ │ ├── 📁 background -│ │ │ ├── 📄 __init__.py -│ │ │ ├── 📄 base.py -│ │ │ ├── 📄 chebyshev.py -│ │ │ ├── 📄 enums.py -│ │ │ ├── 📄 factory.py -│ │ │ └── 📄 line_segment.py -│ │ ├── 📁 data -│ │ │ ├── 📄 bragg_pd.py -│ │ │ ├── 📄 bragg_sc.py -│ │ │ ├── 📄 factory.py -│ │ │ └── 📄 total.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 -│ ├── 📁 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 │ │ ├── 📄 __init__.py @@ -117,22 +134,16 @@ │ ├── 📄 __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 +│ ├── 📁 _vendored +│ │ ├── 📁 jupyter_dark_detect +│ │ │ ├── 📄 __init__.py +│ │ │ └── 📄 detector.py +│ │ ├── 📄 __init__.py +│ │ └── 📄 theme_detect.py │ ├── 📄 __init__.py │ ├── 📄 environment.py │ ├── 📄 logging.py diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index cf1795b4..467bfec8 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -11,7 +11,7 @@ repo_url: https://github.com/easyscience/diffraction-lib/ edit_uri: edit/develop/docs/ # Copyright -copyright: © 2025 EasyDiffraction +copyright: © 2026 EasyDiffraction # Extra icons in the bottom right corner extra: @@ -62,7 +62,7 @@ nav: - Analysis Workflow: - Analysis Workflow: user-guide/analysis-workflow/index.md - Project: user-guide/analysis-workflow/project.md - - Sample Model: user-guide/analysis-workflow/model.md + - Structure: user-guide/analysis-workflow/model.md - Experiment: user-guide/analysis-workflow/experiment.md - Analysis: user-guide/analysis-workflow/analysis.md - Summary: user-guide/analysis-workflow/summary.md @@ -71,8 +71,7 @@ nav: - Getting Started: - LBCO quick CIF: tutorials/ed-1.ipynb - LBCO quick code: tutorials/ed-2.ipynb - - LBCO basic: tutorials/ed-3.ipynb - - PbSO4 advanced: tutorials/ed-4.ipynb + - LBCO complete: tutorials/ed-3.ipynb - Powder Diffraction: - Co2SiO4 pd-neut-cwl: tutorials/ed-5.ipynb - HS pd-neut-cwl: tutorials/ed-6.ipynb @@ -86,17 +85,23 @@ nav: - Ni pd-neut-cwl: tutorials/ed-10.ipynb - Si pd-neut-tof: tutorials/ed-11.ipynb - NaCl pd-xray: tutorials/ed-12.ipynb + - Multiple Data Blocks: + - PbSO4 NPD+XRD: tutorials/ed-4.ipynb + - LBCO+Si McStas: tutorials/ed-9.ipynb + - Si Bragg+PDF: tutorials/ed-16.ipynb - Workshops & Schools: - - 2025 DMSC: tutorials/ed-13.ipynb + - DMSC Summer School: tutorials/ed-13.ipynb - API Reference: - API Reference: api-reference/index.md - analysis: api-reference/analysis.md - core: api-reference/core.md - crystallography: api-reference/crystallography.md + - datablocks: + - experiment: api-reference/datablocks/experiment.md + - structure: api-reference/datablocks/structure.md - display: api-reference/display.md - - experiments: api-reference/experiments.md + - experiments: api-reference/experiment.md - io: api-reference/io.md - project: api-reference/project.md - - sample_models: api-reference/sample_models.md - summary: api-reference/summary.md - utils: api-reference/utils.md diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index 154fd5c6..a7c2cc37 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -20,27 +20,22 @@ The tutorials are organized into the following categories. - [LBCO `quick` CIF](ed-1.ipynb) – A minimal example intended as a quick reference for users already familiar with the EasyDiffraction API or who want to see how Rietveld refinement of the La0.5Ba0.5CoO3 crystal structure can be - performed when both the sample model and experiment are loaded from CIF files. + performed when both the structure and experiment are loaded from CIF files. Data collected from constant wavelength neutron powder diffraction at HRPT at PSI. - [LBCO `quick` `code`](ed-2.ipynb) – A minimal example intended as a quick reference for users already familiar with the EasyDiffraction API or who want - to see an example refinement when both the sample model and experiment are + to see an example refinement when both the structure and experiment are defined directly in code. This tutorial covers a Rietveld refinement of the La0.5Ba0.5CoO3 crystal structure using constant wavelength neutron powder diffraction data from HRPT at PSI. -- [LBCO `basic`](ed-3.ipynb) – Demonstrates the use of the EasyDiffraction API - in a simplified, user-friendly manner that closely follows the GUI workflow - for a Rietveld refinement of the La0.5Ba0.5CoO3 crystal structure using - constant wavelength neutron powder diffraction data from HRPT at PSI. This - tutorial provides a full explanation of the workflow with detailed comments - and descriptions of every step, making it suitable for users who are new to - EasyDiffraction or those who prefer a more guided approach. -- [PbSO4 `advanced`](ed-4.ipynb) – Demonstrates a more flexible and advanced - approach to using the EasyDiffraction library, intended for users who are more - comfortable with Python programming. This tutorial covers a Rietveld - refinement of the PbSO4 crystal structure based on the joint fit of both X-ray - and neutron diffraction data. +- [LBCO `complete`](ed-3.ipynb) – Demonstrates the use of the EasyDiffraction + API in a simplified, user-friendly manner that closely follows the GUI + workflow for a Rietveld refinement of the La0.5Ba0.5CoO3 crystal structure + using constant wavelength neutron powder diffraction data from HRPT at PSI. + This tutorial provides a full explanation of the workflow with detailed + comments and descriptions of every step, making it suitable for users who are + new to EasyDiffraction or those who prefer a more guided approach. ## Powder Diffraction @@ -56,10 +51,6 @@ The tutorials are organized into the following categories. - [NCAF `pd-neut-tof`](ed-8.ipynb) – Demonstrates a Rietveld refinement of the Na2Ca3Al2F14 crystal structure using two time-of-flight neutron powder diffraction datasets (from two detector banks) of the WISH instrument at ISIS. -- [LBCO+Si McStas](ed-9.ipynb) – Demonstrates a Rietveld refinement of the - La0.5Ba0.5CoO3 crystal structure with a small amount of Si impurity as a - secondary phase using time-of-flight neutron powder diffraction data simulated - with McStas. ## Single Crystal Diffraction @@ -80,9 +71,20 @@ The tutorials are organized into the following categories. - [NaCl `pd-xray`](ed-12.ipynb) – Demonstrates a PDF analysis of NaCl using data collected from an X-ray powder diffraction experiment. +## Multi-Structure & Multi-Experiment Refinement + +- [PbSO4 NPD+XRD](ed-4.ipynb) – Joint fit of PbSO4 using X-ray and neutron + constant wavelength powder diffraction data. +- [LBCO+Si McStas](ed-9.ipynb) – Multi-phase Rietveld refinement of + La0.5Ba0.5CoO3 with Si impurity using time-of-flight neutron data simulated + with McStas. +- [Si Bragg+PDF](ed-16.ipynb) – Joint refinement of Si combining Bragg + diffraction (SEPD) and pair distribution function (NOMAD) analysis. A single + shared structure is refined simultaneously against both datasets. + ## Workshops & Schools -- [2025 DMSC](ed-13.ipynb) – A workshop tutorial that demonstrates a Rietveld - refinement of the La0.5Ba0.5CoO3 crystal structure using time-of-flight - neutron powder diffraction data simulated with McStas. This tutorial is - designed for the ESS DMSC Summer School 2025. +- [DMSC Summer School](ed-13.ipynb) – A workshop tutorial that demonstrates a + Rietveld refinement of the La0.5Ba0.5CoO3 crystal structure using + time-of-flight neutron powder diffraction data simulated with McStas. This + tutorial is designed for the ESS DMSC Summer School. diff --git a/docs/user-guide/analysis-workflow/analysis.md b/docs/user-guide/analysis-workflow/analysis.md index d22d19f1..73086b3b 100644 --- a/docs/user-guide/analysis-workflow/analysis.md +++ b/docs/user-guide/analysis-workflow/analysis.md @@ -20,7 +20,7 @@ EasyDiffraction relies on third-party crystallographic libraries, referred to as **calculation engines** or just **calculators**, to perform the calculations. The calculation engines are used to calculate the diffraction pattern for the -defined model of the studied sample using the instrumental and other required +defined model of the studied structure using the instrumental and other required experiment-related parameters, such as the wavelength, resolution, etc. You do not necessarily need the measured data to perform the calculations, but @@ -53,25 +53,26 @@ calculating the pair distribution function (PDF) from crystallographic models. ### Set Calculator -To show the supported calculation engines: +The calculator is automatically selected based on the experiment type (e.g., +`cryspy` for Bragg diffraction, `pdffit` for total scattering). To show the +supported calculation engines for a specific experiment: ```python -project.analysis.show_supported_calculators() +project.experiments['hrpt'].show_supported_calculator_types() ``` The example of the output is: -Supported calculators +Supported calculator types -| Calculator | Description | -| ---------- | ----------------------------------------------------------- | -| cryspy | CrysPy library for crystallographic calculations | -| pdffit | PDFfit2 library for pair distribution function calculations | +| Calculator | Description | +| ---------- | ------------------------------------------------ | +| cryspy | CrysPy library for crystallographic calculations | -To select the desired calculation engine, e.g., 'cryspy': +To explicitly select a calculation engine for an experiment: ```python -project.analysis.current_calculator = 'cryspy' +project.experiments['hrpt'].calculator_type = 'cryspy' ``` ## Minimization / Optimization @@ -136,13 +137,13 @@ Supported minimizers | --------------------- | ------------------------------------------------------------------------ | | lmfit | LMFIT library using the default Levenberg-Marquardt least squares method | | lmfit (leastsq) | LMFIT library with Levenberg-Marquardt least squares method | -| lmfit (least_squares) | LMFIT library with SciPy’s trust region reflective algorithm | +| lmfit (least_squares) | LMFIT library with SciPy's trust region reflective algorithm | | dfols | DFO-LS library for derivative-free least-squares optimization | -To select the desired calculation engine, e.g., 'lmfit (least_squares)': +To select the desired minimizer, e.g., 'lmfit': ```python -project.analysis.current_minimizer = 'lmfit (leastsq)' +project.analysis.current_minimizer = 'lmfit' ``` ### Fit Mode @@ -151,37 +152,28 @@ In EasyDiffraction, you can set the **fit mode** to control how the refinement process is performed. The fit mode determines whether the refinement is performed independently for each experiment or jointly across all experiments. -To show the supported fit modes: +The supported fit modes are: -```python -project.analysis.show_supported_fit_modes() -``` - -An example of supported fit modes is: - -Supported fit modes - -| Strategy | Description | -| -------- | ------------------------------------------------------------------- | -| single | Independent fitting of each experiment; no shared parameters | -| joint | Simultaneous fitting of all experiments; some parameters are shared | +| Mode | Description | +| ------ | ------------------------------------------------------------------- | +| single | Independent fitting of each experiment; no shared parameters | +| joint | Simultaneous fitting of all experiments; some parameters are shared | -You can set the fit mode using the `set_fit_mode` method of the `analysis` -object: +You can set the fit mode on the `analysis` object: ```python -project.analysis.fit_mode = 'joint' +project.analysis.fit_mode.mode = 'joint' ``` -To check the current fit mode, you can use the `show_current_fit_mode` method: +To check the current fit mode: ```python -project.analysis.show_current_fit_mode() +print(project.analysis.fit_mode.mode.value) ``` ### Perform Fit -Refining the sample model and experiment parameters against measured data is +Refining the structure and experiment parameters against measured data is usually divided into several steps, where each step involves adding or removing parameters to be refined, calculating the model data, and comparing it to the experimental data as shown in the diagram above. @@ -193,8 +185,8 @@ during the refinement process. Here is an example of how to set parameters to be refined: ```python -# Set sample model parameters to be refined. -project.sample_models['lbco'].cell.length_a.free = True +# Set structure parameters to be refined. +project.structures['lbco'].cell.length_a.free = True # Set experiment parameters to be refined. project.experiments['hrpt'].linked_phases['lbco'].scale.free = True @@ -265,27 +257,27 @@ to constrain. This can be done using the `add` method of the `aliases` object. Aliases are used to reference parameters in a more readable way, making it easier to manage constraints. -An example of setting aliases for parameters in a sample model: +An example of setting aliases for parameters in a structure: ```python # Set aliases for the atomic displacement parameters -project.analysis.aliases.add( +project.analysis.aliases.create( label='biso_La', - param_uid=project.sample_models['lbco'].atom_sites['La'].b_iso.uid, + param_uid=project.structures['lbco'].atom_sites['La'].b_iso.uid, ) -project.analysis.aliases.add( +project.analysis.aliases.create( label='biso_Ba', - param_uid=project.sample_models['lbco'].atom_sites['Ba'].b_iso.uid, + param_uid=project.structures['lbco'].atom_sites['Ba'].b_iso.uid, ) # Set aliases for the occupancies of the atom sites -project.analysis.aliases.add( +project.analysis.aliases.create( label='occ_La', - param_uid=project.sample_models['lbco'].atom_sites['La'].occupancy.uid, + param_uid=project.structures['lbco'].atom_sites['La'].occupancy.uid, ) -project.analysis.aliases.add( +project.analysis.aliases.create( label='occ_Ba', - param_uid=project.sample_models['lbco'].atom_sites['Ba'].occupancy.uid, + param_uid=project.structures['lbco'].atom_sites['Ba'].occupancy.uid, ) ``` @@ -300,12 +292,12 @@ other aliases. An example of setting constraints for the aliases defined above: ```python -project.analysis.constraints.add( +project.analysis.constraints.create( lhs_alias='biso_Ba', rhs_expr='biso_La', ) -project.analysis.constraints.add( +project.analysis.constraints.create( lhs_alias='occ_Ba', rhs_expr='1 - occ_La', ) @@ -339,8 +331,8 @@ User defined constraints To inspect an analysis configuration in CIF format, use: ```python -# Show sample model as CIF -project.sample_models['lbco'].show_as_cif() +# Show structure as CIF +project.structures['lbco'].show_as_cif() ``` Example output: diff --git a/docs/user-guide/analysis-workflow/experiment.md b/docs/user-guide/analysis-workflow/experiment.md index da08cd43..a62d9822 100644 --- a/docs/user-guide/analysis-workflow/experiment.md +++ b/docs/user-guide/analysis-workflow/experiment.md @@ -23,7 +23,7 @@ EasyDiffraction allows you to: Below, you will find instructions on how to define and manage experiments in EasyDiffraction. It is assumed that you have already created a `project` object, as described in the [Project](project.md) section as well as defined its -`sample_models`, as described in the [Sample Model](model.md) section. +`structures`, as described in the [Structure](model.md) section. ### Adding from CIF @@ -126,12 +126,12 @@ project.experiments.add_from_data_path( ``` If you do not have measured data for fitting and only want to view the simulated -pattern, you can define an experiment without measured data using the -`add_without_data` method: +pattern, you can define an experiment without measured data using the `create` +method: ```python # Add an experiment without measured data -project.experiments.add_without_data( +project.experiments.create( name='hrpt', sample_form='powder', beam_mode='constant wavelength', @@ -144,10 +144,11 @@ directly using the `add` method: ```python # Add an experiment by passing the experiment object directly -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory -experiment = Experiment( +experiment = ExperimentFactory.create( name='hrpt', + data_path='data/hrpt_lbco.xye', sample_form='powder', beam_mode='constant wavelength', radiation_probe='neutron', @@ -169,9 +170,9 @@ understand the different aspects of the experiment: as broadening and asymmetry. 3. **Background Category**: Defines the background type and allows you to add background points. -4. **Linked Phases Category**: Links the sample model defined in the previous - step to the experiment, allowing you to specify the scale factor for the - linked phase. +4. **Linked Phases Category**: Links the structure defined in the previous step + to the experiment, allowing you to specify the scale factor for the linked + phase. 5. **Measured Data Category**: Contains the measured data. The expected format depends on the experiment type, but generally includes columns for 2θ angle or TOF and intensity. @@ -188,8 +189,8 @@ project.experiments['hrpt'].instrument.calib_twotheta_offset = 0.6 ```python # Add excluded regions to the experiment -project.experiments['hrpt'].excluded_regions.add(start=0, end=10) -project.experiments['hrpt'].excluded_regions.add(start=160, end=180) +project.experiments['hrpt'].excluded_regions.create(start=0, end=10) +project.experiments['hrpt'].excluded_regions.create(start=160, end=180) ``` ### 3. Peak Category { #peak-category } @@ -213,18 +214,18 @@ project.experiments['hrpt'].peak.broad_lorentz_y = 0.1 project.experiments['hrpt'].background_type = 'line-segment' # 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.create(x=10, y=170) +project.experiments['hrpt'].background.create(x=30, y=170) +project.experiments['hrpt'].background.create(x=50, y=170) +project.experiments['hrpt'].background.create(x=110, y=170) +project.experiments['hrpt'].background.create(x=165, y=170) ``` ### 5. Linked Phases Category { #linked-phases-category } ```python -# Link the sample model defined in the previous step to the experiment -project.experiments['hrpt'].linked_phases.add(id='lbco', scale=10.0) +# Link the structure defined in the previous step to the experiment +project.experiments['hrpt'].linked_phases.create(id='lbco', scale=10.0) ``` ### 6. Measured Data Category { #measured-data-category } diff --git a/docs/user-guide/analysis-workflow/index.md b/docs/user-guide/analysis-workflow/index.md index 1ce3ec66..51c1f623 100644 --- a/docs/user-guide/analysis-workflow/index.md +++ b/docs/user-guide/analysis-workflow/index.md @@ -17,10 +17,10 @@ flowchart LR ``` - [:material-archive: Project](project.md) – Establish a **project** as a - container for sample model and experiment parameters, measured and calculated + container for structure and experiment parameters, measured and calculated data, analysis settings and results. -- [:material-puzzle: Sample Model](model.md) – Load an existing - **crystallographic model** in CIF format or define a new one from scratch. +- [:material-puzzle: Structure](model.md) – Load an existing **crystallographic + model** in CIF format or define a new one from scratch. - [:material-microscope: Experiment](experiment.md) – Import **experimental diffraction data** and configure **instrumental** and other relevant parameters. diff --git a/docs/user-guide/analysis-workflow/model.md b/docs/user-guide/analysis-workflow/model.md index 9cdb4c9c..c437ea62 100644 --- a/docs/user-guide/analysis-workflow/model.md +++ b/docs/user-guide/analysis-workflow/model.md @@ -2,17 +2,16 @@ icon: material/puzzle --- -# :material-puzzle: Sample Model +# :material-puzzle: Structure -The **Sample Model** in EasyDiffraction represents the **crystallographic +The **Structure** in EasyDiffraction represents the **crystallographic structure** used to calculate the diffraction pattern, which is then fitted to the **experimentally measured data** to refine the structural parameters. EasyDiffraction allows you to: - **Load an existing model** from a file (**CIF** format). -- **Manually define** a new sample model by specifying crystallographic - parameters. +- **Manually define** a new structure by specifying crystallographic parameters. Below, you will find instructions on how to define and manage crystallographic models in EasyDiffraction. It is assumed that you have already created a @@ -20,20 +19,20 @@ models in EasyDiffraction. It is assumed that you have already created a ## Adding a Model from CIF -This is the most straightforward way to define a sample model in -EasyDiffraction. If you have a crystallographic information file (CIF) for your -sample model, you can add it to your project using the `add_phase_from_file` -method of the `project` instance. In this case, the name of the model will be +This is the most straightforward way to define a structure in EasyDiffraction. +If you have a crystallographic information file (CIF) for your structure, you +can add it to your project using the `add_from_cif_path` method of the +`project.structures` collection. In this case, the name of the model will be taken from CIF. ```python # Load a phase from a CIF file -project.add_phase_from_file('data/lbco.cif') +project.structures.add_from_cif_path('data/lbco.cif') ``` -Accessing the model after loading it will be done through the `sample_models` -object of the `project` instance. The name of the model will be the same as the -data block id in the CIF file. For example, if the CIF file contains a data +Accessing the model after loading it will be done through the `structures` +collection of the `project` instance. The name of the model will be the same as +the data block id in the CIF file. For example, if the CIF file contains a data block with the id `lbco`, @@ -52,27 +51,27 @@ data_lbco you can access it in the code as follows: ```python -# Access the sample model by its name -project.sample_models['lbco'] +# Access the structure by its name +project.structures['lbco'] ``` ## Defining a Model Manually If you do not have a CIF file or prefer to define the model manually, you can -use the `add` method of the `sample_models` object of the `project` instance. In +use the `create` method of the `structures` object of the `project` instance. In this case, you will need to specify the name of the model, which will be used to reference it later. ```python -# Add a sample model with default parameters -# The sample model name is used to reference it later. -project.sample_models.add(name='nacl') +# Add a structure with default parameters +# The structure name is used to reference it later. +project.structures.create(name='nacl') ``` -The `add` method creates a new sample model with default parameters. You can -then modify its parameters to match your specific crystallographic structure. -All parameters are grouped into the following categories, which makes it easier -to manage the model: +The `add` method creates a new structure with default parameters. You can then +modify its parameters to match your specific crystallographic structure. All +parameters are grouped into the following categories, which makes it easier to +manage the model: 1. **Space Group Category**: Defines the symmetry of the crystal structure. 2. **Cell Category**: Specifies the dimensions and angles of the unit cell. @@ -83,21 +82,21 @@ to manage the model: ```python # Set space group -project.sample_models['nacl'].space_group.name_h_m = 'F m -3 m' +project.structures['nacl'].space_group.name_h_m = 'F m -3 m' ``` ### 2. Cell Category { #cell-category } ```python # Define unit cell parameters -project.sample_models['nacl'].cell.length_a = 5.691694 +project.structures['nacl'].cell.length_a = 5.691694 ``` ### 3. Atom Sites Category { #atom-sites-category } ```python # Add atomic sites -project.sample_models['nacl'].atom_sites.append( +project.structures['nacl'].atom_sites.create( label='Na', type_symbol='Na', fract_x=0, @@ -106,7 +105,7 @@ project.sample_models['nacl'].atom_sites.append( occupancy=1, b_iso_or_equiv=0.5, ) -project.sample_models['nacl'].atom_sites.append( +project.structures['nacl'].atom_sites.create( label='Cl', type_symbol='Cl', fract_x=0, @@ -119,33 +118,33 @@ project.sample_models['nacl'].atom_sites.append( ## Listing Defined Models -To check which sample models have been added to the `project`, use: +To check which structures have been added to the `project`, use: ```python -# Show defined sample models -project.sample_models.show_names() +# Show defined structures +project.structures.show_names() ``` Expected output: ``` -Defined sample models 🧩 +Defined structures 🧩 ['lbco', 'nacl'] ``` ## Viewing a Model as CIF -To inspect a sample model in CIF format, use: +To inspect a structure in CIF format, use: ```python -# Show sample model as CIF -project.sample_models['lbco'].show_as_cif() +# Show structure as CIF +project.structures['lbco'].show_as_cif() ``` Example output: ``` -Sample model 🧩 'lbco' as cif +Structure 🧩 'lbco' as cif ╒═══════════════════════════════════════════╕ │ data_lbco │ │ │ @@ -179,9 +178,9 @@ Sample model 🧩 'lbco' as cif ## Saving a Model Saving the project, as described in the [Project](project.md) section, will also -save the model. Each model is saved as a separate CIF file in the -`sample_models` subdirectory of the project directory. The project file contains -references to these files. +save the model. Each model is saved as a separate CIF file in the `structures` +subdirectory of the project directory. The project file contains references to +these files. Below is an example of the saved CIF file for the `lbco` model: diff --git a/docs/user-guide/analysis-workflow/project.md b/docs/user-guide/analysis-workflow/project.md index 542e9dad..45e1d9c5 100644 --- a/docs/user-guide/analysis-workflow/project.md +++ b/docs/user-guide/analysis-workflow/project.md @@ -8,7 +8,7 @@ The **Project** serves as a container for all data and metadata associated with a particular data analysis task. It acts as the top-level entity in EasyDiffraction, ensuring structured organization and easy access to relevant information. Each project can contain multiple **experimental datasets**, with -each dataset containing contribution from multiple **sample models**. +each dataset containing contribution from multiple **structures**. EasyDiffraction allows you to: @@ -73,7 +73,7 @@ The example below illustrates a typical **project structure** for a
 📁 La0.5Ba0.5CoO3     - Project directory.
 ├── 📄 project.cif    - Main project description file.
-├── 📁 sample_models  - Folder with sample models (crystallographic structures).
+├── 📁 structures  - Folder with structures (crystallographic structures).
 │   ├── 📄 lbco.cif   - File with La0.5Ba0.5CoO3 structure parameters.
 │   └── ...
 ├── 📁 experiments    - Folder with instrumental parameters and measured data.
@@ -96,13 +96,13 @@ showing the contents of all files in the project.
 
     If you save the project right after creating it, the project directory will
     only contain the `project.cif` file. The other folders and files will be
-    created as you add sample models, experiments, and set up the analysis. The
+    created as you add structures, experiments, and set up the analysis. The
     summary folder will be created after the analysis is completed.
 
 ### 1. project.cif
 
 This file provides an overview of the project, including file names of the
-**sample models** and **experiments** associated with the project.
+**structures** and **experiments** associated with the project.
 
 
 
@@ -114,7 +114,7 @@ data_La0.5Ba0.5CoO3
 _project.description "neutrons, powder, constant wavelength, HRPT@PSI"
 
 loop_
-_sample_model.cif_file_name
+_structure.cif_file_name
 lbco.cif
 
 loop_
@@ -125,9 +125,9 @@ hrpt.cif
 
 
 
-### 2. sample_models / lbco.cif
+### 2. structures / lbco.cif
 
-This file contains crystallographic information associated with the sample
+This file contains crystallographic information associated with the structure
 model, including **space group**, **unit cell parameters**, and **atomic
 positions**.
 
@@ -271,4 +271,4 @@ occ_Ba   "1 - occ_La"
 ---
 
 Now that the Project has been defined, you can proceed to the next step:
-[Sample Model](model.md).
+[Structure](model.md).
diff --git a/docs/user-guide/concept.md b/docs/user-guide/concept.md
index 2ad7e077..fb3f4687 100644
--- a/docs/user-guide/concept.md
+++ b/docs/user-guide/concept.md
@@ -58,8 +58,8 @@ Credits: DOI 10.1126/science.1238932
 ## Data Analysis
 
 Data analysis uses the reduced data to extract meaningful information about the
-sample. This may include determining the crystal or magnetic structure,
-identifying phases, performing quantitative analysis, etc.
+crystallographic structure. This may include determining the crystal or magnetic
+structure, identifying phases, performing quantitative analysis, etc.
 
 Analysis often involves comparing experimental data with data calculated from a
 crystallographic model to validate and interpret the results. For powder
@@ -73,7 +73,7 @@ By "model", we usually refer to a **crystallographic model** of the sample. This
 includes unit cell parameters, space group, atomic positions, thermal
 parameters, and more. However, the term "model" also encompasses experimental
 aspects such as instrumental resolution, background, peak shape, etc. Therefore,
-EasyDiffraction separates the model into two parts: the **sample model** and the
+EasyDiffraction separates the model into two parts: the **structure** and the
 **experiment**.
 
 The aim of data analysis is to refine the structural parameters of the sample by
diff --git a/docs/user-guide/data-format.md b/docs/user-guide/data-format.md
index acbe6bfe..63e2e52e 100644
--- a/docs/user-guide/data-format.md
+++ b/docs/user-guide/data-format.md
@@ -173,8 +173,8 @@ human-readable crystallographic data.
 
 ## Experiment Definition
 
-The previous example described the **sample model** (crystallographic model),
-but how is the **experiment** itself represented?
+The previous example described the **structure** (crystallographic model), but
+how is the **experiment** itself represented?
 
 The experiment is also saved as a CIF file. For example, background intensity in
 a powder diffraction experiment might be represented as:
@@ -206,7 +206,7 @@ EasyDiffraction uses CIF consistently throughout its workflow, including in the
 following blocks:
 
 - **project**: contains the project information
-- **sample model**: defines the sample model
+- **structure**: defines the structure
 - **experiment**: contains the experiment setup and measured data
 - **analysis**: stores fitting and analysis parameters
 - **summary**: captures analysis results
diff --git a/docs/user-guide/first-steps.md b/docs/user-guide/first-steps.md
index d5a832d6..acb50bec 100644
--- a/docs/user-guide/first-steps.md
+++ b/docs/user-guide/first-steps.md
@@ -37,12 +37,12 @@ A complete tutorial using the `import` syntax can be found
 ### Importing specific parts
 
 Alternatively, you can import specific classes or methods from the package. For
-example, you can import the `Project`, `SampleModel`, `Experiment` classes and
+example, you can import the `Project`, `Structure`, `Experiment` classes and
 `download_from_repository` method like this:
 
 ```python
 from easydiffraction import Project
-from easydiffraction import SampleModel
+from easydiffraction import Structure
 from easydiffraction import Experiment
 from easydiffraction import download_from_repository
 ```
@@ -66,7 +66,7 @@ workflow. One of them is the `download_from_repository` function, which allows
 you to download data files from our remote repository, making it easy to access
 and use them while experimenting with EasyDiffraction.
 
-For example, you can download a sample data file like this:
+For example, you can download a data file like this:
 
 ```python
 import easydiffraction as ed
@@ -91,22 +91,22 @@ calculation, minimization, and plotting. These methods can be called on the
 
 ### Supported calculators
 
-For example, you can use the `show_supported_calculators()` method to see which
-calculation engines are available for use in your project:
+The calculator is automatically selected based on the experiment type. You can
+use the `show_supported_calculator_types()` method on an experiment to see which
+calculation engines are compatible:
 
 ```python
-project.show_supported_calculators()
+project.experiments['hrpt'].show_supported_calculator_types()
 ```
 
 This will display a list of supported calculators along with their descriptions,
 allowing you to choose the one that best fits your needs.
 
-An example of the output for the `show_supported_calculators()` method is:
+An example of the output for a Bragg diffraction experiment:
 
-| Calculator | Description                                                 |
-| ---------- | ----------------------------------------------------------- |
-| cryspy     | CrysPy library for crystallographic calculations            |
-| pdffit     | PDFfit2 library for pair distribution function calculations |
+| Calculator | Description                                      |
+| ---------- | ------------------------------------------------ |
+| cryspy     | CrysPy library for crystallographic calculations |
 
 ### Supported minimizers
 
@@ -138,16 +138,16 @@ who want to quickly understand how to work with parameters in their projects.
 An example of the output for the `project.analysis.how_to_access_parameters()`
 method is:
 
-|     | Code variable                                          | Unique ID for CIF                |
-| --- | ------------------------------------------------------ | -------------------------------- |
-| 1   | project.sample_models['lbco'].atom_site['La'].adp_type | lbco.atom_site.La.ADP_type       |
-| 2   | project.sample_models['lbco'].atom_site['La'].b_iso    | lbco.atom_site.La.B_iso_or_equiv |
-| 3   | project.sample_models['lbco'].atom_site['La'].fract_x  | lbco.atom_site.La.fract_x        |
-| 4   | project.sample_models['lbco'].atom_site['La'].fract_y  | lbco.atom_site.La.fract_y        |
-| ... | ...                                                    | ...                              |
-| 59  | project.experiments['hrpt'].peak.broad_gauss_u         | hrpt.peak.broad_gauss_u          |
-| 60  | project.experiments['hrpt'].peak.broad_gauss_v         | hrpt.peak.broad_gauss_v          |
-| 61  | project.experiments['hrpt'].peak.broad_gauss_w         | hrpt.peak.broad_gauss_w          |
+|     | Code variable                                       | Unique ID for CIF                |
+| --- | --------------------------------------------------- | -------------------------------- |
+| 1   | project.structures['lbco'].atom_site['La'].adp_type | lbco.atom_site.La.ADP_type       |
+| 2   | project.structures['lbco'].atom_site['La'].b_iso    | lbco.atom_site.La.B_iso_or_equiv |
+| 3   | project.structures['lbco'].atom_site['La'].fract_x  | lbco.atom_site.La.fract_x        |
+| 4   | project.structures['lbco'].atom_site['La'].fract_y  | lbco.atom_site.La.fract_y        |
+| ... | ...                                                 | ...                              |
+| 59  | project.experiments['hrpt'].peak.broad_gauss_u      | hrpt.peak.broad_gauss_u          |
+| 60  | project.experiments['hrpt'].peak.broad_gauss_v      | hrpt.peak.broad_gauss_v          |
+| 61  | project.experiments['hrpt'].peak.broad_gauss_w      | hrpt.peak.broad_gauss_w          |
 
 ### Supported plotters
 
@@ -169,7 +169,7 @@ An example of the output is:
 
 Once the EasyDiffraction package is imported, you can proceed with the **data
 analysis**. This step can be split into several sub-steps, such as creating a
-project, defining sample models, adding experimental data, etc.
+project, defining structures, adding experimental data, etc.
 
 EasyDiffraction provides a **Python API** that allows you to perform these steps
 programmatically in a certain linear order. This is especially useful for users
diff --git a/docs/user-guide/parameters.md b/docs/user-guide/parameters.md
index 0fc1d609..794f65aa 100644
--- a/docs/user-guide/parameters.md
+++ b/docs/user-guide/parameters.md
@@ -2,7 +2,7 @@
 
 The data analysis process, introduced in the [Concept](concept.md) section,
 assumes that you mainly work with different parameters. The parameters are used
-to describe the sample model and the experiment and are required to set up the
+to describe the structure and the experiment and are required to set up the
 analysis.
 
 Each parameter in EasyDiffraction has a specific name used for code reference,
@@ -40,9 +40,9 @@ means the parameter is fixed. To optimize a parameter, set `free` to `True`.
 
 Although parameters are central, EasyDiffraction hides their creation and
 attribute handling from the user. The user only accesses the required parameters
-through the top-level objects, such as `project`, `sample_models`,
-`experiments`, etc. The parameters are created and initialized automatically
-when a new project is created or an existing one is loaded.
+through the top-level objects, such as `project`, `structures`, `experiments`,
+etc. The parameters are created and initialized automatically when a new project
+is created or an existing one is loaded.
 
 In the following sections, you can see a list of the parameters used in
 EasyDiffraction. Use the tabs to switch between how to access a parameter in
@@ -51,27 +51,26 @@ code and its CIF name for serialization.
 !!! warning "Important"
 
     Remember that parameters are accessed in code through their parent objects,
-    such as `project`, `sample_models`, or `experiments`. For example, if you
-    have a sample model with the ID `nacl`, you can access the space group name
+    such as `project`, `structures`, or `experiments`. For example, if you
+    have a structure with the ID `nacl`, you can access the space group name
     using the following syntax:
 
     ```python
-    project.sample_models['nacl'].space_group.name_h_m
+    project.structures['nacl'].space_group.name_h_m
     ```
 
-In the example above, `space_group` is a sample model category, and `name_h_m`
-is the parameter. For simplicity, only the last part (`category.parameter`) of
-the full access name will be shown in the tables below.
+In the example above, `space_group` is a structure category, and `name_h_m` is
+the parameter. For simplicity, only the last part (`category.parameter`) of the
+full access name will be shown in the tables below.
 
 In addition, the CIF names are also provided for each parameter, which are used
 to serialize the parameters in the CIF format.
 
 Tags defining the corresponding experiment type are also given before the table.
 
-## Sample model parameters
+## Structure parameters
 
-Below is a list of parameters used to describe the sample model in
-EasyDiffraction.
+Below is a list of parameters used to describe the structure in EasyDiffraction.
 
 ### Crystall structure parameters
 
diff --git a/pixi.lock b/pixi.lock
index 106286d9..1685316c 100644
--- a/pixi.lock
+++ b/pixi.lock
@@ -4669,8 +4669,8 @@ packages:
   requires_python: '>=3.9,<4.0'
 - pypi: ./
   name: easydiffraction
-  version: 0.10.2+devdirty78
-  sha256: 735c06d04ba73c012b4d00d6562d8f55a629fd23dfa89532d2818e52d493013d
+  version: 0.10.2+devdirty36
+  sha256: c26412f987f3f60607ea00f77d4f12aadc3b38ea31833f634b218e09965dbdbc
   requires_dist:
   - asciichartpy
   - asteval
diff --git a/pyproject.toml b/pyproject.toml
index d59c6205..dd645c71 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -265,8 +265,9 @@ ban-relative-imports = 'all'
 force-single-line = true
 
 [tool.ruff.lint.per-file-ignores]
-'*test_*.py' = ['S101']  # allow asserts in test files
-'conftest.py' = ['S101'] # allow asserts in test files
+'*test_*.py' = ['S101']    # allow asserts in test files
+'conftest.py' = ['S101']   # allow asserts in test files
+'*/__init__.py' = ['F401'] # re-exports are intentional in __init__.py
 # Vendored jupyter_dark_detect: keep as-is from upstream for easy updates
 # https://github.com/OpenMined/jupyter-dark-detect/tree/main/jupyter_dark_detect
 'src/easydiffraction/utils/_vendored/jupyter_dark_detect/*' = [
diff --git a/src/easydiffraction/__init__.py b/src/easydiffraction/__init__.py
index 259c931d..d15d6682 100644
--- a/src/easydiffraction/__init__.py
+++ b/src/easydiffraction/__init__.py
@@ -1,9 +1,9 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
 
-from easydiffraction.experiments.experiment.factory import ExperimentFactory
+from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory
+from easydiffraction.datablocks.structure.item.factory import StructureFactory
 from easydiffraction.project.project import Project
-from easydiffraction.sample_models.sample_model.factory import SampleModelFactory
 from easydiffraction.utils.logging import Logger
 from easydiffraction.utils.logging import console
 from easydiffraction.utils.logging import log
@@ -13,18 +13,3 @@
 from easydiffraction.utils.utils import get_value_from_xye_header
 from easydiffraction.utils.utils import list_tutorials
 from easydiffraction.utils.utils import show_version
-
-__all__ = [
-    'Project',
-    'ExperimentFactory',
-    'SampleModelFactory',
-    'download_data',
-    'download_tutorial',
-    'download_all_tutorials',
-    'list_tutorials',
-    'get_value_from_xye_header',
-    'show_version',
-    'Logger',
-    'log',
-    'console',
-]
diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py
index 3532290e..bf403247 100644
--- a/src/easydiffraction/analysis/analysis.py
+++ b/src/easydiffraction/analysis/analysis.py
@@ -7,18 +7,19 @@
 
 import pandas as pd
 
-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.aliases.factory import AliasesFactory
+from easydiffraction.analysis.categories.constraints.factory import ConstraintsFactory
+from easydiffraction.analysis.categories.fit_mode import FitModeEnum
+from easydiffraction.analysis.categories.fit_mode import FitModeFactory
 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.parameters import StringDescriptor
-from easydiffraction.core.singletons import ConstraintsHandler
+from easydiffraction.core.singleton import ConstraintsHandler
+from easydiffraction.core.variable import NumericDescriptor
+from easydiffraction.core.variable import Parameter
+from easydiffraction.core.variable import StringDescriptor
+from easydiffraction.datablocks.experiment.collection import Experiments
 from easydiffraction.display.tables import TableRenderer
-from easydiffraction.experiments.experiments import Experiments
 from easydiffraction.utils.logging import console
 from easydiffraction.utils.logging import log
 from easydiffraction.utils.utils import render_cif
@@ -30,7 +31,7 @@ class Analysis:
 
     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.
+    computations across the project's structures and experiments.
 
     Typical usage:
 
@@ -46,8 +47,6 @@ class Analysis:
         fitter: Active fitter/minimizer driver.
     """
 
-    _calculator = CalculatorFactory.create_calculator('cryspy')
-
     def __init__(self, project) -> None:
         """Create a new Analysis instance bound to a project.
 
@@ -55,13 +54,152 @@ def __init__(self, project) -> None:
             project: The project that owns models and experiments.
         """
         self.project = project
-        self.aliases = Aliases()
-        self.constraints = Constraints()
+        self._aliases_type: str = AliasesFactory.default_tag()
+        self.aliases = AliasesFactory.create(self._aliases_type)
+        self._constraints_type: str = ConstraintsFactory.default_tag()
+        self.constraints = ConstraintsFactory.create(self._constraints_type)
         self.constraints_handler = ConstraintsHandler.get()
-        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 = Fitter('lmfit (leastsq)')
+        self._fit_mode_type: str = FitModeFactory.default_tag()
+        self._fit_mode = FitModeFactory.create(self._fit_mode_type)
+        self._joint_fit_experiments = JointFitExperiments()
+        self.fitter = Fitter('lmfit')
+
+    def help(self) -> None:
+        """Print a summary of analysis properties and methods."""
+        from easydiffraction.core.guard import GuardedBase
+
+        console.paragraph("Help for 'Analysis'")
+
+        cls = type(self)
+
+        # Auto-discover properties from MRO
+        seen_props: dict = {}
+        for base in cls.mro():
+            for key, attr in base.__dict__.items():
+                if key.startswith('_') or not isinstance(attr, property):
+                    continue
+                if key not in seen_props:
+                    seen_props[key] = attr
+
+        prop_rows = []
+        for i, key in enumerate(sorted(seen_props), 1):
+            prop = seen_props[key]
+            writable = '✓' if prop.fset else '✗'
+            doc = GuardedBase._first_sentence(prop.fget.__doc__ if prop.fget else None)
+            prop_rows.append([str(i), key, writable, doc])
+
+        if prop_rows:
+            console.paragraph('Properties')
+            render_table(
+                columns_headers=['#', 'Name', 'Writable', 'Description'],
+                columns_alignment=['right', 'left', 'center', 'left'],
+                columns_data=prop_rows,
+            )
+
+        # Auto-discover methods from MRO
+        seen_methods: set = set()
+        methods_list: list = []
+        for base in cls.mro():
+            for key, attr in base.__dict__.items():
+                if key.startswith('_') or key in seen_methods:
+                    continue
+                if isinstance(attr, property):
+                    continue
+                raw = attr
+                if isinstance(raw, (staticmethod, classmethod)):
+                    raw = raw.__func__
+                if callable(raw):
+                    seen_methods.add(key)
+                    methods_list.append((key, raw))
+
+        method_rows = []
+        for i, (key, method) in enumerate(sorted(methods_list), 1):
+            doc = GuardedBase._first_sentence(getattr(method, '__doc__', None))
+            method_rows.append([str(i), f'{key}()', doc])
+
+        if method_rows:
+            console.paragraph('Methods')
+            render_table(
+                columns_headers=['#', 'Name', 'Description'],
+                columns_alignment=['right', 'left', 'left'],
+                columns_data=method_rows,
+            )
+
+    # ------------------------------------------------------------------
+    #  Aliases (switchable-category pattern)
+    # ------------------------------------------------------------------
+
+    @property
+    def aliases_type(self) -> str:
+        """Tag of the active aliases collection type."""
+        return self._aliases_type
+
+    @aliases_type.setter
+    def aliases_type(self, new_type: str) -> None:
+        """Switch to a different aliases collection type.
+
+        Args:
+            new_type: Aliases tag (e.g. ``'default'``).
+        """
+        supported_tags = AliasesFactory.supported_tags()
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported aliases type '{new_type}'. "
+                f'Supported: {supported_tags}. '
+                f"For more information, use 'show_supported_aliases_types()'",
+            )
+            return
+        self.aliases = AliasesFactory.create(new_type)
+        self._aliases_type = new_type
+        console.paragraph('Aliases type changed to')
+        console.print(new_type)
+
+    def show_supported_aliases_types(self) -> None:
+        """Print a table of supported aliases collection types."""
+        AliasesFactory.show_supported()
+
+    def show_current_aliases_type(self) -> None:
+        """Print the currently used aliases collection type."""
+        console.paragraph('Current aliases type')
+        console.print(self._aliases_type)
+
+    # ------------------------------------------------------------------
+    #  Constraints (switchable-category pattern)
+    # ------------------------------------------------------------------
+
+    @property
+    def constraints_type(self) -> str:
+        """Tag of the active constraints collection type."""
+        return self._constraints_type
+
+    @constraints_type.setter
+    def constraints_type(self, new_type: str) -> None:
+        """Switch to a different constraints collection type.
+
+        Args:
+            new_type: Constraints tag (e.g. ``'default'``).
+        """
+        supported_tags = ConstraintsFactory.supported_tags()
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported constraints type '{new_type}'. "
+                f'Supported: {supported_tags}. '
+                f"For more information, use 'show_supported_constraints_types()'",
+            )
+            return
+        self.constraints = ConstraintsFactory.create(new_type)
+        self._constraints_type = new_type
+        console.paragraph('Constraints type changed to')
+        console.print(new_type)
+
+    def show_supported_constraints_types(self) -> None:
+        """Print a table of supported constraints collection types."""
+        ConstraintsFactory.show_supported()
+
+    def show_current_constraints_type(self) -> None:
+        """Print the currently used constraints collection type."""
+        console.paragraph('Current constraints type')
+        console.print(self._constraints_type)
 
     def _get_params_as_dataframe(
         self,
@@ -108,13 +246,13 @@ def _get_params_as_dataframe(
         return df
 
     def show_all_params(self) -> None:
-        """Print a table with all parameters for sample models and
+        """Print a table with all parameters for structures and
         experiments.
         """
-        sample_models_params = self.project.sample_models.parameters
+        structures_params = self.project.structures.parameters
         experiments_params = self.project.experiments.parameters
 
-        if not sample_models_params and not experiments_params:
+        if not structures_params and not experiments_params:
             log.warning('No parameters found.')
             return
 
@@ -129,8 +267,8 @@ def show_all_params(self) -> None:
             'fittable',
         ]
 
-        console.paragraph('All parameters for all sample models (🧩 data blocks)')
-        df = self._get_params_as_dataframe(sample_models_params)
+        console.paragraph('All parameters for all structures (🧩 data blocks)')
+        df = self._get_params_as_dataframe(structures_params)
         filtered_df = df[filtered_headers]
         tabler.render(filtered_df)
 
@@ -143,10 +281,10 @@ def show_fittable_params(self) -> None:
         """Print a table with parameters that can be included in
         fitting.
         """
-        sample_models_params = self.project.sample_models.fittable_parameters
+        structures_params = self.project.structures.fittable_parameters
         experiments_params = self.project.experiments.fittable_parameters
 
-        if not sample_models_params and not experiments_params:
+        if not structures_params and not experiments_params:
             log.warning('No fittable parameters found.')
             return
 
@@ -163,8 +301,8 @@ def show_fittable_params(self) -> None:
             'free',
         ]
 
-        console.paragraph('Fittable parameters for all sample models (🧩 data blocks)')
-        df = self._get_params_as_dataframe(sample_models_params)
+        console.paragraph('Fittable parameters for all structures (🧩 data blocks)')
+        df = self._get_params_as_dataframe(structures_params)
         filtered_df = df[filtered_headers]
         tabler.render(filtered_df)
 
@@ -177,9 +315,9 @@ def show_free_params(self) -> None:
         """Print a table with only currently-free (varying)
         parameters.
         """
-        sample_models_params = self.project.sample_models.free_parameters
+        structures_params = self.project.structures.free_parameters
         experiments_params = self.project.experiments.free_parameters
-        free_params = sample_models_params + experiments_params
+        free_params = structures_params + experiments_params
 
         if not free_params:
             log.warning('No free parameters found.')
@@ -200,8 +338,7 @@ def show_free_params(self) -> None:
         ]
 
         console.paragraph(
-            'Free parameters for both sample models (🧩 data blocks) '
-            'and experiments (🔬 data blocks)'
+            'Free parameters for both structures (🧩 data blocks) and experiments (🔬 data blocks)'
         )
         df = self._get_params_as_dataframe(free_params)
         filtered_df = df[filtered_headers]
@@ -213,10 +350,10 @@ def how_to_access_parameters(self) -> None:
         The output explains how to reference specific parameters in
         code.
         """
-        sample_models_params = self.project.sample_models.parameters
+        structures_params = self.project.structures.parameters
         experiments_params = self.project.experiments.parameters
         all_params = {
-            'sample_models': sample_models_params,
+            'structures': structures_params,
             'experiments': experiments_params,
         }
 
@@ -277,10 +414,10 @@ def show_parameter_cif_uids(self) -> None:
         The output explains which unique identifiers are used when
         creating CIF-based constraints.
         """
-        sample_models_params = self.project.sample_models.parameters
+        structures_params = self.project.structures.parameters
         experiments_params = self.project.experiments.parameters
         all_params = {
-            'sample_models': sample_models_params,
+            'structures': structures_params,
             'experiments': experiments_params,
         }
 
@@ -328,40 +465,6 @@ def show_parameter_cif_uids(self) -> None:
             columns_data=columns_data,
         )
 
-    def show_current_calculator(self) -> None:
-        """Print the name of the currently selected calculator
-        engine.
-        """
-        console.paragraph('Current calculator')
-        console.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
-        self.calculator = calculator
-        self._calculator_key = calculator_name
-        console.paragraph('Current calculator changed to')
-        console.print(self.current_calculator)
-
     def show_current_minimizer(self) -> None:
         """Print the name of the currently selected minimizer."""
         console.paragraph('Current minimizer')
@@ -372,7 +475,7 @@ def show_available_minimizers() -> None:
         """Print a table of available minimizer drivers on this
         system.
         """
-        MinimizerFactory.show_available_minimizers()
+        MinimizerFactory.show_supported()
 
     @property
     def current_minimizer(self) -> Optional[str]:
@@ -384,78 +487,63 @@ def current_minimizer(self, selection: str) -> None:
         """Switch to a different minimizer implementation.
 
         Args:
-            selection: Minimizer selection string, e.g.
-                'lmfit (leastsq)'.
+            selection: Minimizer selection string, e.g. 'lmfit'.
         """
         self.fitter = Fitter(selection)
         console.paragraph('Current minimizer changed to')
         console.print(self.current_minimizer)
 
+    # ------------------------------------------------------------------
+    #  Fit mode (switchable-category pattern)
+    # ------------------------------------------------------------------
+
     @property
-    def fit_mode(self) -> str:
-        """Current fitting strategy: either 'single' or 'joint'."""
+    def fit_mode(self):
+        """Fit-mode category item holding the active strategy."""
         return self._fit_mode
 
-    @fit_mode.setter
-    def fit_mode(self, strategy: str) -> None:
-        """Set the fitting strategy.
+    @property
+    def fit_mode_type(self) -> str:
+        """Tag of the active fit-mode category type."""
+        return self._fit_mode_type
 
-        When set to 'joint', all experiments get default weights and
-        are used together in a single optimization.
+    @fit_mode_type.setter
+    def fit_mode_type(self, new_type: str) -> None:
+        """Switch to a different fit-mode category type.
 
         Args:
-                strategy: Either 'single' or 'joint'.
-
-        Raises:
-            ValueError: If an unsupported strategy value is
-                provided.
+            new_type: Fit-mode tag (e.g. ``'default'``).
         """
-        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.names:
-                self.joint_fit_experiments.add(id=id, weight=0.5)
-        console.paragraph('Current fit mode changed to')
-        console.print(self._fit_mode)
-
-    def show_available_fit_modes(self) -> None:
-        """Print all supported fitting strategies and their
-        descriptions.
-        """
-        strategies = [
-            {
-                'Strategy': 'single',
-                'Description': 'Independent fitting of each experiment; no shared parameters',
-            },
-            {
-                'Strategy': 'joint',
-                'Description': 'Simultaneous fitting of all experiments; '
-                'some parameters are shared',
-            },
-        ]
+        supported_tags = FitModeFactory.supported_tags()
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported fit-mode type '{new_type}'. "
+                f'Supported: {supported_tags}. '
+                f"For more information, use 'show_supported_fit_mode_types()'",
+            )
+            return
+        self._fit_mode = FitModeFactory.create(new_type)
+        self._fit_mode_type = new_type
+        console.paragraph('Fit-mode type changed to')
+        console.print(new_type)
 
-        columns_headers = ['Strategy', 'Description']
-        columns_alignment = ['left', 'left']
-        columns_data = []
-        for item in strategies:
-            strategy = item['Strategy']
-            description = item['Description']
-            columns_data.append([strategy, description])
+    def show_supported_fit_mode_types(self) -> None:
+        """Print a table of supported fit-mode category types."""
+        FitModeFactory.show_supported()
 
-        console.paragraph('Available fit modes')
-        render_table(
-            columns_headers=columns_headers,
-            columns_alignment=columns_alignment,
-            columns_data=columns_data,
-        )
+    def show_current_fit_mode_type(self) -> None:
+        """Print the currently used fit-mode category type."""
+        console.paragraph('Current fit-mode type')
+        console.print(self._fit_mode_type)
 
-    def show_current_fit_mode(self) -> None:
-        """Print the currently active fitting strategy."""
-        console.paragraph('Current fit mode')
-        console.print(self.fit_mode)
+    # ------------------------------------------------------------------
+    #  Joint-fit experiments (category)
+    # ------------------------------------------------------------------
+
+    @property
+    def joint_fit_experiments(self):
+        """Per-experiment weight collection for joint fitting."""
+        return self._joint_fit_experiments
 
     def show_constraints(self) -> None:
         """Print a table of all user-defined symbolic constraints."""
@@ -519,9 +607,9 @@ def fit(self):
             project.analysis.fit()
             project.analysis.show_fit_results()  # Display results
         """
-        sample_models = self.project.sample_models
-        if not sample_models:
-            log.warning('No sample models found in the project. Cannot run fit.')
+        structures = self.project.structures
+        if not structures:
+            log.warning('No structures found in the project. Cannot run fit.')
             return
 
         experiments = self.project.experiments
@@ -530,23 +618,26 @@ def fit(self):
             return
 
         # Run the fitting process
-        if self.fit_mode == 'joint':
+        mode = FitModeEnum(self._fit_mode.mode.value)
+        if mode is FitModeEnum.JOINT:
+            # Auto-populate joint_fit_experiments if empty
+            if not len(self._joint_fit_experiments):
+                for id in experiments.names:
+                    self._joint_fit_experiments.create(id=id, weight=0.5)
             console.paragraph(
-                f"Using all experiments 🔬 {experiments.names} for '{self.fit_mode}' fitting"
+                f"Using all experiments 🔬 {experiments.names} for '{mode.value}' fitting"
             )
             self.fitter.fit(
-                sample_models,
+                structures,
                 experiments,
-                weights=self.joint_fit_experiments,
+                weights=self._joint_fit_experiments,
                 analysis=self,
             )
-        elif self.fit_mode == 'single':
+        elif mode is FitModeEnum.SINGLE:
             # TODO: Find a better way without creating dummy
             #  experiments?
             for expt_name in experiments.names:
-                console.paragraph(
-                    f"Using experiment 🔬 '{expt_name}' for '{self.fit_mode}' fitting"
-                )
+                console.paragraph(f"Using experiment 🔬 '{expt_name}' for '{mode.value}' fitting")
                 experiment = experiments[expt_name]
                 dummy_experiments = Experiments()  # TODO: Find a better name
 
@@ -555,14 +646,14 @@ def fit(self):
                 # parameters can be resolved correctly during fitting.
                 object.__setattr__(dummy_experiments, '_parent', self.project)
 
-                dummy_experiments._add(experiment)
+                dummy_experiments.add(experiment)
                 self.fitter.fit(
-                    sample_models,
+                    structures,
                     dummy_experiments,
                     analysis=self,
                 )
         else:
-            raise NotImplementedError(f'Fit mode {self.fit_mode} not implemented yet.')
+            raise NotImplementedError(f'Fit mode {mode.value} not implemented yet.')
 
         # After fitting, get the results
         self.fit_results = self.fitter.results
@@ -586,10 +677,10 @@ def show_fit_results(self) -> None:
             log.warning('No fit results available. Run fit() first.')
             return
 
-        sample_models = self.project.sample_models
+        structures = self.project.structures
         experiments = self.project.experiments
 
-        self.fitter._process_fit_results(sample_models, experiments)
+        self.fitter._process_fit_results(structures, experiments)
 
     def _update_categories(self, called_by_minimizer=False) -> None:
         """Update all categories owned by Analysis.
diff --git a/src/easydiffraction/analysis/calculators/__init__.py b/src/easydiffraction/analysis/calculators/__init__.py
index 429f2648..5874b1a4 100644
--- a/src/easydiffraction/analysis/calculators/__init__.py
+++ b/src/easydiffraction/analysis/calculators/__init__.py
@@ -1,2 +1,6 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.analysis.calculators.crysfml import CrysfmlCalculator
+from easydiffraction.analysis.calculators.cryspy import CryspyCalculator
+from easydiffraction.analysis.calculators.pdffit import PdffitCalculator
diff --git a/src/easydiffraction/analysis/calculators/base.py b/src/easydiffraction/analysis/calculators/base.py
index f4d99c4d..5ebd3086 100644
--- a/src/easydiffraction/analysis/calculators/base.py
+++ b/src/easydiffraction/analysis/calculators/base.py
@@ -6,9 +6,9 @@
 
 import numpy as np
 
-from easydiffraction.experiments.experiment.base import ExperimentBase
-from easydiffraction.sample_models.sample_model.base import SampleModelBase
-from easydiffraction.sample_models.sample_models import SampleModels
+from easydiffraction.datablocks.experiment.item.base import ExperimentBase
+from easydiffraction.datablocks.structure.collection import Structures
+from easydiffraction.datablocks.structure.item.base import Structure
 
 
 class CalculatorBase(ABC):
@@ -27,11 +27,11 @@ def engine_imported(self) -> bool:
     @abstractmethod
     def calculate_structure_factors(
         self,
-        sample_model: SampleModelBase,
+        structure: Structure,
         experiment: ExperimentBase,
         called_by_minimizer: bool,
     ) -> None:
-        """Calculate structure factors for a single sample model and
+        """Calculate structure factors for a single structure and
         experiment.
         """
         pass
@@ -39,15 +39,15 @@ def calculate_structure_factors(
     @abstractmethod
     def calculate_pattern(
         self,
-        sample_model: SampleModels,  # TODO: SampleModelBase?
+        structure: Structures,  # TODO: Structure?
         experiment: ExperimentBase,
         called_by_minimizer: bool,
     ) -> np.ndarray:
-        """Calculate the diffraction pattern for a single sample model
-        and experiment.
+        """Calculate the diffraction pattern for a single structure and
+        experiment.
 
         Args:
-            sample_model: The sample model object.
+            structure: The structure object.
             experiment: The experiment object.
             called_by_minimizer: Whether the calculation is called by a
                 minimizer.
diff --git a/src/easydiffraction/analysis/calculators/crysfml.py b/src/easydiffraction/analysis/calculators/crysfml.py
index babd3e08..5d47300e 100644
--- a/src/easydiffraction/analysis/calculators/crysfml.py
+++ b/src/easydiffraction/analysis/calculators/crysfml.py
@@ -9,10 +9,12 @@
 import numpy as np
 
 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_model.base import SampleModelBase
-from easydiffraction.sample_models.sample_models import SampleModels
+from easydiffraction.analysis.calculators.factory import CalculatorFactory
+from easydiffraction.core.metadata import TypeInfo
+from easydiffraction.datablocks.experiment.collection import Experiments
+from easydiffraction.datablocks.experiment.item.base import ExperimentBase
+from easydiffraction.datablocks.structure.collection import Structures
+from easydiffraction.datablocks.structure.item.base import Structure
 
 try:
     from pycrysfml import cfml_py_utilities
@@ -27,9 +29,14 @@
     cfml_py_utilities = None
 
 
+@CalculatorFactory.register
 class CrysfmlCalculator(CalculatorBase):
     """Wrapper for Crysfml library."""
 
+    type_info = TypeInfo(
+        tag='crysfml',
+        description='CrysFML library for crystallographic calculations',
+    )
     engine_imported: bool = cfml_py_utilities is not None
 
     @property
@@ -38,13 +45,13 @@ def name(self) -> str:
 
     def calculate_structure_factors(
         self,
-        sample_models: SampleModels,
+        structures: Structures,
         experiments: Experiments,
     ) -> None:
         """Call Crysfml to calculate structure factors.
 
         Args:
-            sample_models: The sample models to calculate structure
+            structures: The structures to calculate structure
                 factors for.
             experiments: The experiments associated with the sample
                 models.
@@ -53,16 +60,16 @@ def calculate_structure_factors(
 
     def calculate_pattern(
         self,
-        sample_model: SampleModels,
+        structure: Structures,
         experiment: ExperimentBase,
         called_by_minimizer: bool = False,
     ) -> Union[np.ndarray, List[float]]:
         """Calculates the diffraction pattern using Crysfml for the
-        given sample model and experiment.
+        given structure and experiment.
 
         Args:
-            sample_model: The sample model to calculate the pattern for.
-            experiment: The experiment associated with the sample model.
+            structure: The structure to calculate the pattern for.
+            experiment: The experiment associated with the structure.
             called_by_minimizer: Whether the calculation is called by a
             minimizer.
 
@@ -73,7 +80,7 @@ def calculate_pattern(
         # Intentionally unused, required by public API/signature
         del called_by_minimizer
 
-        crysfml_dict = self._crysfml_dict(sample_model, experiment)
+        crysfml_dict = self._crysfml_dict(structure, experiment)
         try:
             _, y = cfml_py_utilities.cw_powder_pattern_from_dict(crysfml_dict)
             y = self._adjust_pattern_length(y, len(experiment.data.x))
@@ -104,53 +111,53 @@ def _adjust_pattern_length(
 
     def _crysfml_dict(
         self,
-        sample_model: SampleModels,
+        structure: Structures,
         experiment: ExperimentBase,
-    ) -> Dict[str, Union[ExperimentBase, SampleModelBase]]:
-        """Converts the sample model and experiment into a dictionary
+    ) -> Dict[str, Union[ExperimentBase, Structure]]:
+        """Converts the structure and experiment into a dictionary
         format for Crysfml.
 
         Args:
-            sample_model: The sample model to convert.
+            structure: The structure to convert.
             experiment: The experiment to convert.
 
         Returns:
-            A dictionary representation of the sample model and
+            A dictionary representation of the structure and
                 experiment.
         """
-        sample_model_dict = self._convert_sample_model_to_dict(sample_model)
+        structure_dict = self._convert_structure_to_dict(structure)
         experiment_dict = self._convert_experiment_to_dict(experiment)
         return {
-            'phases': [sample_model_dict],
+            'phases': [structure_dict],
             'experiments': [experiment_dict],
         }
 
-    def _convert_sample_model_to_dict(
+    def _convert_structure_to_dict(
         self,
-        sample_model: SampleModelBase,
+        structure: Structure,
     ) -> Dict[str, Any]:
-        """Converts a sample model into a dictionary format.
+        """Converts a structure into a dictionary format.
 
         Args:
-            sample_model: The sample model to convert.
+            structure: The structure to convert.
 
         Returns:
-            A dictionary representation of the sample model.
+            A dictionary representation of the structure.
         """
-        sample_model_dict = {
-            sample_model.name: {
-                '_space_group_name_H-M_alt': sample_model.space_group.name_h_m.value,
-                '_cell_length_a': sample_model.cell.length_a.value,
-                '_cell_length_b': sample_model.cell.length_b.value,
-                '_cell_length_c': sample_model.cell.length_c.value,
-                '_cell_angle_alpha': sample_model.cell.angle_alpha.value,
-                '_cell_angle_beta': sample_model.cell.angle_beta.value,
-                '_cell_angle_gamma': sample_model.cell.angle_gamma.value,
+        structure_dict = {
+            structure.name: {
+                '_space_group_name_H-M_alt': structure.space_group.name_h_m.value,
+                '_cell_length_a': structure.cell.length_a.value,
+                '_cell_length_b': structure.cell.length_b.value,
+                '_cell_length_c': structure.cell.length_c.value,
+                '_cell_angle_alpha': structure.cell.angle_alpha.value,
+                '_cell_angle_beta': structure.cell.angle_beta.value,
+                '_cell_angle_gamma': structure.cell.angle_gamma.value,
                 '_atom_site': [],
             }
         }
 
-        for atom in sample_model.atom_sites:
+        for atom in structure.atom_sites:
             atom_site = {
                 '_label': atom.label.value,
                 '_type_symbol': atom.type_symbol.value,
@@ -161,9 +168,9 @@ def _convert_sample_model_to_dict(
                 '_adp_type': 'Biso',  # Assuming Biso for simplicity
                 '_B_iso_or_equiv': atom.b_iso.value,
             }
-            sample_model_dict[sample_model.name]['_atom_site'].append(atom_site)
+            structure_dict[structure.name]['_atom_site'].append(atom_site)
 
-        return sample_model_dict
+        return structure_dict
 
     def _convert_experiment_to_dict(
         self,
diff --git a/src/easydiffraction/analysis/calculators/cryspy.py b/src/easydiffraction/analysis/calculators/cryspy.py
index 4e5e9b2a..ad09dc2a 100644
--- a/src/easydiffraction/analysis/calculators/cryspy.py
+++ b/src/easydiffraction/analysis/calculators/cryspy.py
@@ -12,10 +12,12 @@
 import numpy as np
 
 from easydiffraction.analysis.calculators.base import CalculatorBase
-from easydiffraction.experiments.experiment.base import ExperimentBase
-from easydiffraction.experiments.experiment.enums import BeamModeEnum
-from easydiffraction.experiments.experiment.enums import SampleFormEnum
-from easydiffraction.sample_models.sample_model.base import SampleModelBase
+from easydiffraction.analysis.calculators.factory import CalculatorFactory
+from easydiffraction.core.metadata import TypeInfo
+from easydiffraction.datablocks.experiment.item.base import ExperimentBase
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.structure.item.base import Structure
 
 try:
     import cryspy
@@ -31,6 +33,7 @@
     cryspy = None
 
 
+@CalculatorFactory.register
 class CryspyCalculator(CalculatorBase):
     """Cryspy-based diffraction calculator.
 
@@ -38,6 +41,10 @@ class CryspyCalculator(CalculatorBase):
     patterns.
     """
 
+    type_info = TypeInfo(
+        tag='cryspy',
+        description='CrysPy library for crystallographic calculations',
+    )
     engine_imported: bool = cryspy is not None
 
     @property
@@ -50,7 +57,7 @@ def __init__(self) -> None:
 
     def calculate_structure_factors(
         self,
-        sample_model: SampleModelBase,
+        structure: Structure,
         experiment: ExperimentBase,
         called_by_minimizer: bool = False,
     ):
@@ -58,23 +65,23 @@ def calculate_structure_factors(
         implemented.
 
         Args:
-            sample_model: The sample model to calculate structure
+            structure: The structure to calculate structure
                 factors for.
             experiment: The experiment associated with the sample
                 models.
             called_by_minimizer: Whether the calculation is called by a
                 minimizer.
         """
-        combined_name = f'{sample_model.name}_{experiment.name}'
+        combined_name = f'{structure.name}_{experiment.name}'
 
         if called_by_minimizer:
             if self._cryspy_dicts and combined_name in self._cryspy_dicts:
-                cryspy_dict = self._recreate_cryspy_dict(sample_model, experiment)
+                cryspy_dict = self._recreate_cryspy_dict(structure, experiment)
             else:
-                cryspy_obj = self._recreate_cryspy_obj(sample_model, experiment)
+                cryspy_obj = self._recreate_cryspy_obj(structure, experiment)
                 cryspy_dict = cryspy_obj.get_dictionary()
         else:
-            cryspy_obj = self._recreate_cryspy_obj(sample_model, experiment)
+            cryspy_obj = self._recreate_cryspy_obj(structure, experiment)
             cryspy_dict = cryspy_obj.get_dictionary()
 
         self._cryspy_dicts[combined_name] = copy.deepcopy(cryspy_dict)
@@ -108,12 +115,12 @@ def calculate_structure_factors(
 
     def calculate_pattern(
         self,
-        sample_model: SampleModelBase,
+        structure: Structure,
         experiment: ExperimentBase,
         called_by_minimizer: bool = False,
     ) -> Union[np.ndarray, List[float]]:
         """Calculates the diffraction pattern using Cryspy for the given
-        sample model and experiment.
+        structure and experiment.
 
         We only recreate the cryspy_obj if this method is
          - NOT called by the minimizer, or
@@ -122,8 +129,8 @@ def calculate_pattern(
         This allows significantly speeding up the calculation
 
         Args:
-            sample_model: The sample model to calculate the pattern for.
-            experiment: The experiment associated with the sample model.
+            structure: The structure to calculate the pattern for.
+            experiment: The experiment associated with the structure.
             called_by_minimizer: Whether the calculation is called by a
                 minimizer.
 
@@ -131,16 +138,16 @@ def calculate_pattern(
             The calculated diffraction pattern as a NumPy array or a
                 list of floats.
         """
-        combined_name = f'{sample_model.name}_{experiment.name}'
+        combined_name = f'{structure.name}_{experiment.name}'
 
         if called_by_minimizer:
             if self._cryspy_dicts and combined_name in self._cryspy_dicts:
-                cryspy_dict = self._recreate_cryspy_dict(sample_model, experiment)
+                cryspy_dict = self._recreate_cryspy_dict(structure, experiment)
             else:
-                cryspy_obj = self._recreate_cryspy_obj(sample_model, experiment)
+                cryspy_obj = self._recreate_cryspy_obj(structure, experiment)
                 cryspy_dict = cryspy_obj.get_dictionary()
         else:
-            cryspy_obj = self._recreate_cryspy_obj(sample_model, experiment)
+            cryspy_obj = self._recreate_cryspy_obj(structure, experiment)
             cryspy_dict = cryspy_obj.get_dictionary()
 
         self._cryspy_dicts[combined_name] = copy.deepcopy(cryspy_dict)
@@ -184,53 +191,53 @@ def calculate_pattern(
 
     def _recreate_cryspy_dict(
         self,
-        sample_model: SampleModelBase,
+        structure: Structure,
         experiment: ExperimentBase,
     ) -> Dict[str, Any]:
-        """Recreates the Cryspy dictionary for the given sample model
-        and experiment.
+        """Recreates the Cryspy dictionary for the given structure and
+        experiment.
 
         Args:
-            sample_model: The sample model to update.
+            structure: The structure to update.
             experiment: The experiment to update.
 
         Returns:
             The updated Cryspy dictionary.
         """
-        combined_name = f'{sample_model.name}_{experiment.name}'
+        combined_name = f'{structure.name}_{experiment.name}'
         cryspy_dict = copy.deepcopy(self._cryspy_dicts[combined_name])
 
-        cryspy_model_id = f'crystal_{sample_model.name}'
+        cryspy_model_id = f'crystal_{structure.name}'
         cryspy_model_dict = cryspy_dict[cryspy_model_id]
 
         ################################
-        # Update sample model parameters
+        # Update structure parameters
         ################################
 
         # Cell
         cryspy_cell = cryspy_model_dict['unit_cell_parameters']
-        cryspy_cell[0] = sample_model.cell.length_a.value
-        cryspy_cell[1] = sample_model.cell.length_b.value
-        cryspy_cell[2] = sample_model.cell.length_c.value
-        cryspy_cell[3] = np.deg2rad(sample_model.cell.angle_alpha.value)
-        cryspy_cell[4] = np.deg2rad(sample_model.cell.angle_beta.value)
-        cryspy_cell[5] = np.deg2rad(sample_model.cell.angle_gamma.value)
+        cryspy_cell[0] = structure.cell.length_a.value
+        cryspy_cell[1] = structure.cell.length_b.value
+        cryspy_cell[2] = structure.cell.length_c.value
+        cryspy_cell[3] = np.deg2rad(structure.cell.angle_alpha.value)
+        cryspy_cell[4] = np.deg2rad(structure.cell.angle_beta.value)
+        cryspy_cell[5] = np.deg2rad(structure.cell.angle_gamma.value)
 
         # Atomic coordinates
         cryspy_xyz = cryspy_model_dict['atom_fract_xyz']
-        for idx, atom_site in enumerate(sample_model.atom_sites):
+        for idx, atom_site in enumerate(structure.atom_sites):
             cryspy_xyz[0][idx] = atom_site.fract_x.value
             cryspy_xyz[1][idx] = atom_site.fract_y.value
             cryspy_xyz[2][idx] = atom_site.fract_z.value
 
         # Atomic occupancies
         cryspy_occ = cryspy_model_dict['atom_occupancy']
-        for idx, atom_site in enumerate(sample_model.atom_sites):
+        for idx, atom_site in enumerate(structure.atom_sites):
             cryspy_occ[idx] = atom_site.occupancy.value
 
         # Atomic ADPs - Biso only for now
         cryspy_biso = cryspy_model_dict['atom_b_iso']
-        for idx, atom_site in enumerate(sample_model.atom_sites):
+        for idx, atom_site in enumerate(structure.atom_sites):
             cryspy_biso[idx] = atom_site.b_iso.value
 
         ##############################
@@ -298,14 +305,14 @@ def _recreate_cryspy_dict(
 
     def _recreate_cryspy_obj(
         self,
-        sample_model: SampleModelBase,
+        structure: Structure,
         experiment: ExperimentBase,
     ) -> Any:
-        """Recreates the Cryspy object for the given sample model and
+        """Recreates the Cryspy object for the given structure and
         experiment.
 
         Args:
-            sample_model: The sample model to recreate.
+            structure: The structure to recreate.
             experiment: The experiment to recreate.
 
         Returns:
@@ -313,14 +320,14 @@ def _recreate_cryspy_obj(
         """
         cryspy_obj = str_to_globaln('')
 
-        cryspy_sample_model_cif = self._convert_sample_model_to_cryspy_cif(sample_model)
-        cryspy_sample_model_obj = str_to_globaln(cryspy_sample_model_cif)
-        cryspy_obj.add_items(cryspy_sample_model_obj.items)
+        cryspy_structure_cif = self._convert_structure_to_cryspy_cif(structure)
+        cryspy_structure_obj = str_to_globaln(cryspy_structure_cif)
+        cryspy_obj.add_items(cryspy_structure_obj.items)
 
         # Add single experiment to cryspy_obj
         cryspy_experiment_cif = self._convert_experiment_to_cryspy_cif(
             experiment,
-            linked_sample_model=sample_model,
+            linked_structure=structure,
         )
 
         cryspy_experiment_obj = str_to_globaln(cryspy_experiment_cif)
@@ -328,30 +335,30 @@ def _recreate_cryspy_obj(
 
         return cryspy_obj
 
-    def _convert_sample_model_to_cryspy_cif(
+    def _convert_structure_to_cryspy_cif(
         self,
-        sample_model: SampleModelBase,
+        structure: Structure,
     ) -> str:
-        """Converts a sample model to a Cryspy CIF string.
+        """Converts a structure to a Cryspy CIF string.
 
         Args:
-            sample_model: The sample model to convert.
+            structure: The structure to convert.
 
         Returns:
-            The Cryspy CIF string representation of the sample model.
+            The Cryspy CIF string representation of the structure.
         """
-        return sample_model.as_cif
+        return structure.as_cif
 
     def _convert_experiment_to_cryspy_cif(
         self,
         experiment: ExperimentBase,
-        linked_sample_model: Any,
+        linked_structure: Any,
     ) -> str:
         """Converts an experiment to a Cryspy CIF string.
 
         Args:
             experiment: The experiment to convert.
-            linked_sample_model: The sample model linked to the
+            linked_structure: The structure linked to the
                 experiment.
 
         Returns:
@@ -487,14 +494,14 @@ def _convert_experiment_to_cryspy_cif(
         # Add phase data
         if expt_type.sample_form.value == SampleFormEnum.SINGLE_CRYSTAL:
             cif_lines.append('')
-            cif_lines.append(f'_phase_label {linked_sample_model.name}')
+            cif_lines.append(f'_phase_label {linked_structure.name}')
             cif_lines.append('_phase_scale 1.0')
         elif expt_type.sample_form.value == SampleFormEnum.POWDER:
             cif_lines.append('')
             cif_lines.append('loop_')
             cif_lines.append('_phase_label')
             cif_lines.append('_phase_scale')
-            cif_lines.append(f'{linked_sample_model.name} 1.0')
+            cif_lines.append(f'{linked_structure.name} 1.0')
 
         # Add background data
         if expt_type.sample_form.value == SampleFormEnum.POWDER:
diff --git a/src/easydiffraction/analysis/calculators/factory.py b/src/easydiffraction/analysis/calculators/factory.py
index 79b4eba9..fa3812da 100644
--- a/src/easydiffraction/analysis/calculators/factory.py
+++ b/src/easydiffraction/analysis/calculators/factory.py
@@ -1,105 +1,38 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
+"""Calculator factory — delegates to ``FactoryBase``.
+
+Overrides ``_supported_map`` to filter out calculators whose engines
+are not importable in the current environment.
+"""
+
+from __future__ import annotations
 
 from typing import Dict
-from typing import List
-from typing import Optional
 from typing import Type
-from typing import Union
 
-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.logging import console
-from easydiffraction.utils.logging import log
-from easydiffraction.utils.utils import render_table
+from easydiffraction.core.factory import FactoryBase
+from easydiffraction.datablocks.experiment.item.enums import CalculatorEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
 
 
-class CalculatorFactory:
+class CalculatorFactory(FactoryBase):
     """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.
+    Only calculators whose ``engine_imported`` flag is ``True`` are
+    available for creation.
     """
 
-    _potential_calculators: Dict[str, Dict[str, Union[str, Type[CalculatorBase]]]] = {
-        'crysfml': {
-            'description': 'CrysFML library for crystallographic calculations',
-            'class': CrysfmlCalculator,
-        },
-        'cryspy': {
-            'description': 'CrysPy library for crystallographic calculations',
-            'class': CryspyCalculator,
-        },
-        'pdffit': {
-            'description': 'PDFfit2 library for pair distribution function calculations',
-            'class': PdffitCalculator,
-        },
+    _default_rules = {
+        frozenset({
+            ('scattering_type', ScatteringTypeEnum.BRAGG),
+        }): CalculatorEnum.CRYSPY,
+        frozenset({
+            ('scattering_type', ScatteringTypeEnum.TOTAL),
+        }): CalculatorEnum.PDFFIT,
     }
 
     @classmethod
-    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()
-            if cfg['class']().engine_imported  # instantiate and check the @property
-        }
-
-    @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]] = []
-        for name, config in cls._supported_calculators().items():
-            description: str = config.get('description', 'No description provided.')
-            columns_data.append([name, description])
-
-        console.paragraph('Supported calculators')
-        render_table(
-            columns_headers=columns_headers,
-            columns_alignment=columns_alignment,
-            columns_data=columns_data,
-        )
-
-    @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:
-            log.warning(
-                f"Unknown calculator '{calculator_name}', "
-                f'Supported calculators: {cls.list_supported_calculators()}'
-            )
-            return None
-
-        return config['class']()
+    def _supported_map(cls) -> Dict[str, Type]:
+        """Only include calculators whose engines are importable."""
+        return {klass.type_info.tag: klass for klass in cls._registry if klass.engine_imported}
diff --git a/src/easydiffraction/analysis/calculators/pdffit.py b/src/easydiffraction/analysis/calculators/pdffit.py
index 9f099ff6..debd90de 100644
--- a/src/easydiffraction/analysis/calculators/pdffit.py
+++ b/src/easydiffraction/analysis/calculators/pdffit.py
@@ -14,8 +14,10 @@
 import numpy as np
 
 from easydiffraction.analysis.calculators.base import CalculatorBase
-from easydiffraction.experiments.experiment.base import ExperimentBase
-from easydiffraction.sample_models.sample_model.base import SampleModelBase
+from easydiffraction.analysis.calculators.factory import CalculatorFactory
+from easydiffraction.core.metadata import TypeInfo
+from easydiffraction.datablocks.experiment.item.base import ExperimentBase
+from easydiffraction.datablocks.structure.item.base import Structure
 
 try:
     from diffpy.pdffit2 import PdfFit
@@ -38,25 +40,30 @@
     PdfFit = None
 
 
+@CalculatorFactory.register
 class PdffitCalculator(CalculatorBase):
     """Wrapper for Pdffit library."""
 
+    type_info = TypeInfo(
+        tag='pdffit',
+        description='PDFfit2 for pair distribution function calculations',
+    )
     engine_imported: bool = PdfFit is not None
 
     @property
     def name(self):
         return 'pdffit'
 
-    def calculate_structure_factors(self, sample_models, experiments):
+    def calculate_structure_factors(self, structures, experiments):
         # PDF doesn't compute HKL but we keep interface consistent
         # Intentionally unused, required by public API/signature
-        del sample_models, experiments
+        del structures, experiments
         print('[pdffit] Calculating HKLs (not applicable)...')
         return []
 
     def calculate_pattern(
         self,
-        sample_model: SampleModelBase,
+        structure: Structure,
         experiment: ExperimentBase,
         called_by_minimizer: bool = False,
     ):
@@ -67,12 +74,12 @@ def calculate_pattern(
         calculator = PdfFit()
 
         # ---------------------------
-        # Set sample model parameters
+        # Set structure parameters
         # ---------------------------
 
         # 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
+        # Convert the structure to CIF supported by PDFfit
+        cif_string_v2 = structure.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.
@@ -80,18 +87,18 @@ def calculate_pattern(
         cif_string_v1 = re.sub(pattern, '_', cif_string_v2)
 
         # Create the PDFit structure
-        structure = pdffit_cif_parser().parse(cif_string_v1)
+        pdffit_structure = pdffit_cif_parser().parse(cif_string_v1)
 
         # Set all model parameters:
         # space group, cell parameters, and atom sites (including ADPs)
-        calculator.add_structure(structure)
+        calculator.add_structure(pdffit_structure)
 
         # -------------------------
         # Set experiment parameters
         # -------------------------
 
         # Set some peak-related parameters
-        calculator.setvar('pscale', experiment.linked_phases[sample_model.name].scale.value)
+        calculator.setvar('pscale', experiment.linked_phases[structure.name].scale.value)
         calculator.setvar('delta1', experiment.peak.sharp_delta_1.value)
         calculator.setvar('delta2', experiment.peak.sharp_delta_2.value)
         calculator.setvar('spdiameter', experiment.peak.damp_particle_diameter.value)
diff --git a/src/easydiffraction/analysis/categories/aliases/__init__.py b/src/easydiffraction/analysis/categories/aliases/__init__.py
new file mode 100644
index 00000000..aa72de6b
--- /dev/null
+++ b/src/easydiffraction/analysis/categories/aliases/__init__.py
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.analysis.categories.aliases.default import Alias
+from easydiffraction.analysis.categories.aliases.default import Aliases
diff --git a/src/easydiffraction/analysis/categories/aliases.py b/src/easydiffraction/analysis/categories/aliases/default.py
similarity index 58%
rename from src/easydiffraction/analysis/categories/aliases.py
rename to src/easydiffraction/analysis/categories/aliases/default.py
index b55db11d..49b263f4 100644
--- a/src/easydiffraction/analysis/categories/aliases.py
+++ b/src/easydiffraction/analysis/categories/aliases/default.py
@@ -6,12 +6,15 @@
 parameters via readable labels instead of raw unique identifiers.
 """
 
+from __future__ import annotations
+
+from easydiffraction.analysis.categories.aliases.factory import AliasesFactory
 from easydiffraction.core.category import CategoryCollection
 from easydiffraction.core.category import CategoryItem
-from easydiffraction.core.parameters import StringDescriptor
+from easydiffraction.core.metadata import TypeInfo
 from easydiffraction.core.validation import AttributeSpec
-from easydiffraction.core.validation import DataTypes
 from easydiffraction.core.validation import RegexValidator
+from easydiffraction.core.variable import StringDescriptor
 from easydiffraction.io.cif.handler import CifHandler
 
 
@@ -27,80 +30,61 @@ class Alias(CategoryItem):
             ``label``.
     """
 
-    def __init__(
-        self,
-        *,
-        label: str,
-        param_uid: str,
-    ) -> None:
+    def __init__(self) -> None:
         super().__init__()
 
-        self._label: StringDescriptor = StringDescriptor(
+        self._label = StringDescriptor(
             name='label',
-            description='...',
+            description='...',  # TODO
             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',
-                ]
+                default='_',
+                validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'),
             ),
+            cif_handler=CifHandler(names=['_alias.label']),
         )
-        self._param_uid: StringDescriptor = StringDescriptor(
+        self._param_uid = StringDescriptor(
             name='param_uid',
-            description='...',
+            description='...',  # TODO
             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',
-                ]
+                default='_',
+                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: str(self.label.value)
 
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
     @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
 
 
+@AliasesFactory.register
 class Aliases(CategoryCollection):
     """Collection of :class:`Alias` items."""
 
+    type_info = TypeInfo(
+        tag='default',
+        description='Parameter alias mappings',
+    )
+
     def __init__(self):
         """Create an empty collection of aliases."""
         super().__init__(item_type=Alias)
diff --git a/src/easydiffraction/analysis/categories/aliases/factory.py b/src/easydiffraction/analysis/categories/aliases/factory.py
new file mode 100644
index 00000000..07e1fe38
--- /dev/null
+++ b/src/easydiffraction/analysis/categories/aliases/factory.py
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Aliases factory — delegates entirely to ``FactoryBase``."""
+
+from __future__ import annotations
+
+from easydiffraction.core.factory import FactoryBase
+
+
+class AliasesFactory(FactoryBase):
+    """Create alias collections by tag."""
+
+    _default_rules = {
+        frozenset(): 'default',
+    }
diff --git a/src/easydiffraction/analysis/categories/constraints/__init__.py b/src/easydiffraction/analysis/categories/constraints/__init__.py
new file mode 100644
index 00000000..ded70ca6
--- /dev/null
+++ b/src/easydiffraction/analysis/categories/constraints/__init__.py
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.analysis.categories.constraints.default import Constraint
+from easydiffraction.analysis.categories.constraints.default import Constraints
diff --git a/src/easydiffraction/analysis/categories/constraints.py b/src/easydiffraction/analysis/categories/constraints/default.py
similarity index 58%
rename from src/easydiffraction/analysis/categories/constraints.py
rename to src/easydiffraction/analysis/categories/constraints/default.py
index 205a28b1..647dd273 100644
--- a/src/easydiffraction/analysis/categories/constraints.py
+++ b/src/easydiffraction/analysis/categories/constraints/default.py
@@ -6,13 +6,16 @@
 ``rhs_expr`` is evaluated elsewhere by the analysis engine.
 """
 
+from __future__ import annotations
+
+from easydiffraction.analysis.categories.constraints.factory import ConstraintsFactory
 from easydiffraction.core.category import CategoryCollection
 from easydiffraction.core.category import CategoryItem
-from easydiffraction.core.parameters import StringDescriptor
-from easydiffraction.core.singletons import ConstraintsHandler
+from easydiffraction.core.metadata import TypeInfo
+from easydiffraction.core.singleton import ConstraintsHandler
 from easydiffraction.core.validation import AttributeSpec
-from easydiffraction.core.validation import DataTypes
 from easydiffraction.core.validation import RegexValidator
+from easydiffraction.core.variable import StringDescriptor
 from easydiffraction.io.cif.handler import CifHandler
 
 
@@ -24,80 +27,61 @@ class Constraint(CategoryItem):
         rhs_expr: Right-hand side expression as a string.
     """
 
-    def __init__(
-        self,
-        *,
-        lhs_alias: str,
-        rhs_expr: str,
-    ) -> None:
+    def __init__(self) -> None:
         super().__init__()
 
-        self._lhs_alias: StringDescriptor = StringDescriptor(
+        self._lhs_alias = StringDescriptor(
             name='lhs_alias',
-            description='...',
+            description='Left-hand side of the equation.',  # TODO
             value_spec=AttributeSpec(
-                value=lhs_alias,
-                type_=DataTypes.STRING,
-                default='...',
-                content_validator=RegexValidator(pattern=r'.*'),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_constraint.lhs_alias',
-                ]
+                default='...',  # TODO
+                validator=RegexValidator(pattern=r'.*'),
             ),
+            cif_handler=CifHandler(names=['_constraint.lhs_alias']),
         )
-        self._rhs_expr: StringDescriptor = StringDescriptor(
+        self._rhs_expr = StringDescriptor(
             name='rhs_expr',
-            description='...',
+            description='Right-hand side expression.',  # TODO
             value_spec=AttributeSpec(
-                value=rhs_expr,
-                type_=DataTypes.STRING,
-                default='...',
-                content_validator=RegexValidator(pattern=r'.*'),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_constraint.rhs_expr',
-                ]
+                default='...',  # TODO
+                validator=RegexValidator(pattern=r'.*'),
             ),
+            cif_handler=CifHandler(names=['_constraint.rhs_expr']),
         )
 
         self._identity.category_code = 'constraint'
         self._identity.category_entry_name = lambda: str(self.lhs_alias.value)
 
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
     @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
 
 
+@ConstraintsFactory.register
 class Constraints(CategoryCollection):
     """Collection of :class:`Constraint` items."""
 
+    type_info = TypeInfo(
+        tag='default',
+        description='Symbolic parameter constraints',
+    )
+
     _update_priority = 90  # After most others, but before data categories
 
     def __init__(self):
diff --git a/src/easydiffraction/analysis/categories/constraints/factory.py b/src/easydiffraction/analysis/categories/constraints/factory.py
new file mode 100644
index 00000000..829260f4
--- /dev/null
+++ b/src/easydiffraction/analysis/categories/constraints/factory.py
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Constraints factory — delegates entirely to ``FactoryBase``."""
+
+from __future__ import annotations
+
+from easydiffraction.core.factory import FactoryBase
+
+
+class ConstraintsFactory(FactoryBase):
+    """Create constraint collections by tag."""
+
+    _default_rules = {
+        frozenset(): 'default',
+    }
diff --git a/src/easydiffraction/analysis/categories/fit_mode/__init__.py b/src/easydiffraction/analysis/categories/fit_mode/__init__.py
new file mode 100644
index 00000000..45267810
--- /dev/null
+++ b/src/easydiffraction/analysis/categories/fit_mode/__init__.py
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.analysis.categories.fit_mode.enums import FitModeEnum
+from easydiffraction.analysis.categories.fit_mode.factory import FitModeFactory
+from easydiffraction.analysis.categories.fit_mode.fit_mode import FitMode
diff --git a/src/easydiffraction/analysis/categories/fit_mode/enums.py b/src/easydiffraction/analysis/categories/fit_mode/enums.py
new file mode 100644
index 00000000..156e9c30
--- /dev/null
+++ b/src/easydiffraction/analysis/categories/fit_mode/enums.py
@@ -0,0 +1,24 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Enumeration for fit-mode values."""
+
+from __future__ import annotations
+
+from enum import Enum
+
+
+class FitModeEnum(str, Enum):
+    """Fitting strategy for the analysis."""
+
+    SINGLE = 'single'
+    JOINT = 'joint'
+
+    @classmethod
+    def default(cls) -> FitModeEnum:
+        return cls.SINGLE
+
+    def description(self) -> str:
+        if self is FitModeEnum.SINGLE:
+            return 'Independent fitting of each experiment; no shared parameters'
+        elif self is FitModeEnum.JOINT:
+            return 'Simultaneous fitting of all experiments; some parameters are shared'
diff --git a/src/easydiffraction/analysis/categories/fit_mode/factory.py b/src/easydiffraction/analysis/categories/fit_mode/factory.py
new file mode 100644
index 00000000..48edef66
--- /dev/null
+++ b/src/easydiffraction/analysis/categories/fit_mode/factory.py
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Fit-mode factory — delegates entirely to ``FactoryBase``."""
+
+from __future__ import annotations
+
+from easydiffraction.core.factory import FactoryBase
+
+
+class FitModeFactory(FactoryBase):
+    """Create fit-mode category items by tag."""
+
+    _default_rules = {
+        frozenset(): 'default',
+    }
diff --git a/src/easydiffraction/analysis/categories/fit_mode/fit_mode.py b/src/easydiffraction/analysis/categories/fit_mode/fit_mode.py
new file mode 100644
index 00000000..8a5bd24b
--- /dev/null
+++ b/src/easydiffraction/analysis/categories/fit_mode/fit_mode.py
@@ -0,0 +1,61 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Fit-mode category item.
+
+Stores the active fitting strategy as a CIF-serializable descriptor
+validated by ``FitModeEnum``.
+"""
+
+from __future__ import annotations
+
+from easydiffraction.analysis.categories.fit_mode.enums import FitModeEnum
+from easydiffraction.analysis.categories.fit_mode.factory import FitModeFactory
+from easydiffraction.core.category import CategoryItem
+from easydiffraction.core.metadata import TypeInfo
+from easydiffraction.core.validation import AttributeSpec
+from easydiffraction.core.validation import MembershipValidator
+from easydiffraction.core.variable import StringDescriptor
+from easydiffraction.io.cif.handler import CifHandler
+
+
+@FitModeFactory.register
+class FitMode(CategoryItem):
+    """Fitting strategy selector.
+
+    Holds a single ``mode`` descriptor whose value is one of
+    ``FitModeEnum`` members (``'single'`` or ``'joint'``).
+    """
+
+    type_info = TypeInfo(
+        tag='default',
+        description='Fit-mode category',
+    )
+
+    def __init__(self) -> None:
+        super().__init__()
+
+        self._mode: StringDescriptor = StringDescriptor(
+            name='mode',
+            description='Fitting strategy',
+            value_spec=AttributeSpec(
+                default=FitModeEnum.default().value,
+                validator=MembershipValidator(allowed=[member.value for member in FitModeEnum]),
+            ),
+            cif_handler=CifHandler(names=['_analysis.fit_mode']),
+        )
+
+        self._identity.category_code = 'fit_mode'
+
+    @property
+    def mode(self):
+        """Active fitting strategy descriptor."""
+        return self._mode
+
+    @mode.setter
+    def mode(self, value: str) -> None:
+        """Set the fitting strategy value.
+
+        Args:
+            value: ``'single'`` or ``'joint'``.
+        """
+        self._mode.value = value
diff --git a/src/easydiffraction/analysis/categories/joint_fit_experiments/__init__.py b/src/easydiffraction/analysis/categories/joint_fit_experiments/__init__.py
new file mode 100644
index 00000000..2857b28d
--- /dev/null
+++ b/src/easydiffraction/analysis/categories/joint_fit_experiments/__init__.py
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.analysis.categories.joint_fit_experiments.default import JointFitExperiment
+from easydiffraction.analysis.categories.joint_fit_experiments.default import JointFitExperiments
diff --git a/src/easydiffraction/analysis/categories/joint_fit_experiments.py b/src/easydiffraction/analysis/categories/joint_fit_experiments/default.py
similarity index 61%
rename from src/easydiffraction/analysis/categories/joint_fit_experiments.py
rename to src/easydiffraction/analysis/categories/joint_fit_experiments/default.py
index 818dd6f6..6acf4f44 100644
--- a/src/easydiffraction/analysis/categories/joint_fit_experiments.py
+++ b/src/easydiffraction/analysis/categories/joint_fit_experiments/default.py
@@ -6,14 +6,19 @@
 fitted simultaneously.
 """
 
+from __future__ import annotations
+
+from easydiffraction.analysis.categories.joint_fit_experiments.factory import (
+    JointFitExperimentsFactory,
+)
 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.metadata import TypeInfo
 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.core.variable import NumericDescriptor
+from easydiffraction.core.variable import StringDescriptor
 from easydiffraction.io.cif.handler import CifHandler
 
 
@@ -25,80 +30,61 @@ class JointFitExperiment(CategoryItem):
         weight: Relative weight factor in the combined objective.
     """
 
-    def __init__(
-        self,
-        *,
-        id: str,
-        weight: float,
-    ) -> None:
+    def __init__(self) -> None:
         super().__init__()
 
         self._id: StringDescriptor = StringDescriptor(
             name='id',  # TODO: need new name instead of id
-            description='...',
+            description='Experiment identifier',  # TODO
             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',
-                ]
+                default='_',
+                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='...',
+            description='Weight factor',  # TODO
             value_spec=AttributeSpec(
-                value=weight,
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_joint_fit_experiment.weight',
-                ]
+                validator=RangeValidator(),
             ),
+            cif_handler=CifHandler(names=['_joint_fit_experiment.weight']),
         )
 
         self._identity.category_code = 'joint_fit_experiment'
         self._identity.category_entry_name = lambda: str(self.id.value)
 
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
     @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
 
 
+@JointFitExperimentsFactory.register
 class JointFitExperiments(CategoryCollection):
     """Collection of :class:`JointFitExperiment` items."""
 
+    type_info = TypeInfo(
+        tag='default',
+        description='Joint-fit experiment weights',
+    )
+
     def __init__(self):
         """Create an empty joint-fit experiments collection."""
         super().__init__(item_type=JointFitExperiment)
diff --git a/src/easydiffraction/analysis/categories/joint_fit_experiments/factory.py b/src/easydiffraction/analysis/categories/joint_fit_experiments/factory.py
new file mode 100644
index 00000000..2919c741
--- /dev/null
+++ b/src/easydiffraction/analysis/categories/joint_fit_experiments/factory.py
@@ -0,0 +1,17 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Joint-fit-experiments factory — delegates entirely to
+``FactoryBase``.
+"""
+
+from __future__ import annotations
+
+from easydiffraction.core.factory import FactoryBase
+
+
+class JointFitExperimentsFactory(FactoryBase):
+    """Create joint-fit experiment collections by tag."""
+
+    _default_rules = {
+        frozenset(): 'default',
+    }
diff --git a/src/easydiffraction/analysis/fit_helpers/metrics.py b/src/easydiffraction/analysis/fit_helpers/metrics.py
index 23d5d42b..97c715d8 100644
--- a/src/easydiffraction/analysis/fit_helpers/metrics.py
+++ b/src/easydiffraction/analysis/fit_helpers/metrics.py
@@ -6,8 +6,8 @@
 
 import numpy as np
 
-from easydiffraction.experiments.experiments import Experiments
-from easydiffraction.sample_models.sample_models import SampleModels
+from easydiffraction.datablocks.experiment.collection import Experiments
+from easydiffraction.datablocks.structure.collection import Structures
 
 
 def calculate_r_factor(
@@ -121,14 +121,14 @@ def calculate_reduced_chi_square(
 
 
 def get_reliability_inputs(
-    sample_models: SampleModels,
+    structures: Structures,
     experiments: Experiments,
 ) -> Tuple[np.ndarray, np.ndarray, Optional[np.ndarray]]:
     """Collect observed and calculated data points for reliability
     calculations.
 
     Args:
-        sample_models: Collection of sample models.
+        structures: Collection of structures.
         experiments: Collection of experiments.
 
     Returns:
@@ -139,8 +139,8 @@ def get_reliability_inputs(
     y_calc_all = []
     y_err_all = []
     for experiment in experiments.values():
-        for sample_model in sample_models:
-            sample_model._update_categories()
+        for structure in structures:
+            structure._update_categories()
         experiment._update_categories()
 
         y_calc = experiment.data.intensity_calc
diff --git a/src/easydiffraction/analysis/fitting.py b/src/easydiffraction/analysis/fitting.py
index 0541318e..453aaf1a 100644
--- a/src/easydiffraction/analysis/fitting.py
+++ b/src/easydiffraction/analysis/fitting.py
@@ -11,9 +11,9 @@
 
 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
+from easydiffraction.core.variable import Parameter
+from easydiffraction.datablocks.experiment.collection import Experiments
+from easydiffraction.datablocks.structure.collection import Structures
 
 if TYPE_CHECKING:
     from easydiffraction.analysis.fit_helpers.reporting import FitResults
@@ -22,15 +22,15 @@
 class Fitter:
     """Handles the fitting workflow using a pluggable minimizer."""
 
-    def __init__(self, selection: str = 'lmfit (leastsq)') -> None:
+    def __init__(self, selection: str = 'lmfit') -> None:
         self.selection: str = selection
-        self.engine: str = selection.split(' ')[0]  # Extracts 'lmfit' or 'dfols'
-        self.minimizer = MinimizerFactory.create_minimizer(selection)
+        self.engine: str = selection
+        self.minimizer = MinimizerFactory.create(selection)
         self.results: Optional[FitResults] = None
 
     def fit(
         self,
-        sample_models: SampleModels,
+        structures: Structures,
         experiments: Experiments,
         weights: Optional[np.array] = None,
         analysis=None,
@@ -42,13 +42,13 @@ def fit(
         to display the fit results after fitting is complete.
 
         Args:
-            sample_models: Collection of sample models.
+            structures: Collection of structures.
             experiments: Collection of experiments.
             weights: Optional weights for joint fitting.
             analysis: Optional Analysis object to update its categories
                 during fitting.
         """
-        params = sample_models.free_parameters + experiments.free_parameters
+        params = structures.free_parameters + experiments.free_parameters
 
         if not params:
             print('⚠️ No parameters selected for fitting.')
@@ -61,7 +61,7 @@ def objective_function(engine_params: Dict[str, Any]) -> np.ndarray:
             return self._residual_function(
                 engine_params=engine_params,
                 parameters=params,
-                sample_models=sample_models,
+                structures=structures,
                 experiments=experiments,
                 weights=weights,
                 analysis=analysis,
@@ -72,7 +72,7 @@ def objective_function(engine_params: Dict[str, Any]) -> np.ndarray:
 
     def _process_fit_results(
         self,
-        sample_models: SampleModels,
+        structures: Structures,
         experiments: Experiments,
     ) -> None:
         """Collect reliability inputs and display fit results.
@@ -83,11 +83,11 @@ def _process_fit_results(
         the console.
 
         Args:
-            sample_models: Collection of sample models.
+            structures: Collection of structures.
             experiments: Collection of experiments.
         """
         y_obs, y_calc, y_err = get_reliability_inputs(
-            sample_models,
+            structures,
             experiments,
         )
 
@@ -105,26 +105,26 @@ def _process_fit_results(
 
     def _collect_free_parameters(
         self,
-        sample_models: SampleModels,
+        structures: Structures,
         experiments: Experiments,
     ) -> List[Parameter]:
-        """Collect free parameters from sample models and experiments.
+        """Collect free parameters from structures and experiments.
 
         Args:
-            sample_models: Collection of sample models.
+            structures: Collection of structures.
             experiments: Collection of experiments.
 
         Returns:
             List of free parameters.
         """
-        free_params: List[Parameter] = sample_models.free_parameters + experiments.free_parameters
+        free_params: List[Parameter] = structures.free_parameters + experiments.free_parameters
         return free_params
 
     def _residual_function(
         self,
         engine_params: Dict[str, Any],
         parameters: List[Parameter],
-        sample_models: SampleModels,
+        structures: Structures,
         experiments: Experiments,
         weights: Optional[np.array] = None,
         analysis=None,
@@ -136,7 +136,7 @@ def _residual_function(
         Args:
             engine_params: Engine-specific parameter dict.
             parameters: List of parameters being optimized.
-            sample_models: Collection of sample models.
+            structures: Collection of structures.
             experiments: Collection of experiments.
             weights: Optional weights for joint fitting.
             analysis: Optional Analysis object to update its categories
@@ -149,10 +149,10 @@ def _residual_function(
         self.minimizer._sync_result_to_parameters(parameters, engine_params)
 
         # Update categories to reflect new parameter values
-        # Order matters: sample models first (symmetry, structure),
+        # Order matters: structures first (symmetry, structure),
         # then analysis (constraints), then experiments (calculations)
-        for sample_model in sample_models:
-            sample_model._update_categories()
+        for structure in structures:
+            structure._update_categories()
 
         if analysis is not None:
             analysis._update_categories(called_by_minimizer=True)
diff --git a/src/easydiffraction/analysis/minimizers/__init__.py b/src/easydiffraction/analysis/minimizers/__init__.py
index 429f2648..8a41a6f2 100644
--- a/src/easydiffraction/analysis/minimizers/__init__.py
+++ b/src/easydiffraction/analysis/minimizers/__init__.py
@@ -1,2 +1,5 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.analysis.minimizers.dfols import DfolsMinimizer
+from easydiffraction.analysis.minimizers.lmfit import LmfitMinimizer
diff --git a/src/easydiffraction/analysis/minimizers/base.py b/src/easydiffraction/analysis/minimizers/base.py
index 275ad973..069601ed 100644
--- a/src/easydiffraction/analysis/minimizers/base.py
+++ b/src/easydiffraction/analysis/minimizers/base.py
@@ -155,7 +155,7 @@ def _objective_function(
         self,
         engine_params: Dict[str, Any],
         parameters: List[Any],
-        sample_models: Any,
+        structures: Any,
         experiments: Any,
         calculator: Any,
     ) -> np.ndarray:
@@ -163,7 +163,7 @@ def _objective_function(
         return self._compute_residuals(
             engine_params,
             parameters,
-            sample_models,
+            structures,
             experiments,
             calculator,
         )
@@ -171,7 +171,7 @@ def _objective_function(
     def _create_objective_function(
         self,
         parameters: List[Any],
-        sample_models: Any,
+        structures: Any,
         experiments: Any,
         calculator: Any,
     ) -> Callable[[Dict[str, Any]], np.ndarray]:
@@ -179,7 +179,7 @@ def _create_objective_function(
         return lambda engine_params: self._objective_function(
             engine_params,
             parameters,
-            sample_models,
+            structures,
             experiments,
             calculator,
         )
diff --git a/src/easydiffraction/analysis/minimizers/dfols.py b/src/easydiffraction/analysis/minimizers/dfols.py
index 544d4b7a..b68a034a 100644
--- a/src/easydiffraction/analysis/minimizers/dfols.py
+++ b/src/easydiffraction/analysis/minimizers/dfols.py
@@ -9,15 +9,23 @@
 from dfols import solve
 
 from easydiffraction.analysis.minimizers.base import MinimizerBase
+from easydiffraction.analysis.minimizers.factory import MinimizerFactory
+from easydiffraction.core.metadata import TypeInfo
 
 DEFAULT_MAX_ITERATIONS = 1000
 
 
+@MinimizerFactory.register
 class DfolsMinimizer(MinimizerBase):
     """Minimizer using the DFO-LS package (Derivative-Free Optimization
     for Least-Squares).
     """
 
+    type_info = TypeInfo(
+        tag='dfols',
+        description='DFO-LS derivative-free least-squares optimization',
+    )
+
     def __init__(
         self,
         name: str = 'dfols',
@@ -59,7 +67,9 @@ def _sync_result_to_parameters(
         result_values = raw_result.x if hasattr(raw_result, 'x') else raw_result
 
         for i, param in enumerate(parameters):
-            param.value = result_values[i]
+            # Bypass validation but set the dirty flag so
+            # _update_categories() knows work is needed.
+            param._set_value_from_minimizer(result_values[i])
             # DFO-LS doesn't provide uncertainties; set to None or
             # calculate later if needed
             param.uncertainty = None
diff --git a/src/easydiffraction/analysis/minimizers/factory.py b/src/easydiffraction/analysis/minimizers/factory.py
index 0b1afaa2..e12a9533 100644
--- a/src/easydiffraction/analysis/minimizers/factory.py
+++ b/src/easydiffraction/analysis/minimizers/factory.py
@@ -1,126 +1,15 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
+"""Minimizer factory — delegates to ``FactoryBase``."""
 
-from typing import Any
-from typing import Dict
-from typing import List
-from typing import Optional
-from typing import Type
+from __future__ import annotations
 
-from easydiffraction.analysis.minimizers.base import MinimizerBase
-from easydiffraction.analysis.minimizers.dfols import DfolsMinimizer
-from easydiffraction.analysis.minimizers.lmfit import LmfitMinimizer
-from easydiffraction.utils.logging import console
-from easydiffraction.utils.utils import render_table
+from easydiffraction.core.factory import FactoryBase
 
 
-class MinimizerFactory:
-    _available_minimizers: Dict[str, Dict[str, Any]] = {
-        'lmfit': {
-            'engine': 'lmfit',
-            'method': 'leastsq',
-            'description': 'LMFIT library using the default Levenberg-Marquardt '
-            'least squares method',
-            'class': LmfitMinimizer,
-        },
-        'lmfit (leastsq)': {
-            'engine': 'lmfit',
-            'method': 'leastsq',
-            'description': 'LMFIT library with Levenberg-Marquardt least squares method',
-            'class': LmfitMinimizer,
-        },
-        'lmfit (least_squares)': {
-            'engine': 'lmfit',
-            'method': 'least_squares',
-            'description': 'LMFIT library with SciPy’s trust region reflective algorithm',
-            'class': LmfitMinimizer,
-        },
-        'dfols': {
-            'engine': 'dfols',
-            'method': None,
-            'description': 'DFO-LS library for derivative-free least-squares optimization',
-            'class': DfolsMinimizer,
-        },
-    }
-
-    @classmethod
-    def list_available_minimizers(cls) -> List[str]:
-        """List all available minimizers.
-
-        Returns:
-            A list of minimizer names.
-        """
-        return list(cls._available_minimizers.keys())
-
-    @classmethod
-    def show_available_minimizers(cls) -> None:
-        # TODO: Rename this method to `show_supported_minimizers` for
-        #  consistency with other methods in the library. E.g.
-        #  `show_supported_calculators`, etc.
-        """Display a table of available minimizers and their
-        descriptions.
-        """
-        columns_headers: List[str] = ['Minimizer', 'Description']
-        columns_alignment = ['left', 'left']
-        columns_data: List[List[str]] = []
-        for name, config in cls._available_minimizers.items():
-            description: str = config.get('description', 'No description provided.')
-            columns_data.append([name, description])
-
-        console.paragraph('Supported minimizers')
-        render_table(
-            columns_headers=columns_headers,
-            columns_alignment=columns_alignment,
-            columns_data=columns_data,
-        )
-
-    @classmethod
-    def create_minimizer(cls, selection: str) -> MinimizerBase:
-        """Create a minimizer instance based on the selection.
-
-        Args:
-            selection: The name of the minimizer to create.
+class MinimizerFactory(FactoryBase):
+    """Factory for creating minimizer instances."""
 
-        Returns:
-            An instance of the selected minimizer.
-
-        Raises:
-            ValueError: If the selection is not a valid minimizer.
-        """
-        config = cls._available_minimizers.get(selection)
-        if not config:
-            raise ValueError(
-                f"Unknown minimizer '{selection}'. Use one of {cls.list_available_minimizers()}"
-            )
-
-        minimizer_class: Type[MinimizerBase] = config.get('class')
-        method: Optional[str] = config.get('method')
-
-        kwargs: Dict[str, Any] = {}
-        if method is not None:
-            kwargs['method'] = method
-
-        return minimizer_class(**kwargs)
-
-    @classmethod
-    def register_minimizer(
-        cls,
-        name: str,
-        minimizer_cls: Type[MinimizerBase],
-        method: Optional[str] = None,
-        description: str = 'No description provided.',
-    ) -> None:
-        """Register a new minimizer.
-
-        Args:
-            name: The name of the minimizer.
-            minimizer_cls: The class of the minimizer.
-            method: The method used by the minimizer (optional).
-            description: A description of the minimizer.
-        """
-        cls._available_minimizers[name] = {
-            'engine': name,
-            'method': method,
-            'description': description,
-            'class': minimizer_cls,
-        }
+    _default_rules = {
+        frozenset(): 'lmfit',
+    }
diff --git a/src/easydiffraction/analysis/minimizers/lmfit.py b/src/easydiffraction/analysis/minimizers/lmfit.py
index d7a651cb..cb0a59fd 100644
--- a/src/easydiffraction/analysis/minimizers/lmfit.py
+++ b/src/easydiffraction/analysis/minimizers/lmfit.py
@@ -8,14 +8,22 @@
 import lmfit
 
 from easydiffraction.analysis.minimizers.base import MinimizerBase
+from easydiffraction.analysis.minimizers.factory import MinimizerFactory
+from easydiffraction.core.metadata import TypeInfo
 
 DEFAULT_METHOD = 'leastsq'
 DEFAULT_MAX_ITERATIONS = 1000
 
 
+@MinimizerFactory.register
 class LmfitMinimizer(MinimizerBase):
     """Minimizer using the lmfit package."""
 
+    type_info = TypeInfo(
+        tag='lmfit',
+        description='LMFIT with Levenberg-Marquardt least squares',
+    )
+
     def __init__(
         self,
         name: str = 'lmfit',
@@ -88,7 +96,9 @@ def _sync_result_to_parameters(
         for param in parameters:
             param_result = param_values.get(param._minimizer_uid)
             if param_result is not None:
-                param._value = param_result.value  # Bypass ranges check
+                # Bypass validation but set the dirty flag so
+                # _update_categories() knows work is needed.
+                param._set_value_from_minimizer(param_result.value)
                 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
index 6be057da..d4b57dc3 100644
--- a/src/easydiffraction/core/category.py
+++ b/src/easydiffraction/core/category.py
@@ -5,13 +5,15 @@
 
 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.core.variable import GenericDescriptorBase
+from easydiffraction.core.variable import GenericStringDescriptor
 from easydiffraction.io.cif.serialize import category_collection_from_cif
 from easydiffraction.io.cif.serialize import category_collection_to_cif
 from easydiffraction.io.cif.serialize import category_item_from_cif
 from easydiffraction.io.cif.serialize import category_item_to_cif
 
+# ======================================================================
+
 
 class CategoryItem(GuardedBase):
     """Base class for items in a category collection."""
@@ -56,6 +58,106 @@ def from_cif(self, block, idx=0):
         """Populate this item from a CIF block."""
         category_item_from_cif(self, block, idx)
 
+    def help(self) -> None:
+        """Print parameters, other properties, and methods."""
+        from easydiffraction.utils.logging import console
+        from easydiffraction.utils.utils import render_table
+
+        cls = type(self)
+        console.paragraph(f"Help for '{cls.__name__}'")
+
+        # Deduplicate properties
+        seen: dict = {}
+        for key, prop in cls._iter_properties():
+            if key not in seen:
+                seen[key] = prop
+
+        # Split into descriptor-backed and other
+        param_rows = []
+        other_rows = []
+        p_idx = 0
+        o_idx = 0
+        for key in sorted(seen):
+            prop = seen[key]
+            try:
+                val = getattr(self, key)
+            except Exception:
+                val = None
+            if isinstance(val, GenericDescriptorBase):
+                p_idx += 1
+                type_str = 'string' if isinstance(val, GenericStringDescriptor) else 'numeric'
+                writable = '✓' if prop.fset else '✗'
+                param_rows.append([
+                    str(p_idx),
+                    key,
+                    type_str,
+                    str(val.value),
+                    writable,
+                    val.description or '',
+                ])
+            else:
+                o_idx += 1
+                writable = '✓' if prop.fset else '✗'
+                doc = self._first_sentence(prop.fget.__doc__ if prop.fget else None)
+                other_rows.append([str(o_idx), key, writable, doc])
+
+        if param_rows:
+            console.paragraph('Parameters')
+            render_table(
+                columns_headers=[
+                    '#',
+                    'Name',
+                    'Type',
+                    'Value',
+                    'Writable',
+                    'Description',
+                ],
+                columns_alignment=[
+                    'right',
+                    'left',
+                    'left',
+                    'right',
+                    'center',
+                    'left',
+                ],
+                columns_data=param_rows,
+            )
+
+        if other_rows:
+            console.paragraph('Other properties')
+            render_table(
+                columns_headers=[
+                    '#',
+                    'Name',
+                    'Writable',
+                    'Description',
+                ],
+                columns_alignment=[
+                    'right',
+                    'left',
+                    'center',
+                    'left',
+                ],
+                columns_data=other_rows,
+            )
+
+        methods = dict(cls._iter_methods())
+        method_rows = []
+        for i, key in enumerate(sorted(methods), 1):
+            doc = self._first_sentence(getattr(methods[key], '__doc__', None))
+            method_rows.append([str(i), f'{key}()', doc])
+
+        if method_rows:
+            console.paragraph('Methods')
+            render_table(
+                columns_headers=['#', 'Name', 'Description'],
+                columns_alignment=['right', 'left', 'left'],
+                columns_data=method_rows,
+            )
+
+
+# ======================================================================
+
 
 class CategoryCollection(CollectionBase):
     """Handles loop-style category containers (e.g. AtomSites).
@@ -66,6 +168,21 @@ class CategoryCollection(CollectionBase):
     # TODO: Common for all categories
     _update_priority = 10  # Default. Lower values run first.
 
+    def _key_for(self, item):
+        """Return the category-level identity key for *item*."""
+        return item._identity.category_entry_name
+
+    def _mark_parent_dirty(self) -> None:
+        """Set ``_need_categories_update`` on the parent datablock.
+
+        Called whenever the collection content changes (items added or
+        removed) so that subsequent ``_update_categories()`` calls
+        re-run all category updates.
+        """
+        parent = getattr(self, '_parent', None)
+        if parent is not None and hasattr(parent, '_need_categories_update'):
+            parent._need_categories_update = True
+
     def __str__(self) -> str:
         """Human-readable representation of this component."""
         name = self._log_name
@@ -98,18 +215,27 @@ def from_cif(self, block):
         """Populate this collection from a CIF block."""
         category_collection_from_cif(self, block)
 
-    @checktype
-    def _add(self, item) -> None:
-        """Add an item to the collection."""
+    def add(self, item) -> None:
+        """Insert or replace a pre-built item into the collection.
+
+        Args:
+            item: A ``CategoryItem`` instance to add.
+        """
         self[item._identity.category_entry_name] = item
+        self._mark_parent_dirty()
+
+    def create(self, **kwargs) -> None:
+        """Create a new item with the given attributes and add it.
 
-    # TODO: Disallow args and only allow kwargs?
-    # TODO: Check kwargs as for, e.g.,
-    #  ExperimentFactory.create(**kwargs)?
-    @checktype
-    def add(self, *args, **kwargs) -> None:
-        """Create and add a new child instance from the provided
-        arguments.
+        A default instance of the collection's item type is created,
+        then each keyword argument is applied via ``setattr``.
+
+        Args:
+            **kwargs: Attribute names and values for the new item.
         """
-        child_obj = self._item_type(*args, **kwargs)
-        self._add(child_obj)
+        child_obj = self._item_type()
+
+        for attr, val in kwargs.items():
+            setattr(child_obj, attr, val)
+
+        self.add(child_obj)
diff --git a/src/easydiffraction/core/collection.py b/src/easydiffraction/core/collection.py
index a84bdb51..164f3d77 100644
--- a/src/easydiffraction/core/collection.py
+++ b/src/easydiffraction/core/collection.py
@@ -40,9 +40,9 @@ def __getitem__(self, name: str):
 
     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
+        # Check if item with same key exists; if so, replace it
         for i, existing_item in enumerate(self._items):
-            if existing_item._identity.category_entry_name == name:
+            if self._key_for(existing_item) == name:
                 self._items[i] = item
                 self._rebuild_index()
                 return
@@ -53,15 +53,19 @@ def __setitem__(self, name: str, item) -> None:
 
     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:
+            if self._key_for(item) == name:
                 object.__setattr__(item, '_parent', None)  # Unlink the parent before removal
                 del self._items[i]
                 self._rebuild_index()
                 return
         raise KeyError(name)
 
+    def __contains__(self, name: str) -> bool:
+        """Check whether an item with the given key exists."""
+        self._rebuild_index()
+        return name in self._index
+
     def __iter__(self):
         """Iterate over items in insertion order."""
         return iter(self._items)
@@ -70,9 +74,22 @@ def __len__(self) -> int:
         """Return the number of items in the collection."""
         return len(self._items)
 
+    def remove(self, name: str) -> None:
+        """Remove an item by its key.
+
+        Args:
+            name: Identity key of the item to remove.
+
+        Raises:
+            KeyError: If no item with the given key exists.
+        """
+        del self[name]
+
     def _key_for(self, item):
-        """Return the identity key for ``item`` (category or
-        datablock).
+        """Return the identity key for *item*.
+
+        Subclasses must override to return the appropriate key
+        (``category_entry_name`` or ``datablock_entry_name``).
         """
         return item._identity.category_entry_name or item._identity.datablock_entry_name
 
@@ -100,3 +117,25 @@ def items(self):
     def names(self):
         """List of all item keys in the collection."""
         return list(self.keys())
+
+    def help(self) -> None:
+        """Print a summary of public attributes and contained items."""
+        super().help()
+
+        from easydiffraction.utils.logging import console
+        from easydiffraction.utils.utils import render_table
+
+        if self._items:
+            console.paragraph(f'Items ({len(self._items)})')
+            rows = []
+            for i, item in enumerate(self._items, 1):
+                key = self._key_for(item)
+                rows.append([str(i), str(key), f"['{key}']"])
+            render_table(
+                columns_headers=['#', 'Name', 'Access'],
+                columns_alignment=['right', 'left', 'left'],
+                columns_data=rows,
+            )
+        else:
+            console.paragraph('Items')
+            console.print('(empty)')
diff --git a/src/easydiffraction/core/datablock.py b/src/easydiffraction/core/datablock.py
index a8cc0ed8..221845b6 100644
--- a/src/easydiffraction/core/datablock.py
+++ b/src/easydiffraction/core/datablock.py
@@ -3,13 +3,11 @@
 
 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.core.variable import Parameter
 
 
 class DatablockItem(GuardedBase):
@@ -17,13 +15,21 @@ class DatablockItem(GuardedBase):
 
     def __init__(self):
         super().__init__()
-        self._need_categories_update = False
+        self._need_categories_update = True
 
     def __str__(self) -> str:
         """Human-readable representation of this component."""
-        name = self._log_name
-        items = getattr(self, '_items', None)
-        return f'<{name} ({items})>'
+        name = self.unique_name
+        cls = type(self).__name__
+        categories = '\n'.join(f'  - {c}' for c in self.categories)
+        return f"{cls} datablock '{name}':\n{categories}"
+
+    def __repr__(self) -> str:
+        """Developer-oriented representation of this component."""
+        name = self.unique_name
+        cls = type(self).__name__
+        num_categories = len(self.categories)
+        return f'<{cls} datablock "{name}" ({num_categories} categories)>'
 
     def _update_categories(
         self,
@@ -31,7 +37,7 @@ def _update_categories(
     ) -> None:
         # TODO: Make abstract method and implement in subclasses.
         # This should call apply_symmetry and apply_constraints in the
-        # case of sample models. In the case of experiments, it should
+        # case of structures. In the case of experiments, it should
         # run calculations to update the "data" categories.
         # Any parameter change should set _need_categories_update to
         # True.
@@ -40,9 +46,15 @@ def _update_categories(
         # Should this be also called when parameters are accessed? E.g.
         # if one change background coefficients, then access the
         # background points in the data category?
-        # return
-        # if not self._need_categories_update:
-        #    return
+        #
+        # Dirty-flag guard: skip if no parameter has changed since the
+        # last update.  Minimisers use _set_value_from_minimizer()
+        # which bypasses validation but still sets this flag.
+        # During fitting the guard is bypassed because experiment
+        # calculations depend on structure parameters owned by a
+        # different DatablockItem whose flag changes are invisible here.
+        if not called_by_minimizer and not self._need_categories_update:
+            return
 
         for category in self.categories:
             category._update(called_by_minimizer=called_by_minimizer)
@@ -79,14 +91,56 @@ def as_cif(self) -> str:
         self._update_categories()
         return datablock_item_to_cif(self)
 
+    def help(self) -> None:
+        """Print a summary of public attributes and categories."""
+        super().help()
+
+        from easydiffraction.utils.logging import console
+        from easydiffraction.utils.utils import render_table
+
+        cats = self.categories
+        if cats:
+            console.paragraph('Categories')
+            rows = []
+            for c in cats:
+                code = c._identity.category_code or type(c).__name__
+                type_name = type(c).__name__
+                num_params = len(c.parameters)
+                rows.append([code, type_name, str(num_params)])
+            render_table(
+                columns_headers=['Category', 'Type', '# Parameters'],
+                columns_alignment=['left', 'left', 'right'],
+                columns_data=rows,
+            )
+
+
+# ======================================================================
+
 
 class DatablockCollection(CollectionBase):
-    """Handles top-level category collections (e.g. SampleModels,
+    """Handles top-level category collections (e.g. Structures,
     Experiments).
 
     Each item is a DatablockItem.
+
+    Subclasses provide explicit ``add_from_*`` convenience methods
+    that delegate to the corresponding factory classmethods, then
+    call :meth:`add` with the resulting item.
     """
 
+    def _key_for(self, item):
+        """Return the datablock-level identity key for *item*."""
+        return item._identity.datablock_entry_name
+
+    def add(self, item) -> None:
+        """Add a pre-built item to the collection.
+
+        Args:
+            item: A ``DatablockItem`` instance (e.g. a ``Structure``
+                or ``ExperimentBase`` subclass).
+        """
+        self[item._identity.datablock_entry_name] = item
+
     def __str__(self) -> str:
         """Human-readable representation of this component."""
         name = self._log_name
@@ -121,8 +175,3 @@ def as_cif(self) -> str:
         from easydiffraction.io.cif.serialize import datablock_collection_to_cif
 
         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/factory.py b/src/easydiffraction/core/factory.py
index 3500768f..8e699085 100644
--- a/src/easydiffraction/core/factory.py
+++ b/src/easydiffraction/core/factory.py
@@ -1,36 +1,225 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
+"""Base factory with registration, lookup, and context-dependent
+defaults.
 
-from typing import Iterable
-from typing import Mapping
+Concrete factories inherit from ``FactoryBase`` and only need to
+define ``_default_rules``.
+"""
+
+from __future__ import annotations
+
+from typing import Any
+from typing import Dict
+from typing import FrozenSet
+from typing import List
+from typing import Tuple
+from typing import Type
+
+from easydiffraction.utils.logging import console
+from easydiffraction.utils.utils import render_table
 
 
 class FactoryBase:
-    """Reusable argument validation mixin."""
+    """Shared base for all factories.
+
+    Subclasses must set:
+
+    * ``_default_rules`` -- mapping of ``frozenset`` conditions to tag
+      strings.  Use ``frozenset(): 'tag'`` for a universal default.
+
+    The ``__init_subclass__`` hook ensures every subclass gets its own
+    independent ``_registry`` list.
+    """
+
+    _registry: List[Type] = []
+    _default_rules: Dict[FrozenSet[Tuple[str, Any]], str] = {}
+
+    def __init_subclass__(cls, **kwargs):
+        """Give each subclass its own independent registry and rules."""
+        super().__init_subclass__(**kwargs)
+        cls._registry = []
+        if '_default_rules' not in cls.__dict__:
+            cls._default_rules = {}
+
+    # ------------------------------------------------------------------
+    # Registration
+    # ------------------------------------------------------------------
+
+    @classmethod
+    def register(cls, klass):
+        """Class decorator to register a concrete class.
+
+        Usage::
+
+            @SomeFactory.register
+            class MyClass(SomeBase):
+                type_info = TypeInfo(...)
+
+        Returns the class unmodified.
+        """
+        cls._registry.append(klass)
+        return klass
+
+    # ------------------------------------------------------------------
+    # Supported-map helpers
+    # ------------------------------------------------------------------
+
+    @classmethod
+    def _supported_map(cls) -> Dict[str, Type]:
+        """Build ``{tag: class}`` from all registered classes."""
+        return {klass.type_info.tag: klass for klass in cls._registry}
+
+    @classmethod
+    def supported_tags(cls) -> List[str]:
+        """Return list of all supported tags."""
+        return list(cls._supported_map().keys())
+
+    # ------------------------------------------------------------------
+    # Default resolution
+    # ------------------------------------------------------------------
+
+    @classmethod
+    def default_tag(cls, **conditions) -> str:
+        """Resolve the default tag for a given experimental context.
+
+        Uses *largest-subset matching*: the rule whose key is the
+        biggest subset of the given conditions wins.  A rule with an
+        empty key (``frozenset()``) acts as a universal fallback.
 
-    @staticmethod
-    def _validate_args(
-        present: set[str],
-        allowed_specs: Iterable[Mapping[str, Iterable[str]]],
-        factory_name: str,
+        Args:
+            **conditions: Experimental-axis values, e.g.
+                ``scattering_type=ScatteringTypeEnum.BRAGG``.
+
+        Returns:
+            The resolved default tag string.
+
+        Raises:
+            ValueError: If no rule matches the given conditions.
+        """
+        condition_set = frozenset(conditions.items())
+        best_match_tag: str | None = None
+        best_match_size = -1
+
+        for rule_key, rule_tag in cls._default_rules.items():
+            if rule_key <= condition_set and len(rule_key) > best_match_size:
+                best_match_tag = rule_tag
+                best_match_size = len(rule_key)
+
+        if best_match_tag is None:
+            raise ValueError(
+                f'No default rule matches conditions {dict(conditions)}. '
+                f'Available rules: {cls._default_rules}'
+            )
+        return best_match_tag
+
+    # ------------------------------------------------------------------
+    # Creation
+    # ------------------------------------------------------------------
+
+    @classmethod
+    def create(cls, tag: str, **kwargs) -> Any:
+        """Instantiate a registered class by *tag*.
+
+        Args:
+            tag: ``type_info.tag`` value.
+            **kwargs: Forwarded to the class constructor.
+
+        Raises:
+            ValueError: If *tag* is not in the registry.
+        """
+        supported = cls._supported_map()
+        if tag not in supported:
+            raise ValueError(f"Unsupported type: '{tag}'. Supported: {list(supported.keys())}")
+        return supported[tag](**kwargs)
+
+    @classmethod
+    def create_default_for(cls, **conditions) -> Any:
+        """Instantiate the default class for a given context.
+
+        Combines ``default_tag(**conditions)`` with ``create(tag)``.
+
+        Args:
+            **conditions: Experimental-axis values.
+        """
+        tag = cls.default_tag(**conditions)
+        return cls.create(tag)
+
+    # ------------------------------------------------------------------
+    # Querying
+    # ------------------------------------------------------------------
+
+    @classmethod
+    def supported_for(
+        cls,
+        *,
+        calculator=None,
+        sample_form=None,
+        scattering_type=None,
+        beam_mode=None,
+        radiation_probe=None,
+    ) -> List[Type]:
+        """Return classes matching conditions and/or calculator.
+
+        Args:
+            calculator: Optional ``CalculatorEnum`` value.
+            sample_form: Optional ``SampleFormEnum`` value.
+            scattering_type: Optional ``ScatteringTypeEnum`` value.
+            beam_mode: Optional ``BeamModeEnum`` value.
+            radiation_probe: Optional ``RadiationProbeEnum`` value.
+        """
+        result = []
+        for klass in cls._supported_map().values():
+            compat = getattr(klass, 'compatibility', None)
+            if compat and not compat.supports(
+                sample_form=sample_form,
+                scattering_type=scattering_type,
+                beam_mode=beam_mode,
+                radiation_probe=radiation_probe,
+            ):
+                continue
+            calc_support = getattr(klass, 'calculator_support', None)
+            if calculator and calc_support and not calc_support.supports(calculator):
+                continue
+            result.append(klass)
+        return result
+
+    # ------------------------------------------------------------------
+    # Display
+    # ------------------------------------------------------------------
+
+    @classmethod
+    def show_supported(
+        cls,
+        *,
+        calculator=None,
+        sample_form=None,
+        scattering_type=None,
+        beam_mode=None,
+        radiation_probe=None,
     ) -> 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)
+        """Pretty-print a table of supported types.
+
+        Args:
+            calculator: Optional ``CalculatorEnum`` filter.
+            sample_form: Optional ``SampleFormEnum`` filter.
+            scattering_type: Optional ``ScatteringTypeEnum`` filter.
+            beam_mode: Optional ``BeamModeEnum`` filter.
+            radiation_probe: Optional ``RadiationProbeEnum`` filter.
+        """
+        matching = cls.supported_for(
+            calculator=calculator,
+            sample_form=sample_form,
+            scattering_type=scattering_type,
+            beam_mode=beam_mode,
+            radiation_probe=radiation_probe,
+        )
+        columns_headers = ['Type', 'Description']
+        columns_alignment = ['left', 'left']
+        columns_data = [[klass.type_info.tag, klass.type_info.description] for klass in matching]
+        console.paragraph('Supported types')
+        render_table(
+            columns_headers=columns_headers,
+            columns_alignment=columns_alignment,
+            columns_data=columns_data,
         )
diff --git a/src/easydiffraction/core/guard.py b/src/easydiffraction/core/guard.py
index 0979f4cd..a0033d14 100644
--- a/src/easydiffraction/core/guard.py
+++ b/src/easydiffraction/core/guard.py
@@ -1,6 +1,8 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
 
+from __future__ import annotations
+
 from abc import ABC
 from abc import abstractmethod
 
@@ -16,6 +18,7 @@ class GuardedBase(ABC):
     _diagnoser = Diagnostics()
 
     def __init__(self):
+        super().__init__()
         self._identity = Identity(owner=self)
 
     def __str__(self) -> str:
@@ -141,3 +144,79 @@ def as_cif(self) -> str:
         by subclasses).
         """
         raise NotImplementedError
+
+    @staticmethod
+    def _first_sentence(docstring: str | None) -> str:
+        """Extract the first paragraph from a docstring.
+
+        Returns text before the first blank line, with continuation
+        lines joined into a single string.
+        """
+        if not docstring:
+            return ''
+        first_para = docstring.strip().split('\n\n')[0]
+        return ' '.join(line.strip() for line in first_para.splitlines())
+
+    @classmethod
+    def _iter_methods(cls):
+        """Iterate over public methods in the class hierarchy.
+
+        Yields:
+            tuple[str, callable]: Each (name, function) pair.
+        """
+        seen: set = set()
+        for base in cls.mro():
+            for key, attr in base.__dict__.items():
+                if key.startswith('_') or key in seen:
+                    continue
+                if isinstance(attr, property):
+                    continue
+                raw = attr
+                if isinstance(raw, (staticmethod, classmethod)):
+                    raw = raw.__func__
+                if callable(raw):
+                    seen.add(key)
+                    yield key, raw
+
+    def help(self) -> None:
+        """Print a summary of public properties and methods."""
+        from easydiffraction.utils.logging import console
+        from easydiffraction.utils.utils import render_table
+
+        cls = type(self)
+        console.paragraph(f"Help for '{cls.__name__}'")
+
+        # Deduplicate (MRO may yield the same name)
+        seen: dict = {}
+        for key, prop in cls._iter_properties():
+            if key not in seen:
+                seen[key] = prop
+
+        prop_rows = []
+        for i, key in enumerate(sorted(seen), 1):
+            prop = seen[key]
+            writable = '✓' if prop.fset else '✗'
+            doc = self._first_sentence(prop.fget.__doc__ if prop.fget else None)
+            prop_rows.append([str(i), key, writable, doc])
+
+        if prop_rows:
+            console.paragraph('Properties')
+            render_table(
+                columns_headers=['#', 'Name', 'Writable', 'Description'],
+                columns_alignment=['right', 'left', 'center', 'left'],
+                columns_data=prop_rows,
+            )
+
+        methods = dict(cls._iter_methods())
+        method_rows = []
+        for i, key in enumerate(sorted(methods), 1):
+            doc = self._first_sentence(getattr(methods[key], '__doc__', None))
+            method_rows.append([str(i), f'{key}()', doc])
+
+        if method_rows:
+            console.paragraph('Methods')
+            render_table(
+                columns_headers=['#', 'Name', 'Description'],
+                columns_alignment=['right', 'left', 'left'],
+                columns_data=method_rows,
+            )
diff --git a/src/easydiffraction/core/metadata.py b/src/easydiffraction/core/metadata.py
new file mode 100644
index 00000000..4a820515
--- /dev/null
+++ b/src/easydiffraction/core/metadata.py
@@ -0,0 +1,107 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Metadata dataclasses for factory-created classes.
+
+Three frozen dataclasses describe a concrete class:
+
+- ``TypeInfo`` — stable tag and human-readable description.
+- ``Compatibility`` — experimental conditions (multiple fields).
+- ``CalculatorSupport`` — which calculation engines can handle it.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+from typing import FrozenSet
+
+
+@dataclass(frozen=True)
+class TypeInfo:
+    """Stable identity and human-readable description for a factory-
+    created class.
+
+    Attributes:
+        tag: Short, stable string identifier used for serialization,
+            user-facing selection, and factory lookup.  Must be unique
+            within a factory's registry.  Examples: ``'line-segment'``,
+            ``'pseudo-voigt'``, ``'cryspy'``.
+        description: One-line human-readable explanation.  Used in
+            ``show_supported()`` tables and documentation.
+    """
+
+    tag: str
+    description: str = ''
+
+
+@dataclass(frozen=True)
+class Compatibility:
+    """Experimental conditions under which a class can be used.
+
+    Each field is a frozenset of enum values representing the set of
+    supported values for that axis.  An empty frozenset means
+    "compatible with any value of this axis" (i.e. no restriction).
+    """
+
+    sample_form: FrozenSet = frozenset()
+    scattering_type: FrozenSet = frozenset()
+    beam_mode: FrozenSet = frozenset()
+    radiation_probe: FrozenSet = frozenset()
+
+    def supports(
+        self,
+        sample_form=None,
+        scattering_type=None,
+        beam_mode=None,
+        radiation_probe=None,
+    ) -> bool:
+        """Check if this compatibility matches the given conditions.
+
+        Each argument is an optional enum member.  Returns ``True`` if
+        every provided value is in the corresponding frozenset (or the
+        frozenset is empty, meaning *any*).
+
+        Example::
+
+            compat.supports(
+                scattering_type=ScatteringTypeEnum.BRAGG,
+                beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH,
+            )
+        """
+        for axis, value in (
+            ('sample_form', sample_form),
+            ('scattering_type', scattering_type),
+            ('beam_mode', beam_mode),
+            ('radiation_probe', radiation_probe),
+        ):
+            if value is None:
+                continue
+            allowed = getattr(self, axis)
+            if allowed and value not in allowed:
+                return False
+        return True
+
+
+@dataclass(frozen=True)
+class CalculatorSupport:
+    """Which calculation engines can handle this class.
+
+    Attributes:
+        calculators: Frozenset of ``CalculatorEnum`` values.  Empty
+            means "any calculator" (no restriction).
+    """
+
+    calculators: FrozenSet = frozenset()
+
+    def supports(self, calculator) -> bool:
+        """Check if a specific calculator can handle this class.
+
+        Args:
+            calculator: A ``CalculatorEnum`` value.
+
+        Returns:
+            ``True`` if the calculator is in the set, or if the set is
+            empty (meaning any calculator is accepted).
+        """
+        if not self.calculators:
+            return True
+        return calculator in self.calculators
diff --git a/src/easydiffraction/core/singletons.py b/src/easydiffraction/core/singleton.py
similarity index 94%
rename from src/easydiffraction/core/singletons.py
rename to src/easydiffraction/core/singleton.py
index 2cd553c1..10af4667 100644
--- a/src/easydiffraction/core/singletons.py
+++ b/src/easydiffraction/core/singleton.py
@@ -12,6 +12,8 @@
 
 T = TypeVar('T', bound='SingletonBase')
 
+# ======================================================================
+
 
 class SingletonBase:
     """Base class to implement Singleton pattern.
@@ -30,6 +32,9 @@ def get(cls: Type[T]) -> T:
         return cls._instance
 
 
+# ======================================================================
+
+
 class UidMapHandler(SingletonBase):
     """Global handler to manage UID-to-Parameter object mapping."""
 
@@ -47,7 +52,7 @@ def add_to_uid_map(self, parameter):
         Only Descriptor or Parameter instances are allowed (not
         Components or others).
         """
-        from easydiffraction.core.parameters import GenericDescriptorBase
+        from easydiffraction.core.variable import GenericDescriptorBase
 
         if not isinstance(parameter, GenericDescriptorBase):
             raise TypeError(
@@ -71,6 +76,9 @@ def replace_uid(self, old_uid, new_uid):
     # TODO: Implement removing from the UID map
 
 
+# ======================================================================
+
+
 # TODO: Implement changing atrr '.constrained' back to False
 #  when removing constraints
 class ConstraintsHandler(SingletonBase):
@@ -164,8 +172,7 @@ def apply(self) -> None:
                 param = uid_map[dependent_uid]
 
                 # Update its value and mark it as constrained
-                param._value = rhs_value  # To bypass ranges check
-                param._constrained = True  # To bypass read-only check
+                param._set_value_constrained(rhs_value)
 
             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
index 5e31c485..1fd268d9 100644
--- a/src/easydiffraction/core/validation.py
+++ b/src/easydiffraction/core/validation.py
@@ -6,7 +6,6 @@
 descriptors and parameters. Only documentation was added here.
 """
 
-import functools
 import re
 from abc import ABC
 from abc import abstractmethod
@@ -14,19 +13,26 @@
 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
-# ==============================================================
+# ======================================================================
+
+
+# TODO: MkDocs doesn't unpack types
+class DataTypeHints:
+    Numeric = int | float | np.integer | np.floating
+    String = str
+    Bool = bool
+
+
+# ======================================================================
 
 
 class DataTypes(Enum):
-    NUMERIC = (int, float, np.integer, np.floating, np.number)
+    NUMERIC = (int, float, np.integer, np.floating)
     STRING = (str,)
     BOOL = (bool,)
     ANY = (object,)  # fallback for unconstrained
@@ -40,47 +46,9 @@ def expected_type(self):
         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):
@@ -95,9 +63,9 @@ def __str__(self):
         return self.name.lower()
 
 
-# ==============================================================
+# ======================================================================
 # Advanced runtime custom validators for Parameter types/content
-# ==============================================================
+# ======================================================================
 
 
 class ValidatorBase(ABC):
@@ -120,8 +88,11 @@ def _fallback(
         return current if current is not None else default
 
 
+# ======================================================================
+
+
 class TypeValidator(ValidatorBase):
-    """Ensure a value is of the expected Python type."""
+    """Ensure a value is of the expected data type."""
 
     def __init__(self, expected_type: DataTypes):
         if isinstance(expected_type, DataTypes):
@@ -171,6 +142,9 @@ def validated(
         return value
 
 
+# ======================================================================
+
+
 class RangeValidator(ValidatorBase):
     """Ensure a numeric value lies within [ge, le]."""
 
@@ -209,6 +183,9 @@ def validated(
         return value
 
 
+# ======================================================================
+
+
 class MembershipValidator(ValidatorBase):
     """Ensure that a value is among allowed choices.
 
@@ -248,6 +225,9 @@ def validated(
         return value
 
 
+# ======================================================================
+
+
 class RegexValidator(ValidatorBase):
     """Ensure that a string matches a given regular expression."""
 
@@ -280,9 +260,9 @@ def validated(
         return value
 
 
-# ==============================================================
+# ======================================================================
 # Attribute specification holding metadata and validators
-# ==============================================================
+# ======================================================================
 
 
 class AttributeSpec:
@@ -291,17 +271,15 @@ class AttributeSpec:
     def __init__(
         self,
         *,
-        value=None,
-        type_=None,
         default=None,
-        content_validator=None,
+        data_type=None,
+        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
+        self._data_type_validator = TypeValidator(data_type) if data_type else None
+        self._validator = validator
 
     def validated(
         self,
@@ -319,8 +297,8 @@ def validated(
         default = self.default() if callable(self.default) else self.default
 
         # Type validation
-        if self._type_validator:
-            val = self._type_validator.validated(
+        if self._data_type_validator:
+            val = self._data_type_validator.validated(
                 val,
                 name,
                 default=default,
@@ -334,8 +312,8 @@ def validated(
             return None
 
         # Content validation
-        if self._content_validator and val is not None:
-            val = self._content_validator.validated(
+        if self._validator and val is not None:
+            val = self._validator.validated(
                 val,
                 name,
                 default=default,
diff --git a/src/easydiffraction/core/parameters.py b/src/easydiffraction/core/variable.py
similarity index 80%
rename from src/easydiffraction/core/parameters.py
rename to src/easydiffraction/core/variable.py
index 37b982e0..6b33a39e 100644
--- a/src/easydiffraction/core/parameters.py
+++ b/src/easydiffraction/core/variable.py
@@ -12,7 +12,7 @@
 
 from easydiffraction.core.diagnostic import Diagnostics
 from easydiffraction.core.guard import GuardedBase
-from easydiffraction.core.singletons import UidMapHandler
+from easydiffraction.core.singleton import UidMapHandler
 from easydiffraction.core.validation import AttributeSpec
 from easydiffraction.core.validation import DataTypes
 from easydiffraction.core.validation import RangeValidator
@@ -23,6 +23,8 @@
 if TYPE_CHECKING:
     from easydiffraction.io.cif.handler import CifHandler
 
+# ======================================================================
+
 
 class GenericDescriptorBase(GuardedBase):
     """Base class for all parameter-like descriptors.
@@ -40,7 +42,7 @@ class GenericDescriptorBase(GuardedBase):
     """
 
     _BOOL_SPEC_TEMPLATE = AttributeSpec(
-        type_=DataTypes.BOOL,
+        data_type=DataTypes.BOOL,
         default=False,
     )
 
@@ -64,8 +66,8 @@ def __init__(
 
         if expected_type:
             user_type = (
-                value_spec._type_validator.expected_type
-                if value_spec._type_validator is not None
+                value_spec._data_type_validator.expected_type
+                if value_spec._data_type_validator is not None
                 else None
             )
             if user_type and user_type is not expected_type:
@@ -76,17 +78,24 @@ def __init__(
                 )
             else:
                 # Enforce descriptor's own type if not already defined
-                value_spec._type_validator = TypeValidator(expected_type)
+                value_spec._data_type_validator = TypeValidator(expected_type)
 
         self._value_spec = value_spec
         self._name = name
         self._description = description
 
         # Initial validated states
-        self._value = self._value_spec.validated(
-            value_spec.value,
-            name=self.unique_name,
-        )
+        # self._value = self._value_spec.validated(
+        #    value_spec.value,
+        #    name=self.unique_name,
+        # )
+
+        # Assign default directly.
+        # Skip validation — defaults are trusted.
+        # Callable is needed for dynamic defaults like SpaceGroup
+        # it_coordinate_system_code, and similar cases.
+        default = value_spec.default
+        self._value = default() if callable(default) else default
 
     def __str__(self) -> str:
         return f'<{self.unique_name} = {self.value!r}>'
@@ -154,6 +163,25 @@ def value(self, v):
         if parent_datablock is not None:
             parent_datablock._need_categories_update = True
 
+    def _set_value_from_minimizer(self, v) -> None:
+        """Set the value from a minimizer, bypassing validation.
+
+        Writes ``_value`` directly — no type or range checks — but
+        still marks the owning :class:`DatablockItem` dirty so that
+        ``_update_categories()`` knows work is needed.
+
+        This exists because:
+
+        1. Physical-range validators (e.g. intensity ≥ 0) would reject
+           trial values the minimizer needs to explore.
+        2. Validation overhead is measurable over thousands of
+           objective-function evaluations.
+        """
+        self._value = v
+        parent_datablock = self._datablock_item()
+        if parent_datablock is not None:
+            parent_datablock._need_categories_update = True
+
     @property
     def description(self):
         """Optional human-readable description."""
@@ -179,6 +207,9 @@ def from_cif(self, block, idx=0):
         param_from_cif(self, block, idx)
 
 
+# ======================================================================
+
+
 class GenericStringDescriptor(GenericDescriptorBase):
     _value_type = DataTypes.STRING
 
@@ -189,6 +220,9 @@ def __init__(
         super().__init__(**kwargs)
 
 
+# ======================================================================
+
+
 class GenericNumericDescriptor(GenericDescriptorBase):
     _value_type = DataTypes.NUMERIC
 
@@ -214,6 +248,9 @@ def units(self) -> str:
         return self._units
 
 
+# ======================================================================
+
+
 class GenericParameter(GenericNumericDescriptor):
     """Numeric descriptor extended with fitting-related attributes.
 
@@ -232,16 +269,16 @@ def __init__(
         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),
+            data_type=DataTypes.NUMERIC,
+            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_spec = AttributeSpec(data_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_spec = AttributeSpec(data_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_spec = AttributeSpec(data_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
@@ -275,27 +312,21 @@ def _minimizer_uid(self):
         # 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
 
+    def _set_value_constrained(self, v) -> None:
+        """Set the value from a constraint expression.
+
+        Validates against the spec, marks the parent datablock dirty,
+        and flags the parameter as constrained. Used exclusively by
+        ``ConstraintsHandler.apply()``.
+        """
+        self.value = v
+        self._constrained = True
+
     @property
     def free(self):
         """Whether this parameter is currently varied during fitting."""
@@ -347,6 +378,9 @@ def fit_max(self, v):
         )
 
 
+# ======================================================================
+
+
 class StringDescriptor(GenericStringDescriptor):
     def __init__(
         self,
@@ -365,6 +399,9 @@ def __init__(
         self._cif_handler.attach(self)
 
 
+# ======================================================================
+
+
 class NumericDescriptor(GenericNumericDescriptor):
     def __init__(
         self,
@@ -383,6 +420,9 @@ def __init__(
         self._cif_handler.attach(self)
 
 
+# ======================================================================
+
+
 class Parameter(GenericParameter):
     def __init__(
         self,
diff --git a/src/easydiffraction/experiments/__init__.py b/src/easydiffraction/datablocks/__init__.py
similarity index 100%
rename from src/easydiffraction/experiments/__init__.py
rename to src/easydiffraction/datablocks/__init__.py
diff --git a/src/easydiffraction/experiments/categories/__init__.py b/src/easydiffraction/datablocks/experiment/__init__.py
similarity index 100%
rename from src/easydiffraction/experiments/categories/__init__.py
rename to src/easydiffraction/datablocks/experiment/__init__.py
diff --git a/src/easydiffraction/experiments/categories/background/__init__.py b/src/easydiffraction/datablocks/experiment/categories/__init__.py
similarity index 100%
rename from src/easydiffraction/experiments/categories/background/__init__.py
rename to src/easydiffraction/datablocks/experiment/categories/__init__.py
diff --git a/src/easydiffraction/datablocks/experiment/categories/background/__init__.py b/src/easydiffraction/datablocks/experiment/categories/background/__init__.py
new file mode 100644
index 00000000..b7b3b47d
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/background/__init__.py
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.datablocks.experiment.categories.background.chebyshev import (
+    ChebyshevPolynomialBackground,
+)
+from easydiffraction.datablocks.experiment.categories.background.line_segment import (
+    LineSegmentBackground,
+)
diff --git a/src/easydiffraction/experiments/categories/background/base.py b/src/easydiffraction/datablocks/experiment/categories/background/base.py
similarity index 100%
rename from src/easydiffraction/experiments/categories/background/base.py
rename to src/easydiffraction/datablocks/experiment/categories/background/base.py
diff --git a/src/easydiffraction/experiments/categories/background/chebyshev.py b/src/easydiffraction/datablocks/experiment/categories/background/chebyshev.py
similarity index 69%
rename from src/easydiffraction/experiments/categories/background/chebyshev.py
rename to src/easydiffraction/datablocks/experiment/categories/background/chebyshev.py
index 2a21b0d0..4a6a714d 100644
--- a/src/easydiffraction/experiments/categories/background/chebyshev.py
+++ b/src/easydiffraction/datablocks/experiment/categories/background/chebyshev.py
@@ -14,14 +14,19 @@
 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.parameters import StringDescriptor
+from easydiffraction.core.metadata import CalculatorSupport
+from easydiffraction.core.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
 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.experiments.categories.background.base import BackgroundBase
+from easydiffraction.core.variable import NumericDescriptor
+from easydiffraction.core.variable import Parameter
+from easydiffraction.core.variable import StringDescriptor
+from easydiffraction.datablocks.experiment.categories.background.base import BackgroundBase
+from easydiffraction.datablocks.experiment.categories.background.factory import BackgroundFactory
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import CalculatorEnum
 from easydiffraction.io.cif.handler import CifHandler
 from easydiffraction.utils.logging import console
 from easydiffraction.utils.logging import log
@@ -37,67 +42,47 @@ class PolynomialTerm(CategoryItem):
     not break immediately. Tests should migrate to the short names.
     """
 
-    def __init__(
-        self,
-        *,
-        id=None,  # TODO: rename as in the case of data points?
-        order=None,
-        coef=None,
-    ) -> None:
+    def __init__(self) -> None:
         super().__init__()
 
         self._id = StringDescriptor(
             name='id',
             description='Identifier for this background polynomial term.',
             value_spec=AttributeSpec(
-                type_=DataTypes.STRING,
-                value=id,
                 default='0',
                 # 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-z0-9_]*$'),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_pd_background.id',
-                ]
+                validator=RegexValidator(pattern=r'^[A-Za-z0-9_]*$'),
             ),
+            cif_handler=CifHandler(names=['_pd_background.id']),
         )
         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',
-                ]
+                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',
-                ]
+                validator=RangeValidator(),
             ),
+            cif_handler=CifHandler(names=['_pd_background.Chebyshev_coef']),
         )
 
         self._identity.category_code = 'background'
         self._identity.category_entry_name = lambda: str(self._id.value)
 
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
     @property
     def id(self):
         return self._id
@@ -123,8 +108,24 @@ def coef(self, value):
         self._coef.value = value
 
 
+@BackgroundFactory.register
 class ChebyshevPolynomialBackground(BackgroundBase):
-    _description: str = 'Chebyshev polynomial background'
+    type_info = TypeInfo(
+        tag='chebyshev',
+        description='Chebyshev polynomial background',
+    )
+    compatibility = Compatibility(
+        beam_mode=frozenset({
+            BeamModeEnum.CONSTANT_WAVELENGTH,
+            BeamModeEnum.TIME_OF_FLIGHT,
+        }),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({
+            CalculatorEnum.CRYSPY,
+            CalculatorEnum.CRYSFML,
+        }),
+    )
 
     def __init__(self):
         super().__init__(item_type=PolynomialTerm)
diff --git a/src/easydiffraction/experiments/categories/background/enums.py b/src/easydiffraction/datablocks/experiment/categories/background/enums.py
similarity index 95%
rename from src/easydiffraction/experiments/categories/background/enums.py
rename to src/easydiffraction/datablocks/experiment/categories/background/enums.py
index d7edf42e..2356702a 100644
--- a/src/easydiffraction/experiments/categories/background/enums.py
+++ b/src/easydiffraction/datablocks/experiment/categories/background/enums.py
@@ -12,7 +12,7 @@ class BackgroundTypeEnum(str, Enum):
     """Supported background model types."""
 
     LINE_SEGMENT = 'line-segment'
-    CHEBYSHEV = 'chebyshev polynomial'
+    CHEBYSHEV = 'chebyshev'
 
     @classmethod
     def default(cls) -> 'BackgroundTypeEnum':
diff --git a/src/easydiffraction/datablocks/experiment/categories/background/factory.py b/src/easydiffraction/datablocks/experiment/categories/background/factory.py
new file mode 100644
index 00000000..c52b7dd7
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/background/factory.py
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Background factory — delegates entirely to ``FactoryBase``."""
+
+from easydiffraction.core.factory import FactoryBase
+from easydiffraction.datablocks.experiment.categories.background.enums import BackgroundTypeEnum
+
+
+class BackgroundFactory(FactoryBase):
+    """Create background collections by tag."""
+
+    _default_rules = {
+        frozenset(): BackgroundTypeEnum.LINE_SEGMENT,
+    }
diff --git a/src/easydiffraction/experiments/categories/background/line_segment.py b/src/easydiffraction/datablocks/experiment/categories/background/line_segment.py
similarity index 74%
rename from src/easydiffraction/experiments/categories/background/line_segment.py
rename to src/easydiffraction/datablocks/experiment/categories/background/line_segment.py
index 2df4ca2b..822f6e0d 100644
--- a/src/easydiffraction/experiments/categories/background/line_segment.py
+++ b/src/easydiffraction/datablocks/experiment/categories/background/line_segment.py
@@ -13,14 +13,19 @@
 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.parameters import StringDescriptor
+from easydiffraction.core.metadata import CalculatorSupport
+from easydiffraction.core.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
 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.experiments.categories.background.base import BackgroundBase
+from easydiffraction.core.variable import NumericDescriptor
+from easydiffraction.core.variable import Parameter
+from easydiffraction.core.variable import StringDescriptor
+from easydiffraction.datablocks.experiment.categories.background.base import BackgroundBase
+from easydiffraction.datablocks.experiment.categories.background.factory import BackgroundFactory
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import CalculatorEnum
 from easydiffraction.io.cif.handler import CifHandler
 from easydiffraction.utils.logging import console
 from easydiffraction.utils.logging import log
@@ -30,32 +35,20 @@
 class LineSegment(CategoryItem):
     """Single background control point for interpolation."""
 
-    def __init__(
-        self,
-        *,
-        id=None,  # TODO: rename as in the case of data points?
-        x=None,
-        y=None,
-    ) -> None:
+    def __init__(self) -> None:
         super().__init__()
 
         self._id = StringDescriptor(
             name='id',
             description='Identifier for this background line segment.',
             value_spec=AttributeSpec(
-                type_=DataTypes.STRING,
-                value=id,
                 default='0',
                 # 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-z0-9_]*$'),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_pd_background.id',
-                ]
+                validator=RegexValidator(pattern=r'^[A-Za-z0-9_]*$'),
             ),
+            cif_handler=CifHandler(names=['_pd_background.id']),
         )
         self._x = NumericDescriptor(
             name='x',
@@ -64,10 +57,8 @@ def __init__(
                 'representing the background in a calculated diffractogram.'
             ),
             value_spec=AttributeSpec(
-                value=x,
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(),
+                validator=RangeValidator(),
             ),
             cif_handler=CifHandler(
                 names=[
@@ -83,10 +74,8 @@ def __init__(
                 'representing the background in a calculated diffractogram'
             ),
             value_spec=AttributeSpec(
-                value=y,
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(),
+                validator=RangeValidator(),
             ),  # TODO: rename to intensity
             cif_handler=CifHandler(
                 names=[
@@ -99,6 +88,10 @@ def __init__(
         self._identity.category_code = 'background'
         self._identity.category_entry_name = lambda: str(self._id.value)
 
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
     @property
     def id(self):
         return self._id
@@ -124,8 +117,18 @@ def y(self, value):
         self._y.value = value
 
 
+@BackgroundFactory.register
 class LineSegmentBackground(BackgroundBase):
-    _description: str = 'Linear interpolation between points'
+    type_info = TypeInfo(
+        tag='line-segment',
+        description='Linear interpolation between points',
+    )
+    compatibility = Compatibility(
+        beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH, BeamModeEnum.TIME_OF_FLIGHT}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({CalculatorEnum.CRYSPY, CalculatorEnum.CRYSFML}),
+    )
 
     def __init__(self):
         super().__init__(item_type=LineSegment)
diff --git a/src/easydiffraction/datablocks/experiment/categories/data/__init__.py b/src/easydiffraction/datablocks/experiment/categories/data/__init__.py
new file mode 100644
index 00000000..c228ecd8
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/data/__init__.py
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.datablocks.experiment.categories.data.bragg_pd import PdCwlData
+from easydiffraction.datablocks.experiment.categories.data.bragg_pd import PdTofData
+from easydiffraction.datablocks.experiment.categories.data.bragg_sc import ReflnData
+from easydiffraction.datablocks.experiment.categories.data.total_pd import TotalData
diff --git a/src/easydiffraction/experiments/categories/data/bragg_pd.py b/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py
similarity index 79%
rename from src/easydiffraction/experiments/categories/data/bragg_pd.py
rename to src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py
index 70b0991c..73402d30 100644
--- a/src/easydiffraction/experiments/categories/data/bragg_pd.py
+++ b/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py
@@ -7,13 +7,20 @@
 
 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.metadata import CalculatorSupport
+from easydiffraction.core.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
 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.core.variable import NumericDescriptor
+from easydiffraction.core.variable import StringDescriptor
+from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import CalculatorEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
 from easydiffraction.io.cif.handler import CifHandler
 from easydiffraction.utils.utils import tof_to_d
 from easydiffraction.utils.utils import twotheta_to_d
@@ -22,19 +29,18 @@
 class PdDataPointBaseMixin:
     """Single base data point mixin for powder diffraction data."""
 
-    def __init__(self, **kwargs):
-        super().__init__(**kwargs)
+    def __init__(self):
+        super().__init__()
 
         self._point_id = StringDescriptor(
             name='point_id',
             description='Identifier for this data point in the dataset.',
             value_spec=AttributeSpec(
-                type_=DataTypes.STRING,
                 default='0',
                 # 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-z0-9_]*$'),
+                validator=RegexValidator(pattern=r'^[A-Za-z0-9_]*$'),
             ),
             cif_handler=CifHandler(
                 names=[
@@ -46,23 +52,17 @@ def __init__(self, **kwargs):
             name='d_spacing',
             description='d-spacing value corresponding to this data point.',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(ge=0),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_pd_proc.d_spacing',
-                ]
+                validator=RangeValidator(ge=0),
             ),
+            cif_handler=CifHandler(names=['_pd_proc.d_spacing']),
         )
         self._intensity_meas = NumericDescriptor(
             name='intensity_meas',
             description='Intensity recorded at each measurement point as a function of angle/time',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(ge=0),
+                validator=RangeValidator(ge=0),
             ),
             cif_handler=CifHandler(
                 names=[
@@ -75,9 +75,8 @@ def __init__(self, **kwargs):
             name='intensity_meas_su',
             description='Standard uncertainty of the measured intensity at this data point.',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=1.0,
-                content_validator=RangeValidator(ge=0),
+                validator=RangeValidator(ge=0),
             ),
             cif_handler=CifHandler(
                 names=[
@@ -90,37 +89,26 @@ def __init__(self, **kwargs):
             name='intensity_calc',
             description='Intensity value for a computed diffractogram at this data point.',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(ge=0),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_pd_calc.intensity_total',
-                ]
+                validator=RangeValidator(ge=0),
             ),
+            cif_handler=CifHandler(names=['_pd_calc.intensity_total']),
         )
         self._intensity_bkg = NumericDescriptor(
             name='intensity_bkg',
             description='Intensity value for a computed background at this data point.',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(ge=0),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_pd_calc.intensity_bkg',
-                ]
+                validator=RangeValidator(ge=0),
             ),
+            cif_handler=CifHandler(names=['_pd_calc.intensity_bkg']),
         )
         self._calc_status = StringDescriptor(
             name='calc_status',
             description='Status code of the data point in the calculation process.',
             value_spec=AttributeSpec(
-                type_=DataTypes.STRING,
                 default='incl',  # TODO: Make Enum
-                content_validator=MembershipValidator(allowed=['incl', 'excl']),
+                validator=MembershipValidator(allowed=['incl', 'excl']),
             ),
             cif_handler=CifHandler(
                 names=[
@@ -129,6 +117,10 @@ def __init__(self, **kwargs):
             ),
         )
 
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
     @property
     def point_id(self) -> StringDescriptor:
         return self._point_id
@@ -163,17 +155,17 @@ class PdCwlDataPointMixin:
     wavelength.
     """
 
-    def __init__(self, **kwargs):
-        super().__init__(**kwargs)
+    def __init__(self):
+        super().__init__()
+
         self._two_theta = NumericDescriptor(
             name='two_theta',
             description='Measured 2θ diffraction angle.',
+            units='deg',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(ge=0, le=180),
+                validator=RangeValidator(ge=0, le=180),
             ),
-            units='deg',
             cif_handler=CifHandler(
                 names=[
                     '_pd_proc.2theta_scan',
@@ -182,34 +174,38 @@ def __init__(self, **kwargs):
             ),
         )
 
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
     @property
-    def two_theta(self) -> NumericDescriptor:
+    def two_theta(self):
         return self._two_theta
 
 
 class PdTofDataPointMixin:
     """Mixin for powder diffraction data points with time-of-flight."""
 
-    def __init__(self, **kwargs):
-        super().__init__(**kwargs)
+    def __init__(self):
+        super().__init__()
+
         self._time_of_flight = NumericDescriptor(
             name='time_of_flight',
             description='Measured time for time-of-flight neutron measurement.',
+            units='µs',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(ge=0),
-            ),
-            units='µs',
-            cif_handler=CifHandler(
-                names=[
-                    '_pd_meas.time_of_flight',
-                ]
+                validator=RangeValidator(ge=0),
             ),
+            cif_handler=CifHandler(names=['_pd_meas.time_of_flight']),
         )
 
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
     @property
-    def time_of_flight(self) -> NumericDescriptor:
+    def time_of_flight(self):
         return self._time_of_flight
 
 
@@ -217,6 +213,14 @@ class PdCwlDataPoint(
     PdDataPointBaseMixin,  # TODO: rename to BasePdDataPointMixin???
     PdCwlDataPointMixin,  # TODO: rename to CwlPdDataPointMixin???
     CategoryItem,  # Must be last to ensure mixins initialized first
+    # TODO: Check this. AI suggest class
+    #  CwlThompsonCoxHastings(
+    #     PeakBase, # From CategoryItem
+    #     CwlBroadeningMixin,
+    #     FcjAsymmetryMixin,
+    #  ):
+    #  But also says, that in fact, it is just for consistency. And both
+    #  orders work.
 ):
     """Powder diffraction data point for constant-wavelength
     experiments.
@@ -317,9 +321,8 @@ def _update(self, called_by_minimizer=False):
         experiment = self._parent
         experiments = experiment._parent
         project = experiments._parent
-        sample_models = project.sample_models
-        # calculator = experiment.calculator  # TODO: move from analysis
-        calculator = project.analysis.calculator
+        structures = project.structures
+        calculator = experiment.calculator
 
         initial_calc = np.zeros_like(self.x)
         calc = initial_calc
@@ -328,19 +331,19 @@ def _update(self, called_by_minimizer=False):
         #  for returning list. Warning message should be defined here,
         #  at least some of them.
         # TODO: Adapt following the _update method in bragg_sc.py
-        for linked_phase in experiment._get_valid_linked_phases(sample_models):
-            sample_model_id = linked_phase._identity.category_entry_name
-            sample_model_scale = linked_phase.scale.value
-            sample_model = sample_models[sample_model_id]
+        for linked_phase in experiment._get_valid_linked_phases(structures):
+            structure_id = linked_phase._identity.category_entry_name
+            structure_scale = linked_phase.scale.value
+            structure = structures[structure_id]
 
-            sample_model_calc = calculator.calculate_pattern(
-                sample_model,
+            structure_calc = calculator.calculate_pattern(
+                structure,
                 experiment,
                 called_by_minimizer=called_by_minimizer,
             )
 
-            sample_model_scaled_calc = sample_model_scale * sample_model_calc
-            calc += sample_model_scaled_calc
+            structure_scaled_calc = structure_scale * structure_calc
+            calc += structure_scaled_calc
 
         self._set_intensity_calc(calc + self.intensity_bkg)
 
@@ -378,7 +381,7 @@ def intensity_meas_su(self) -> np.ndarray:
         #  The current implementation is inefficient.
         #  In the future, we should extend the functionality of
         #  the NumericDescriptor to automatically replace the value
-        #  outside of the valid range (`content_validator`) with a
+        #  outside of the valid range (`validator`) with a
         #  default value (`default`), when the value is set.
         #  BraggPdExperiment._load_ascii_data_to_experiment() handles
         #  this for ASCII data, but we also need to handle CIF data and
@@ -406,10 +409,20 @@ def intensity_bkg(self) -> np.ndarray:
         )
 
 
+@DataFactory.register
 class PdCwlData(PdDataBase):
     # TODO: ???
     # _description: str = 'Powder diffraction data points for
     # constant-wavelength experiments.'
+    type_info = TypeInfo(tag='bragg-pd', description='Bragg powder CWL data')
+    compatibility = Compatibility(
+        sample_form=frozenset({SampleFormEnum.POWDER}),
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG}),
+        beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH, BeamModeEnum.TIME_OF_FLIGHT}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({CalculatorEnum.CRYSPY}),
+    )
 
     def __init__(self):
         super().__init__(item_type=PdCwlDataPoint)
@@ -474,10 +487,17 @@ def unfiltered_x(self) -> np.ndarray:
         )
 
 
+@DataFactory.register
 class PdTofData(PdDataBase):
-    # TODO: ???
-    # _description: str = 'Powder diffraction data points for
-    # time-of-flight experiments.'
+    type_info = TypeInfo(tag='bragg-pd-tof', description='Bragg powder TOF data')
+    compatibility = Compatibility(
+        sample_form=frozenset({SampleFormEnum.POWDER}),
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG}),
+        beam_mode=frozenset({BeamModeEnum.TIME_OF_FLIGHT}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({CalculatorEnum.CRYSPY, CalculatorEnum.CRYSFML}),
+    )
 
     def __init__(self):
         super().__init__(item_type=PdTofDataPoint)
diff --git a/src/easydiffraction/experiments/categories/data/bragg_sc.py b/src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py
similarity index 76%
rename from src/easydiffraction/experiments/categories/data/bragg_sc.py
rename to src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py
index c48a15e9..39552b63 100644
--- a/src/easydiffraction/experiments/categories/data/bragg_sc.py
+++ b/src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py
@@ -7,12 +7,19 @@
 
 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.metadata import CalculatorSupport
+from easydiffraction.core.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
 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.core.variable import NumericDescriptor
+from easydiffraction.core.variable import StringDescriptor
+from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import CalculatorEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
 from easydiffraction.io.cif.handler import CifHandler
 from easydiffraction.utils.logging import log
 from easydiffraction.utils.utils import sin_theta_over_lambda_to_d_spacing
@@ -30,152 +37,106 @@ def __init__(self) -> None:
             name='id',
             description='Identifier of the reflection.',
             value_spec=AttributeSpec(
-                type_=DataTypes.STRING,
                 default='0',
                 # 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-z0-9_]*$'),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_refln.id',
-                ]
+                validator=RegexValidator(pattern=r'^[A-Za-z0-9_]*$'),
             ),
+            cif_handler=CifHandler(names=['_refln.id']),
         )
         self._d_spacing = NumericDescriptor(
             name='d_spacing',
             description='The distance between lattice planes in the crystal for this reflection.',
+            units='Å',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(ge=0),
-            ),
-            units='Å',
-            cif_handler=CifHandler(
-                names=[
-                    '_refln.d_spacing',
-                ]
+                validator=RangeValidator(ge=0),
             ),
+            cif_handler=CifHandler(names=['_refln.d_spacing']),
         )
         self._sin_theta_over_lambda = NumericDescriptor(
             name='sin_theta_over_lambda',
             description='The sin(θ)/λ value for this reflection.',
+            units='Å⁻¹',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(ge=0),
-            ),
-            units='Å⁻¹',
-            cif_handler=CifHandler(
-                names=[
-                    '_refln.sin_theta_over_lambda',
-                ]
+                validator=RangeValidator(ge=0),
             ),
+            cif_handler=CifHandler(names=['_refln.sin_theta_over_lambda']),
         )
         self._index_h = NumericDescriptor(
             name='index_h',
             description='Miller index h of a measured reflection.',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_refln.index_h',
-                ]
+                validator=RangeValidator(),
             ),
+            cif_handler=CifHandler(names=['_refln.index_h']),
         )
         self._index_k = NumericDescriptor(
             name='index_k',
             description='Miller index k of a measured reflection.',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_refln.index_k',
-                ]
+                validator=RangeValidator(),
             ),
+            cif_handler=CifHandler(names=['_refln.index_k']),
         )
         self._index_l = NumericDescriptor(
             name='index_l',
             description='Miller index l of a measured reflection.',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_refln.index_l',
-                ]
+                validator=RangeValidator(),
             ),
+            cif_handler=CifHandler(names=['_refln.index_l']),
         )
         self._intensity_meas = NumericDescriptor(
             name='intensity_meas',
             description=' The intensity of the reflection derived from the measurements.',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(ge=0),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_refln.intensity_meas',
-                ]
+                validator=RangeValidator(ge=0),
             ),
+            cif_handler=CifHandler(names=['_refln.intensity_meas']),
         )
         self._intensity_meas_su = NumericDescriptor(
             name='intensity_meas_su',
             description='Standard uncertainty of the measured intensity.',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(ge=0),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_refln.intensity_meas_su',
-                ]
+                validator=RangeValidator(ge=0),
             ),
+            cif_handler=CifHandler(names=['_refln.intensity_meas_su']),
         )
         self._intensity_calc = NumericDescriptor(
             name='intensity_calc',
             description='The intensity of the reflection calculated from the atom site data.',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(ge=0),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_refln.intensity_calc',
-                ]
+                validator=RangeValidator(ge=0),
             ),
+            cif_handler=CifHandler(names=['_refln.intensity_calc']),
         )
         self._wavelength = NumericDescriptor(
             name='wavelength',
             description='The mean wavelength of radiation used to measure this reflection.',
+            units='Å',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(ge=0),
-            ),
-            units='Å',
-            cif_handler=CifHandler(
-                names=[
-                    '_refln.wavelength',
-                ]
+                validator=RangeValidator(ge=0),
             ),
+            cif_handler=CifHandler(names=['_refln.wavelength']),
         )
 
         self._identity.category_code = 'refln'
         self._identity.category_entry_name = lambda: str(self.id.value)
 
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
     @property
     def id(self) -> StringDescriptor:
         return self._id
@@ -217,9 +178,20 @@ def wavelength(self) -> NumericDescriptor:
         return self._wavelength
 
 
+@DataFactory.register
 class ReflnData(CategoryCollection):
     """Collection of reflections for single crystal diffraction data."""
 
+    type_info = TypeInfo(tag='bragg-sc', description='Bragg single-crystal reflection data')
+    compatibility = Compatibility(
+        sample_form=frozenset({SampleFormEnum.SINGLE_CRYSTAL}),
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG}),
+        beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH, BeamModeEnum.TIME_OF_FLIGHT}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({CalculatorEnum.CRYSPY}),
+    )
+
     _update_priority = 100
 
     def __init__(self):
@@ -294,40 +266,39 @@ def _update(self, called_by_minimizer=False):
         experiment = self._parent
         experiments = experiment._parent
         project = experiments._parent
-        sample_models = project.sample_models
-        # calculator = experiment.calculator  # TODO: move from analysis
-        calculator = project.analysis.calculator
+        structures = project.structures
+        calculator = experiment.calculator
 
         linked_crystal = experiment.linked_crystal
         linked_crystal_id = experiment.linked_crystal.id.value
 
-        if linked_crystal_id not in sample_models.names:
+        if linked_crystal_id not in structures.names:
             log.error(
                 f"Linked crystal ID '{linked_crystal_id}' not found in "
-                f'sample model IDs {sample_models.names}.'
+                f'structure IDs {structures.names}.'
             )
             return
 
-        sample_model_id = linked_crystal_id
-        sample_model_scale = linked_crystal.scale.value
-        sample_model = sample_models[sample_model_id]
+        structure_id = linked_crystal_id
+        structure_scale = linked_crystal.scale.value
+        structure = structures[structure_id]
 
         stol, raw_calc = calculator.calculate_structure_factors(
-            sample_model,
+            structure,
             experiment,
             called_by_minimizer=called_by_minimizer,
         )
 
         d_spacing = sin_theta_over_lambda_to_d_spacing(stol)
-        calc = sample_model_scale * raw_calc
+        calc = structure_scale * raw_calc
 
         self._set_d_spacing(d_spacing)
         self._set_sin_theta_over_lambda(stol)
         self._set_intensity_calc(calc)
 
-    ###################
-    # Public properties
-    ###################
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
 
     @property
     def d_spacing(self) -> np.ndarray:
diff --git a/src/easydiffraction/datablocks/experiment/categories/data/factory.py b/src/easydiffraction/datablocks/experiment/categories/data/factory.py
new file mode 100644
index 00000000..1ef25c0b
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/data/factory.py
@@ -0,0 +1,33 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Data collection factory — delegates to ``FactoryBase``."""
+
+from easydiffraction.core.factory import FactoryBase
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
+
+
+class DataFactory(FactoryBase):
+    """Factory for creating diffraction data collections."""
+
+    _default_rules = {
+        frozenset({
+            ('sample_form', SampleFormEnum.POWDER),
+            ('scattering_type', ScatteringTypeEnum.BRAGG),
+            ('beam_mode', BeamModeEnum.CONSTANT_WAVELENGTH),
+        }): 'bragg-pd',
+        frozenset({
+            ('sample_form', SampleFormEnum.POWDER),
+            ('scattering_type', ScatteringTypeEnum.BRAGG),
+            ('beam_mode', BeamModeEnum.TIME_OF_FLIGHT),
+        }): 'bragg-pd-tof',
+        frozenset({
+            ('sample_form', SampleFormEnum.POWDER),
+            ('scattering_type', ScatteringTypeEnum.TOTAL),
+        }): 'total-pd',
+        frozenset({
+            ('sample_form', SampleFormEnum.SINGLE_CRYSTAL),
+            ('scattering_type', ScatteringTypeEnum.BRAGG),
+        }): 'bragg-sc',
+    }
diff --git a/src/easydiffraction/experiments/categories/data/total_pd.py b/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py
similarity index 79%
rename from src/easydiffraction/experiments/categories/data/total_pd.py
rename to src/easydiffraction/datablocks/experiment/categories/data/total_pd.py
index 0e43c3a4..0aa63be5 100644
--- a/src/easydiffraction/experiments/categories/data/total_pd.py
+++ b/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py
@@ -8,13 +8,20 @@
 
 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.metadata import CalculatorSupport
+from easydiffraction.core.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
 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.core.variable import NumericDescriptor
+from easydiffraction.core.variable import StringDescriptor
+from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import CalculatorEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
 from easydiffraction.io.cif.handler import CifHandler
 
 
@@ -32,9 +39,8 @@ def __init__(self) -> None:
             name='point_id',
             description='Identifier for this data point in the dataset.',
             value_spec=AttributeSpec(
-                type_=DataTypes.STRING,
                 default='0',
-                content_validator=RegexValidator(pattern=r'^[A-Za-z0-9_]*$'),
+                validator=RegexValidator(pattern=r'^[A-Za-z0-9_]*$'),
             ),
             cif_handler=CifHandler(
                 names=[
@@ -45,12 +51,11 @@ def __init__(self) -> None:
         self._r = NumericDescriptor(
             name='r',
             description='Interatomic distance in real space.',
+            units='Å',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(ge=0),
+                validator=RangeValidator(ge=0),
             ),
-            units='Å',
             cif_handler=CifHandler(
                 names=[
                     '_pd_proc.r',  # TODO: Use PDF-specific CIF names
@@ -61,7 +66,6 @@ def __init__(self) -> None:
             name='g_r_meas',
             description='Measured pair distribution function G(r).',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
             ),
             cif_handler=CifHandler(
@@ -74,9 +78,8 @@ def __init__(self) -> None:
             name='g_r_meas_su',
             description='Standard uncertainty of measured G(r).',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(ge=0),
+                validator=RangeValidator(ge=0),
             ),
             cif_handler=CifHandler(
                 names=[
@@ -88,7 +91,6 @@ def __init__(self) -> None:
             name='g_r_calc',
             description='Calculated pair distribution function G(r).',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
             ),
             cif_handler=CifHandler(
@@ -101,9 +103,8 @@ def __init__(self) -> None:
             name='calc_status',
             description='Status code of the data point in calculation.',
             value_spec=AttributeSpec(
-                type_=DataTypes.STRING,
                 default='incl',
-                content_validator=MembershipValidator(allowed=['incl', 'excl']),
+                validator=MembershipValidator(allowed=['incl', 'excl']),
             ),
             cif_handler=CifHandler(
                 names=[
@@ -115,6 +116,10 @@ def __init__(self) -> None:
         self._identity.category_code = 'total_data'
         self._identity.category_entry_name = lambda: str(self.point_id.value)
 
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
     @property
     def point_id(self) -> StringDescriptor:
         return self._point_id
@@ -202,9 +207,8 @@ def _update(self, called_by_minimizer=False):
         experiment = self._parent
         experiments = experiment._parent
         project = experiments._parent
-        sample_models = project.sample_models
-        # calculator = experiment.calculator  # TODO: move from analysis
-        calculator = project.analysis.calculator
+        structures = project.structures
+        calculator = experiment.calculator
 
         initial_calc = np.zeros_like(self.x)
         calc = initial_calc
@@ -213,25 +217,25 @@ def _update(self, called_by_minimizer=False):
         #  for returning list. Warning message should be defined here,
         #  at least some of them.
         # TODO: Adapt following the _update method in bragg_sc.py
-        for linked_phase in experiment._get_valid_linked_phases(sample_models):
-            sample_model_id = linked_phase._identity.category_entry_name
-            sample_model_scale = linked_phase.scale.value
-            sample_model = sample_models[sample_model_id]
+        for linked_phase in experiment._get_valid_linked_phases(structures):
+            structure_id = linked_phase._identity.category_entry_name
+            structure_scale = linked_phase.scale.value
+            structure = structures[structure_id]
 
-            sample_model_calc = calculator.calculate_pattern(
-                sample_model,
+            structure_calc = calculator.calculate_pattern(
+                structure,
                 experiment,
                 called_by_minimizer=called_by_minimizer,
             )
 
-            sample_model_scaled_calc = sample_model_scale * sample_model_calc
-            calc += sample_model_scaled_calc
+            structure_scaled_calc = structure_scale * structure_calc
+            calc += structure_scaled_calc
 
         self._set_g_r_calc(calc)
 
-    ###################
-    # Public properties
-    ###################
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
 
     @property
     def calc_status(self) -> np.ndarray:
@@ -267,6 +271,7 @@ def intensity_bkg(self) -> np.ndarray:
         return np.zeros_like(self.intensity_calc)
 
 
+@DataFactory.register
 class TotalData(TotalDataBase):
     """Total scattering (PDF) data collection in r-space.
 
@@ -274,6 +279,16 @@ class TotalData(TotalDataBase):
     is always transformed to r-space.
     """
 
+    type_info = TypeInfo(tag='total-pd', description='Total scattering (PDF) data')
+    compatibility = Compatibility(
+        sample_form=frozenset({SampleFormEnum.POWDER}),
+        scattering_type=frozenset({ScatteringTypeEnum.TOTAL}),
+        beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH, BeamModeEnum.TIME_OF_FLIGHT}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({CalculatorEnum.PDFFIT}),
+    )
+
     def __init__(self):
         super().__init__(item_type=TotalDataPoint)
 
@@ -297,9 +312,9 @@ def _create_items_set_xcoord_and_id(self, values) -> None:
         # Set point IDs
         self._set_point_id([str(i + 1) for i in range(values.size)])
 
-    ###################
-    # Public properties
-    ###################
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
 
     @property
     def x(self) -> np.ndarray:
diff --git a/src/easydiffraction/datablocks/experiment/categories/excluded_regions/__init__.py b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/__init__.py
new file mode 100644
index 00000000..3356f4cf
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/__init__.py
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.datablocks.experiment.categories.excluded_regions.default import (
+    ExcludedRegion,
+)
+from easydiffraction.datablocks.experiment.categories.excluded_regions.default import (
+    ExcludedRegions,
+)
diff --git a/src/easydiffraction/experiments/categories/excluded_regions.py b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/default.py
similarity index 76%
rename from src/easydiffraction/experiments/categories/excluded_regions.py
rename to src/easydiffraction/datablocks/experiment/categories/excluded_regions/default.py
index c882fad0..2696f25c 100644
--- a/src/easydiffraction/experiments/categories/excluded_regions.py
+++ b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/default.py
@@ -2,18 +2,25 @@
 # SPDX-License-Identifier: BSD-3-Clause
 """Exclude ranges of x from fitting/plotting (masked regions)."""
 
+from __future__ import annotations
+
 from typing import List
 
 import numpy as np
 
 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.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
 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.core.variable import NumericDescriptor
+from easydiffraction.core.variable import StringDescriptor
+from easydiffraction.datablocks.experiment.categories.excluded_regions.factory import (
+    ExcludedRegionsFactory,
+)
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
 from easydiffraction.io.cif.handler import CifHandler
 from easydiffraction.utils.logging import console
 from easydiffraction.utils.utils import render_table
@@ -22,13 +29,7 @@
 class ExcludedRegion(CategoryItem):
     """Closed interval [start, end] to be excluded."""
 
-    def __init__(
-        self,
-        *,
-        id=None,  # TODO: rename as in the case of data points?
-        start=None,
-        end=None,
-    ):
+    def __init__(self):
         super().__init__()
 
         # TODO: Add point_id as for the background
@@ -36,56 +37,39 @@ def __init__(
             name='id',
             description='Identifier for this excluded region.',
             value_spec=AttributeSpec(
-                type_=DataTypes.STRING,
-                value=id,
                 default='0',
                 # 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-z0-9_]*$'),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_excluded_region.id',
-                ]
+                validator=RegexValidator(pattern=r'^[A-Za-z0-9_]*$'),
             ),
+            cif_handler=CifHandler(names=['_excluded_region.id']),
         )
         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',
-                ]
+                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',
-                ]
+                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: str(self._id.value)
 
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
     @property
     def id(self):
         return self._id
@@ -111,6 +95,7 @@ def end(self, value: float):
         self._end.value = value
 
 
+@ExcludedRegionsFactory.register
 class ExcludedRegions(CategoryCollection):
     """Collection of ExcludedRegion instances.
 
@@ -119,6 +104,14 @@ class ExcludedRegions(CategoryCollection):
     fitting and plotting.
     """
 
+    type_info = TypeInfo(
+        tag='default',
+        description='Excluded x-axis regions for fitting and plotting',
+    )
+    compatibility = Compatibility(
+        sample_form=frozenset({SampleFormEnum.POWDER}),
+    )
+
     def __init__(self):
         super().__init__(item_type=ExcludedRegion)
 
diff --git a/src/easydiffraction/datablocks/experiment/categories/excluded_regions/factory.py b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/factory.py
new file mode 100644
index 00000000..789e25e7
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/factory.py
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Excluded-regions factory — delegates entirely to ``FactoryBase``."""
+
+from __future__ import annotations
+
+from easydiffraction.core.factory import FactoryBase
+
+
+class ExcludedRegionsFactory(FactoryBase):
+    """Create excluded-regions collections by tag."""
+
+    _default_rules = {
+        frozenset(): 'default',
+    }
diff --git a/src/easydiffraction/datablocks/experiment/categories/experiment_type/__init__.py b/src/easydiffraction/datablocks/experiment/categories/experiment_type/__init__.py
new file mode 100644
index 00000000..63e6bb0b
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/experiment_type/__init__.py
@@ -0,0 +1,4 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.datablocks.experiment.categories.experiment_type.default import ExperimentType
diff --git a/src/easydiffraction/experiments/categories/experiment_type.py b/src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py
similarity index 51%
rename from src/easydiffraction/experiments/categories/experiment_type.py
rename to src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py
index babe82ee..5221e444 100644
--- a/src/easydiffraction/experiments/categories/experiment_type.py
+++ b/src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py
@@ -7,18 +7,24 @@
 ``CifHandler``.
 """
 
+from __future__ import annotations
+
 from easydiffraction.core.category import CategoryItem
-from easydiffraction.core.parameters import StringDescriptor
+from easydiffraction.core.metadata import TypeInfo
 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.core.variable import StringDescriptor
+from easydiffraction.datablocks.experiment.categories.experiment_type.factory import (
+    ExperimentTypeFactory,
+)
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import RadiationProbeEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
 from easydiffraction.io.cif.handler import CifHandler
 
 
+@ExperimentTypeFactory.register
 class ExperimentType(CategoryItem):
     """Container of categorical attributes defining experiment flavor.
 
@@ -29,128 +35,94 @@ class ExperimentType(CategoryItem):
         scattering_type: Bragg or Total.
     """
 
-    def __init__(
-        self,
-        *,
-        sample_form=None,
-        beam_mode=None,
-        radiation_probe=None,
-        scattering_type=None,
-    ):
+    type_info = TypeInfo(
+        tag='default',
+        description='Experiment type descriptor',
+    )
+
+    def __init__(self):
         super().__init__()
 
-        self._sample_form: StringDescriptor = StringDescriptor(
+        self._sample_form = 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().value,
-                content_validator=MembershipValidator(
-                    allowed=[member.value for member in SampleFormEnum]
-                ),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_expt_type.sample_form',
-                ]
+                validator=MembershipValidator(allowed=[member.value for member in SampleFormEnum]),
             ),
+            cif_handler=CifHandler(names=['_expt_type.sample_form']),
         )
 
-        self._beam_mode: StringDescriptor = StringDescriptor(
+        self._beam_mode = StringDescriptor(
             name='beam_mode',
             description='Defines whether the measurement is performed with a '
             'constant wavelength (CW) or time-of-flight (TOF) method',
             value_spec=AttributeSpec(
-                value=beam_mode,
-                type_=DataTypes.STRING,
                 default=BeamModeEnum.default().value,
-                content_validator=MembershipValidator(
-                    allowed=[member.value for member in BeamModeEnum]
-                ),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_expt_type.beam_mode',
-                ]
+                validator=MembershipValidator(allowed=[member.value for member in BeamModeEnum]),
             ),
+            cif_handler=CifHandler(names=['_expt_type.beam_mode']),
         )
-        self._radiation_probe: StringDescriptor = StringDescriptor(
+        self._radiation_probe = 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().value,
-                content_validator=MembershipValidator(
+                validator=MembershipValidator(
                     allowed=[member.value for member in RadiationProbeEnum]
                 ),
             ),
-            cif_handler=CifHandler(
-                names=[
-                    '_expt_type.radiation_probe',
-                ]
-            ),
+            cif_handler=CifHandler(names=['_expt_type.radiation_probe']),
         )
-        self._scattering_type: StringDescriptor = StringDescriptor(
+        self._scattering_type = StringDescriptor(
             name='scattering_type',
             description='Specifies whether the experiment uses Bragg scattering '
             '(for conventional structure refinement) or total scattering '
             '(for pair distribution function analysis - PDF)',
             value_spec=AttributeSpec(
-                value=scattering_type,
-                type_=DataTypes.STRING,
                 default=ScatteringTypeEnum.default().value,
-                content_validator=MembershipValidator(
+                validator=MembershipValidator(
                     allowed=[member.value for member in ScatteringTypeEnum]
                 ),
             ),
-            cif_handler=CifHandler(
-                names=[
-                    '_expt_type.scattering_type',
-                ]
-            ),
+            cif_handler=CifHandler(names=['_expt_type.scattering_type']),
         )
 
         self._identity.category_code = 'expt_type'
 
+    # ------------------------------------------------------------------
+    #  Private setters (used by factories and loaders only)
+    # ------------------------------------------------------------------
+
+    def _set_sample_form(self, value: str) -> None:
+        self._sample_form.value = value
+
+    def _set_beam_mode(self, value: str) -> None:
+        self._beam_mode.value = value
+
+    def _set_radiation_probe(self, value: str) -> None:
+        self._radiation_probe.value = value
+
+    def _set_scattering_type(self, value: str) -> None:
+        self._scattering_type.value = value
+
+    # ------------------------------------------------------------------
+    #  Public read-only properties
+    # ------------------------------------------------------------------
+
     @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/datablocks/experiment/categories/experiment_type/factory.py b/src/easydiffraction/datablocks/experiment/categories/experiment_type/factory.py
new file mode 100644
index 00000000..bf78fb53
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/experiment_type/factory.py
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Experiment-type factory — delegates entirely to ``FactoryBase``."""
+
+from __future__ import annotations
+
+from easydiffraction.core.factory import FactoryBase
+
+
+class ExperimentTypeFactory(FactoryBase):
+    """Create experiment-type descriptors by tag."""
+
+    _default_rules = {
+        frozenset(): 'default',
+    }
diff --git a/src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py b/src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py
new file mode 100644
index 00000000..f3d62fad
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py
@@ -0,0 +1,4 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.datablocks.experiment.categories.extinction.shelx import ShelxExtinction
diff --git a/src/easydiffraction/datablocks/experiment/categories/extinction/factory.py b/src/easydiffraction/datablocks/experiment/categories/extinction/factory.py
new file mode 100644
index 00000000..fbeb32e7
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/extinction/factory.py
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Extinction factory — delegates entirely to ``FactoryBase``."""
+
+from __future__ import annotations
+
+from easydiffraction.core.factory import FactoryBase
+
+
+class ExtinctionFactory(FactoryBase):
+    """Create extinction correction models by tag."""
+
+    _default_rules = {
+        frozenset(): 'shelx',
+    }
diff --git a/src/easydiffraction/experiments/categories/extinction.py b/src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py
similarity index 59%
rename from src/easydiffraction/experiments/categories/extinction.py
rename to src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py
index 329f3ca5..67dfaf94 100644
--- a/src/easydiffraction/experiments/categories/extinction.py
+++ b/src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py
@@ -1,16 +1,33 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
+"""Shelx-style isotropic extinction correction."""
+
+from __future__ import annotations
 
 from easydiffraction.core.category import CategoryItem
-from easydiffraction.core.parameters import Parameter
+from easydiffraction.core.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
 from easydiffraction.core.validation import AttributeSpec
-from easydiffraction.core.validation import DataTypes
 from easydiffraction.core.validation import RangeValidator
+from easydiffraction.core.variable import Parameter
+from easydiffraction.datablocks.experiment.categories.extinction.factory import ExtinctionFactory
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
 from easydiffraction.io.cif.handler import CifHandler
 
 
-class Extinction(CategoryItem):
-    """Extinction correction category for single crystals."""
+@ExtinctionFactory.register
+class ShelxExtinction(CategoryItem):
+    """Shelx-style isotropic extinction correction for single
+    crystals.
+    """
+
+    type_info = TypeInfo(
+        tag='shelx',
+        description='Shelx-style isotropic extinction correction',
+    )
+    compatibility = Compatibility(
+        sample_form=frozenset({SampleFormEnum.SINGLE_CRYSTAL}),
+    )
 
     def __init__(self) -> None:
         super().__init__()
@@ -18,12 +35,11 @@ def __init__(self) -> None:
         self._mosaicity = Parameter(
             name='mosaicity',
             description='Mosaicity value for extinction correction.',
+            units='deg',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=1.0,
-                content_validator=RangeValidator(),
+                validator=RangeValidator(),
             ),
-            units='deg',
             cif_handler=CifHandler(
                 names=[
                     '_extinction.mosaicity',
@@ -33,12 +49,11 @@ def __init__(self) -> None:
         self._radius = Parameter(
             name='radius',
             description='Crystal radius for extinction correction.',
+            units='µm',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=1.0,
-                content_validator=RangeValidator(),
+                validator=RangeValidator(),
             ),
-            units='µm',
             cif_handler=CifHandler(
                 names=[
                     '_extinction.radius',
@@ -48,6 +63,10 @@ def __init__(self) -> None:
 
         self._identity.category_code = 'extinction'
 
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
     @property
     def mosaicity(self):
         return self._mosaicity
diff --git a/src/easydiffraction/datablocks/experiment/categories/instrument/__init__.py b/src/easydiffraction/datablocks/experiment/categories/instrument/__init__.py
new file mode 100644
index 00000000..e4a03696
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/instrument/__init__.py
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.datablocks.experiment.categories.instrument.cwl import CwlPdInstrument
+from easydiffraction.datablocks.experiment.categories.instrument.cwl import CwlScInstrument
+from easydiffraction.datablocks.experiment.categories.instrument.tof import TofPdInstrument
+from easydiffraction.datablocks.experiment.categories.instrument.tof import TofScInstrument
diff --git a/src/easydiffraction/experiments/categories/instrument/base.py b/src/easydiffraction/datablocks/experiment/categories/instrument/base.py
similarity index 100%
rename from src/easydiffraction/experiments/categories/instrument/base.py
rename to src/easydiffraction/datablocks/experiment/categories/instrument/base.py
diff --git a/src/easydiffraction/experiments/categories/instrument/cwl.py b/src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py
similarity index 53%
rename from src/easydiffraction/experiments/categories/instrument/cwl.py
rename to src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py
index 8c714c11..924158a0 100644
--- a/src/easydiffraction/experiments/categories/instrument/cwl.py
+++ b/src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py
@@ -1,11 +1,18 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
 
-from easydiffraction.core.parameters import Parameter
+from easydiffraction.core.metadata import CalculatorSupport
+from easydiffraction.core.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
 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.core.variable import Parameter
+from easydiffraction.datablocks.experiment.categories.instrument.base import InstrumentBase
+from easydiffraction.datablocks.experiment.categories.instrument.factory import InstrumentFactory
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import CalculatorEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
 from easydiffraction.io.cif.handler import CifHandler
 
 
@@ -16,12 +23,11 @@ def __init__(self) -> None:
         self._setup_wavelength: Parameter = Parameter(
             name='wavelength',
             description='Incident neutron or X-ray wavelength',
+            units='Å',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=1.5406,
-                content_validator=RangeValidator(),
+                validator=RangeValidator(),
             ),
-            units='Å',
             cif_handler=CifHandler(
                 names=[
                     '_instr.wavelength',
@@ -40,24 +46,49 @@ def setup_wavelength(self, value):
         self._setup_wavelength.value = value
 
 
+@InstrumentFactory.register
 class CwlScInstrument(CwlInstrumentBase):
+    type_info = TypeInfo(tag='cwl-sc', description='CW single-crystal diffractometer')
+    compatibility = Compatibility(
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG}),
+        beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH}),
+        sample_form=frozenset({SampleFormEnum.SINGLE_CRYSTAL}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({CalculatorEnum.CRYSPY}),
+    )
+
     def __init__(self) -> None:
         super().__init__()
 
 
+@InstrumentFactory.register
 class CwlPdInstrument(CwlInstrumentBase):
+    type_info = TypeInfo(tag='cwl-pd', description='CW powder diffractometer')
+    compatibility = Compatibility(
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG, ScatteringTypeEnum.TOTAL}),
+        beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH}),
+        sample_form=frozenset({SampleFormEnum.POWDER}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({
+            CalculatorEnum.CRYSPY,
+            CalculatorEnum.CRYSFML,
+            CalculatorEnum.PDFFIT,
+        }),
+    )
+
     def __init__(self) -> None:
         super().__init__()
 
         self._calib_twotheta_offset: Parameter = Parameter(
             name='twotheta_offset',
             description='Instrument misalignment offset',
+            units='deg',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(),
+                validator=RangeValidator(),
             ),
-            units='deg',
             cif_handler=CifHandler(
                 names=[
                     '_instr.2theta_offset',
diff --git a/src/easydiffraction/datablocks/experiment/categories/instrument/factory.py b/src/easydiffraction/datablocks/experiment/categories/instrument/factory.py
new file mode 100644
index 00000000..7d4286af
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/instrument/factory.py
@@ -0,0 +1,30 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Instrument factory — delegates to ``FactoryBase``."""
+
+from easydiffraction.core.factory import FactoryBase
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+
+
+class InstrumentFactory(FactoryBase):
+    """Create instrument instances for supported modes."""
+
+    _default_rules = {
+        frozenset({
+            ('beam_mode', BeamModeEnum.CONSTANT_WAVELENGTH),
+            ('sample_form', SampleFormEnum.POWDER),
+        }): 'cwl-pd',
+        frozenset({
+            ('beam_mode', BeamModeEnum.CONSTANT_WAVELENGTH),
+            ('sample_form', SampleFormEnum.SINGLE_CRYSTAL),
+        }): 'cwl-sc',
+        frozenset({
+            ('beam_mode', BeamModeEnum.TIME_OF_FLIGHT),
+            ('sample_form', SampleFormEnum.POWDER),
+        }): 'tof-pd',
+        frozenset({
+            ('beam_mode', BeamModeEnum.TIME_OF_FLIGHT),
+            ('sample_form', SampleFormEnum.SINGLE_CRYSTAL),
+        }): 'tof-sc',
+    }
diff --git a/src/easydiffraction/experiments/categories/instrument/tof.py b/src/easydiffraction/datablocks/experiment/categories/instrument/tof.py
similarity index 56%
rename from src/easydiffraction/experiments/categories/instrument/tof.py
rename to src/easydiffraction/datablocks/experiment/categories/instrument/tof.py
index 0b3d6558..efbff40d 100644
--- a/src/easydiffraction/experiments/categories/instrument/tof.py
+++ b/src/easydiffraction/datablocks/experiment/categories/instrument/tof.py
@@ -1,145 +1,139 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
 
-from easydiffraction.core.parameters import Parameter
+from easydiffraction.core.metadata import CalculatorSupport
+from easydiffraction.core.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
 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.core.variable import Parameter
+from easydiffraction.datablocks.experiment.categories.instrument.base import InstrumentBase
+from easydiffraction.datablocks.experiment.categories.instrument.factory import InstrumentFactory
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import CalculatorEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
 from easydiffraction.io.cif.handler import CifHandler
 
 
+@InstrumentFactory.register
 class TofScInstrument(InstrumentBase):
+    type_info = TypeInfo(tag='tof-sc', description='TOF single-crystal diffractometer')
+    compatibility = Compatibility(
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG}),
+        beam_mode=frozenset({BeamModeEnum.TIME_OF_FLIGHT}),
+        sample_form=frozenset({SampleFormEnum.SINGLE_CRYSTAL}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({CalculatorEnum.CRYSPY}),
+    )
+
     def __init__(self) -> None:
         super().__init__()
 
 
+@InstrumentFactory.register
 class TofPdInstrument(InstrumentBase):
+    type_info = TypeInfo(tag='tof-pd', description='TOF powder diffractometer')
+    compatibility = Compatibility(
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG}),
+        beam_mode=frozenset({BeamModeEnum.TIME_OF_FLIGHT}),
+        sample_form=frozenset({SampleFormEnum.POWDER}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({CalculatorEnum.CRYSPY, CalculatorEnum.CRYSFML}),
+    )
+
     def __init__(self) -> None:
         super().__init__()
 
         self._setup_twotheta_bank: Parameter = Parameter(
             name='twotheta_bank',
             description='Detector bank position',
+            units='deg',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=150.0,
-                content_validator=RangeValidator(),
-            ),
-            units='deg',
-            cif_handler=CifHandler(
-                names=[
-                    '_instr.2theta_bank',
-                ]
+                validator=RangeValidator(),
             ),
+            cif_handler=CifHandler(names=['_instr.2theta_bank']),
         )
         self._calib_d_to_tof_offset: Parameter = Parameter(
             name='d_to_tof_offset',
             description='TOF offset',
+            units='µs',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(),
-            ),
-            units='µs',
-            cif_handler=CifHandler(
-                names=[
-                    '_instr.d_to_tof_offset',
-                ]
+                validator=RangeValidator(),
             ),
+            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',
+            units='µs/Å',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=10000.0,
-                content_validator=RangeValidator(),
-            ),
-            units='µs/Å',
-            cif_handler=CifHandler(
-                names=[
-                    '_instr.d_to_tof_linear',
-                ]
+                validator=RangeValidator(),
             ),
+            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(
-                type_=DataTypes.NUMERIC,
-                default=-0.00001,
-                content_validator=RangeValidator(),
-            ),
             units='µs/Ų',
-            cif_handler=CifHandler(
-                names=[
-                    '_instr.d_to_tof_quad',
-                ]
+            value_spec=AttributeSpec(
+                default=-0.00001,  # TODO: Fix CrysPy to accept 0
+                validator=RangeValidator(),
             ),
+            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',
+            units='µs·Å',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=0.0,
-                content_validator=RangeValidator(),
-            ),
-            units='µs·Å',
-            cif_handler=CifHandler(
-                names=[
-                    '_instr.d_to_tof_recip',
-                ]
+                validator=RangeValidator(),
             ),
+            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/datablocks/experiment/categories/linked_crystal/__init__.py b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/__init__.py
new file mode 100644
index 00000000..1a6b0b67
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/__init__.py
@@ -0,0 +1,4 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.datablocks.experiment.categories.linked_crystal.default import LinkedCrystal
diff --git a/src/easydiffraction/experiments/categories/linked_crystal.py b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py
similarity index 54%
rename from src/easydiffraction/experiments/categories/linked_crystal.py
rename to src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py
index 81417de2..e1fa8b6a 100644
--- a/src/easydiffraction/experiments/categories/linked_crystal.py
+++ b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py
@@ -1,21 +1,38 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
+"""Default linked-crystal reference (id + scale)."""
+
+from __future__ import annotations
 
 from easydiffraction.core.category import CategoryItem
-from easydiffraction.core.parameters import Parameter
-from easydiffraction.core.parameters import StringDescriptor
+from easydiffraction.core.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
 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.core.variable import Parameter
+from easydiffraction.core.variable import StringDescriptor
+from easydiffraction.datablocks.experiment.categories.linked_crystal.factory import (
+    LinkedCrystalFactory,
+)
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
 from easydiffraction.io.cif.handler import CifHandler
 
 
+@LinkedCrystalFactory.register
 class LinkedCrystal(CategoryItem):
     """Linked crystal category for referencing from the experiment for
     single crystal diffraction.
     """
 
+    type_info = TypeInfo(
+        tag='default',
+        description='Crystal reference with id and scale factor',
+    )
+    compatibility = Compatibility(
+        sample_form=frozenset({SampleFormEnum.SINGLE_CRYSTAL}),
+    )
+
     def __init__(self) -> None:
         super().__init__()
 
@@ -23,49 +40,39 @@ def __init__(self) -> None:
             name='id',
             description='Identifier of the linked crystal.',
             value_spec=AttributeSpec(
-                type_=DataTypes.STRING,
                 default='Si',
-                content_validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_sc_crystal_block.id',
-                ]
+                validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'),
             ),
+            cif_handler=CifHandler(names=['_sc_crystal_block.id']),
         )
         self._scale = Parameter(
             name='scale',
             description='Scale factor of the linked crystal.',
             value_spec=AttributeSpec(
-                type_=DataTypes.NUMERIC,
                 default=1.0,
-                content_validator=RangeValidator(),
-            ),
-            cif_handler=CifHandler(
-                names=[
-                    '_sc_crystal_block.scale',
-                ]
+                validator=RangeValidator(),
             ),
+            cif_handler=CifHandler(names=['_sc_crystal_block.scale']),
         )
 
         self._identity.category_code = 'linked_crystal'
 
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
     @property
     def id(self) -> StringDescriptor:
-        """Identifier of the linked crystal."""
         return self._id
 
     @id.setter
     def id(self, value: str):
-        """Set the linked crystal 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
diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_crystal/factory.py b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/factory.py
new file mode 100644
index 00000000..49ac1a64
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/factory.py
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Linked-crystal factory — delegates entirely to ``FactoryBase``."""
+
+from __future__ import annotations
+
+from easydiffraction.core.factory import FactoryBase
+
+
+class LinkedCrystalFactory(FactoryBase):
+    """Create linked-crystal references by tag."""
+
+    _default_rules = {
+        frozenset(): 'default',
+    }
diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_phases/__init__.py b/src/easydiffraction/datablocks/experiment/categories/linked_phases/__init__.py
new file mode 100644
index 00000000..6dd96b94
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/linked_phases/__init__.py
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.datablocks.experiment.categories.linked_phases.default import LinkedPhase
+from easydiffraction.datablocks.experiment.categories.linked_phases.default import LinkedPhases
diff --git a/src/easydiffraction/experiments/categories/linked_phases.py b/src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py
similarity index 60%
rename from src/easydiffraction/experiments/categories/linked_phases.py
rename to src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py
index 497ef69c..067683a9 100644
--- a/src/easydiffraction/experiments/categories/linked_phases.py
+++ b/src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py
@@ -2,86 +2,85 @@
 # SPDX-License-Identifier: BSD-3-Clause
 """Linked phases allow combining phases with scale factors."""
 
+from __future__ import annotations
+
 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.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
 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.core.variable import Parameter
+from easydiffraction.core.variable import StringDescriptor
+from easydiffraction.datablocks.experiment.categories.linked_phases.factory import (
+    LinkedPhasesFactory,
+)
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
 from easydiffraction.io.cif.handler import CifHandler
 
 
 class LinkedPhase(CategoryItem):
     """Link to a phase by id with a scale factor."""
 
-    def __init__(
-        self,
-        *,
-        id=None,  # TODO: need new name instead of id
-        scale=None,
-    ):
+    def __init__(self):
         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',
-                ]
+                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',
-                ]
+                validator=RangeValidator(),
             ),
+            cif_handler=CifHandler(names=['_pd_phase_block.scale']),
         )
 
         self._identity.category_code = 'linked_phases'
         self._identity.category_entry_name = lambda: str(self.id.value)
 
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
     @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
 
 
+@LinkedPhasesFactory.register
 class LinkedPhases(CategoryCollection):
     """Collection of LinkedPhase instances."""
 
+    type_info = TypeInfo(
+        tag='default',
+        description='Phase references with scale factors',
+    )
+    compatibility = Compatibility(
+        sample_form=frozenset({SampleFormEnum.POWDER}),
+    )
+
     def __init__(self):
         """Create an empty collection of linked phases."""
         super().__init__(item_type=LinkedPhase)
diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_phases/factory.py b/src/easydiffraction/datablocks/experiment/categories/linked_phases/factory.py
new file mode 100644
index 00000000..74f16616
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/linked_phases/factory.py
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Linked-phases factory — delegates entirely to ``FactoryBase``."""
+
+from __future__ import annotations
+
+from easydiffraction.core.factory import FactoryBase
+
+
+class LinkedPhasesFactory(FactoryBase):
+    """Create linked-phases collections by tag."""
+
+    _default_rules = {
+        frozenset(): 'default',
+    }
diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/__init__.py b/src/easydiffraction/datablocks/experiment/categories/peak/__init__.py
new file mode 100644
index 00000000..b335b9d3
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/peak/__init__.py
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlPseudoVoigt
+from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlSplitPseudoVoigt
+from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlThompsonCoxHastings
+from easydiffraction.datablocks.experiment.categories.peak.tof import TofPseudoVoigt
+from easydiffraction.datablocks.experiment.categories.peak.tof import TofPseudoVoigtBackToBack
+from easydiffraction.datablocks.experiment.categories.peak.tof import TofPseudoVoigtIkedaCarpenter
+from easydiffraction.datablocks.experiment.categories.peak.total import TotalGaussianDampedSinc
diff --git a/src/easydiffraction/experiments/categories/peak/base.py b/src/easydiffraction/datablocks/experiment/categories/peak/base.py
similarity index 100%
rename from src/easydiffraction/experiments/categories/peak/base.py
rename to src/easydiffraction/datablocks/experiment/categories/peak/base.py
diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/cwl.py b/src/easydiffraction/datablocks/experiment/categories/peak/cwl.py
new file mode 100644
index 00000000..aa03c80c
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/peak/cwl.py
@@ -0,0 +1,85 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Constant-wavelength peak profile classes."""
+
+from easydiffraction.core.metadata import CalculatorSupport
+from easydiffraction.core.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
+from easydiffraction.datablocks.experiment.categories.peak.base import PeakBase
+from easydiffraction.datablocks.experiment.categories.peak.cwl_mixins import CwlBroadeningMixin
+from easydiffraction.datablocks.experiment.categories.peak.cwl_mixins import (
+    EmpiricalAsymmetryMixin,
+)
+from easydiffraction.datablocks.experiment.categories.peak.cwl_mixins import FcjAsymmetryMixin
+from easydiffraction.datablocks.experiment.categories.peak.factory import PeakFactory
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import CalculatorEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
+
+
+@PeakFactory.register
+class CwlPseudoVoigt(
+    PeakBase,
+    CwlBroadeningMixin,
+):
+    """Constant-wavelength pseudo-Voigt peak shape."""
+
+    type_info = TypeInfo(tag='pseudo-voigt', description='Pseudo-Voigt profile')
+    compatibility = Compatibility(
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG}),
+        beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({CalculatorEnum.CRYSPY, CalculatorEnum.CRYSFML}),
+    )
+
+    def __init__(self) -> None:
+        super().__init__()
+
+
+@PeakFactory.register
+class CwlSplitPseudoVoigt(
+    PeakBase,
+    CwlBroadeningMixin,
+    EmpiricalAsymmetryMixin,
+):
+    """Split pseudo-Voigt (empirical asymmetry) for CWL mode."""
+
+    type_info = TypeInfo(
+        tag='split pseudo-voigt',
+        description='Split pseudo-Voigt with empirical asymmetry correction',
+    )
+    compatibility = Compatibility(
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG}),
+        beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({CalculatorEnum.CRYSPY, CalculatorEnum.CRYSFML}),
+    )
+
+    def __init__(self) -> None:
+        super().__init__()
+
+
+@PeakFactory.register
+class CwlThompsonCoxHastings(
+    PeakBase,
+    CwlBroadeningMixin,
+    FcjAsymmetryMixin,
+):
+    """Thompson–Cox–Hastings with FCJ asymmetry for CWL mode."""
+
+    type_info = TypeInfo(
+        tag='thompson-cox-hastings',
+        description='Thompson-Cox-Hastings with FCJ asymmetry correction',
+    )
+    compatibility = Compatibility(
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG}),
+        beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({CalculatorEnum.CRYSPY, CalculatorEnum.CRYSFML}),
+    )
+
+    def __init__(self) -> None:
+        super().__init__()
diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py b/src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py
new file mode 100644
index 00000000..2bc9c178
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py
@@ -0,0 +1,249 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Constant-wavelength (CWL) peak-profile component classes.
+
+This module provides classes that add broadening and asymmetry
+parameters. They are composed into concrete peak classes elsewhere via
+multiple inheritance.
+"""
+
+from easydiffraction.core.validation import AttributeSpec
+from easydiffraction.core.validation import RangeValidator
+from easydiffraction.core.variable import Parameter
+from easydiffraction.io.cif.handler import CifHandler
+
+
+class CwlBroadeningMixin:
+    """CWL Gaussian and Lorentz broadening parameters."""
+
+    def __init__(self):
+        super().__init__()
+
+        self._broad_gauss_u: Parameter = Parameter(
+            name='broad_gauss_u',
+            description='Gaussian broadening coefficient (dependent on '
+            'sample size and instrument resolution)',
+            units='deg²',
+            value_spec=AttributeSpec(
+                default=0.01,
+                validator=RangeValidator(),
+            ),
+            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)',
+            units='deg²',
+            value_spec=AttributeSpec(
+                default=-0.01,
+                validator=RangeValidator(),
+            ),
+            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)',
+            units='deg²',
+            value_spec=AttributeSpec(
+                default=0.02,
+                validator=RangeValidator(),
+            ),
+            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)',
+            units='deg',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            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)',
+            units='deg',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.broad_lorentz_y']),
+        )
+
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
+    @property
+    def broad_gauss_u(self) -> Parameter:
+        return self._broad_gauss_u
+
+    @broad_gauss_u.setter
+    def broad_gauss_u(self, value):
+        self._broad_gauss_u.value = value
+
+    @property
+    def broad_gauss_v(self) -> Parameter:
+        return self._broad_gauss_v
+
+    @broad_gauss_v.setter
+    def broad_gauss_v(self, value):
+        self._broad_gauss_v.value = value
+
+    @property
+    def broad_gauss_w(self) -> Parameter:
+        return self._broad_gauss_w
+
+    @broad_gauss_w.setter
+    def broad_gauss_w(self, value):
+        self._broad_gauss_w.value = value
+
+    @property
+    def broad_lorentz_x(self) -> Parameter:
+        return self._broad_lorentz_x
+
+    @broad_lorentz_x.setter
+    def broad_lorentz_x(self, value):
+        self._broad_lorentz_x.value = value
+
+    @property
+    def broad_lorentz_y(self) -> Parameter:
+        return self._broad_lorentz_y
+
+    @broad_lorentz_y.setter
+    def broad_lorentz_y(self, value):
+        self._broad_lorentz_y.value = value
+
+
+class EmpiricalAsymmetryMixin:
+    """Empirical CWL peak asymmetry parameters."""
+
+    def __init__(self):
+        super().__init__()
+
+        self._asym_empir_1: Parameter = Parameter(
+            name='asym_empir_1',
+            description='Empirical asymmetry coefficient p1',
+            units='',
+            value_spec=AttributeSpec(
+                default=0.1,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.asym_empir_1']),
+        )
+        self._asym_empir_2: Parameter = Parameter(
+            name='asym_empir_2',
+            description='Empirical asymmetry coefficient p2',
+            units='',
+            value_spec=AttributeSpec(
+                default=0.2,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.asym_empir_2']),
+        )
+        self._asym_empir_3: Parameter = Parameter(
+            name='asym_empir_3',
+            description='Empirical asymmetry coefficient p3',
+            units='',
+            value_spec=AttributeSpec(
+                default=0.3,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.asym_empir_3']),
+        )
+        self._asym_empir_4: Parameter = Parameter(
+            name='asym_empir_4',
+            description='Empirical asymmetry coefficient p4',
+            units='',
+            value_spec=AttributeSpec(
+                default=0.4,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.asym_empir_4']),
+        )
+
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
+    @property
+    def asym_empir_1(self) -> Parameter:
+        return self._asym_empir_1
+
+    @asym_empir_1.setter
+    def asym_empir_1(self, value):
+        self._asym_empir_1.value = value
+
+    @property
+    def asym_empir_2(self) -> Parameter:
+        return self._asym_empir_2
+
+    @asym_empir_2.setter
+    def asym_empir_2(self, value):
+        self._asym_empir_2.value = value
+
+    @property
+    def asym_empir_3(self) -> Parameter:
+        return self._asym_empir_3
+
+    @asym_empir_3.setter
+    def asym_empir_3(self, value):
+        self._asym_empir_3.value = value
+
+    @property
+    def asym_empir_4(self) -> Parameter:
+        return self._asym_empir_4
+
+    @asym_empir_4.setter
+    def asym_empir_4(self, value):
+        self._asym_empir_4.value = value
+
+
+class FcjAsymmetryMixin:
+    """Finger–Cox–Jephcoat (FCJ) asymmetry parameters."""
+
+    def __init__(self):
+        super().__init__()
+
+        self._asym_fcj_1: Parameter = Parameter(
+            name='asym_fcj_1',
+            description='Finger-Cox-Jephcoat asymmetry parameter 1',
+            units='',
+            value_spec=AttributeSpec(
+                default=0.01,
+                validator=RangeValidator(),
+            ),
+            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',
+            units='',
+            value_spec=AttributeSpec(
+                default=0.02,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.asym_fcj_2']),
+        )
+
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
+    @property
+    def asym_fcj_1(self):
+        return self._asym_fcj_1
+
+    @asym_fcj_1.setter
+    def asym_fcj_1(self, value):
+        self._asym_fcj_1.value = value
+
+    @property
+    def asym_fcj_2(self):
+        return self._asym_fcj_2
+
+    @asym_fcj_2.setter
+    def asym_fcj_2(self, value):
+        self._asym_fcj_2.value = value
diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/factory.py b/src/easydiffraction/datablocks/experiment/categories/peak/factory.py
new file mode 100644
index 00000000..1992b633
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/peak/factory.py
@@ -0,0 +1,26 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Peak profile factory — delegates to ``FactoryBase``."""
+
+from easydiffraction.core.factory import FactoryBase
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import PeakProfileTypeEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
+
+
+class PeakFactory(FactoryBase):
+    """Factory for creating peak profile objects."""
+
+    _default_rules = {
+        frozenset({
+            ('scattering_type', ScatteringTypeEnum.BRAGG),
+            ('beam_mode', BeamModeEnum.CONSTANT_WAVELENGTH),
+        }): PeakProfileTypeEnum.PSEUDO_VOIGT,
+        frozenset({
+            ('scattering_type', ScatteringTypeEnum.BRAGG),
+            ('beam_mode', BeamModeEnum.TIME_OF_FLIGHT),
+        }): PeakProfileTypeEnum.PSEUDO_VOIGT_IKEDA_CARPENTER,
+        frozenset({
+            ('scattering_type', ScatteringTypeEnum.TOTAL),
+        }): PeakProfileTypeEnum.GAUSSIAN_DAMPED_SINC,
+    }
diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/tof.py b/src/easydiffraction/datablocks/experiment/categories/peak/tof.py
new file mode 100644
index 00000000..1c70b65b
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/peak/tof.py
@@ -0,0 +1,84 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Time-of-flight peak profile classes."""
+
+from easydiffraction.core.metadata import CalculatorSupport
+from easydiffraction.core.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
+from easydiffraction.datablocks.experiment.categories.peak.base import PeakBase
+from easydiffraction.datablocks.experiment.categories.peak.factory import PeakFactory
+from easydiffraction.datablocks.experiment.categories.peak.tof_mixins import (
+    IkedaCarpenterAsymmetryMixin,
+)
+from easydiffraction.datablocks.experiment.categories.peak.tof_mixins import TofBroadeningMixin
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import CalculatorEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
+
+
+@PeakFactory.register
+class TofPseudoVoigt(
+    PeakBase,
+    TofBroadeningMixin,
+):
+    """Time-of-flight pseudo-Voigt peak shape."""
+
+    type_info = TypeInfo(tag='tof-pseudo-voigt', description='TOF pseudo-Voigt profile')
+    compatibility = Compatibility(
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG}),
+        beam_mode=frozenset({BeamModeEnum.TIME_OF_FLIGHT}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({CalculatorEnum.CRYSPY, CalculatorEnum.CRYSFML}),
+    )
+
+    def __init__(self) -> None:
+        super().__init__()
+
+
+@PeakFactory.register
+class TofPseudoVoigtIkedaCarpenter(
+    PeakBase,
+    TofBroadeningMixin,
+    IkedaCarpenterAsymmetryMixin,
+):
+    """TOF pseudo-Voigt with Ikeda–Carpenter asymmetry."""
+
+    type_info = TypeInfo(
+        tag='pseudo-voigt * ikeda-carpenter',
+        description='Pseudo-Voigt with Ikeda-Carpenter asymmetry correction',
+    )
+    compatibility = Compatibility(
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG}),
+        beam_mode=frozenset({BeamModeEnum.TIME_OF_FLIGHT}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({CalculatorEnum.CRYSPY, CalculatorEnum.CRYSFML}),
+    )
+
+    def __init__(self) -> None:
+        super().__init__()
+
+
+@PeakFactory.register
+class TofPseudoVoigtBackToBack(
+    PeakBase,
+    TofBroadeningMixin,
+    IkedaCarpenterAsymmetryMixin,
+):
+    """TOF back-to-back pseudo-Voigt with asymmetry."""
+
+    type_info = TypeInfo(
+        tag='pseudo-voigt * back-to-back',
+        description='TOF back-to-back pseudo-Voigt with asymmetry',
+    )
+    compatibility = Compatibility(
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG}),
+        beam_mode=frozenset({BeamModeEnum.TIME_OF_FLIGHT}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({CalculatorEnum.CRYSPY, CalculatorEnum.CRYSFML}),
+    )
+
+    def __init__(self) -> None:
+        super().__init__()
diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/tof_mixins.py b/src/easydiffraction/datablocks/experiment/categories/peak/tof_mixins.py
new file mode 100644
index 00000000..01a10b26
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/peak/tof_mixins.py
@@ -0,0 +1,218 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Time-of-flight (TOF) peak-profile component classes.
+
+Defines classes that add Gaussian/Lorentz broadening, mixing, and
+Ikeda–Carpenter asymmetry parameters used by TOF peak shapes. This
+module provides classes that add broadening and asymmetry parameters.
+They are composed into concrete peak classes elsewhere via multiple
+inheritance.
+"""
+
+from easydiffraction.core.validation import AttributeSpec
+from easydiffraction.core.validation import RangeValidator
+from easydiffraction.core.variable import Parameter
+from easydiffraction.io.cif.handler import CifHandler
+
+
+class TofBroadeningMixin:
+    """TOF Gaussian/Lorentz broadening and mixing parameters."""
+
+    def __init__(self):
+        super().__init__()
+
+        self._broad_gauss_sigma_0 = Parameter(
+            name='gauss_sigma_0',
+            description='Gaussian broadening coefficient (instrumental resolution)',
+            units='µs²',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.gauss_sigma_0']),
+        )
+        self._broad_gauss_sigma_1 = Parameter(
+            name='gauss_sigma_1',
+            description='Gaussian broadening coefficient (dependent on d-spacing)',
+            units='µs/Å',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.gauss_sigma_1']),
+        )
+        self._broad_gauss_sigma_2 = Parameter(
+            name='gauss_sigma_2',
+            description='Gaussian broadening coefficient (instrument-dependent term)',
+            units='µs²/Ų',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.gauss_sigma_2']),
+        )
+        self._broad_lorentz_gamma_0 = Parameter(
+            name='lorentz_gamma_0',
+            description='Lorentzian broadening coefficient (dependent on microstrain effects)',
+            units='µs',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.lorentz_gamma_0']),
+        )
+        self._broad_lorentz_gamma_1 = Parameter(
+            name='lorentz_gamma_1',
+            description='Lorentzian broadening coefficient (dependent on d-spacing)',
+            units='µs/Å',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.lorentz_gamma_1']),
+        )
+        self._broad_lorentz_gamma_2 = Parameter(
+            name='lorentz_gamma_2',
+            description='Lorentzian broadening coefficient (instrument-dependent term)',
+            units='µs²/Ų',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.lorentz_gamma_2']),
+        )
+        self._broad_mix_beta_0 = Parameter(
+            name='mix_beta_0',
+            description='Mixing parameter. Defines the ratio of Gaussian '
+            'to Lorentzian contributions in TOF profiles',
+            units='deg',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.mix_beta_0']),
+        )
+        self._broad_mix_beta_1 = Parameter(
+            name='mix_beta_1',
+            description='Mixing parameter. Defines the ratio of Gaussian '
+            'to Lorentzian contributions in TOF profiles',
+            units='deg',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.mix_beta_1']),
+        )
+
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
+    @property
+    def broad_gauss_sigma_0(self):
+        return self._broad_gauss_sigma_0
+
+    @broad_gauss_sigma_0.setter
+    def broad_gauss_sigma_0(self, value):
+        self._broad_gauss_sigma_0.value = value
+
+    @property
+    def broad_gauss_sigma_1(self):
+        return self._broad_gauss_sigma_1
+
+    @broad_gauss_sigma_1.setter
+    def broad_gauss_sigma_1(self, value):
+        self._broad_gauss_sigma_1.value = value
+
+    @property
+    def broad_gauss_sigma_2(self):
+        return self._broad_gauss_sigma_2
+
+    @broad_gauss_sigma_2.setter
+    def broad_gauss_sigma_2(self, value):
+        """Set Gaussian sigma_2 parameter."""
+        self._broad_gauss_sigma_2.value = value
+
+    @property
+    def broad_lorentz_gamma_0(self):
+        return self._broad_lorentz_gamma_0
+
+    @broad_lorentz_gamma_0.setter
+    def broad_lorentz_gamma_0(self, value):
+        self._broad_lorentz_gamma_0.value = value
+
+    @property
+    def broad_lorentz_gamma_1(self):
+        return self._broad_lorentz_gamma_1
+
+    @broad_lorentz_gamma_1.setter
+    def broad_lorentz_gamma_1(self, value):
+        self._broad_lorentz_gamma_1.value = value
+
+    @property
+    def broad_lorentz_gamma_2(self):
+        return self._broad_lorentz_gamma_2
+
+    @broad_lorentz_gamma_2.setter
+    def broad_lorentz_gamma_2(self, value):
+        self._broad_lorentz_gamma_2.value = value
+
+    @property
+    def broad_mix_beta_0(self):
+        return self._broad_mix_beta_0
+
+    @broad_mix_beta_0.setter
+    def broad_mix_beta_0(self, value):
+        self._broad_mix_beta_0.value = value
+
+    @property
+    def broad_mix_beta_1(self):
+        return self._broad_mix_beta_1
+
+    @broad_mix_beta_1.setter
+    def broad_mix_beta_1(self, value):
+        self._broad_mix_beta_1.value = value
+
+
+class IkedaCarpenterAsymmetryMixin:
+    """Ikeda–Carpenter asymmetry parameters."""
+
+    def __init__(self):
+        super().__init__()
+
+        self._asym_alpha_0 = Parameter(
+            name='asym_alpha_0',
+            description='Ikeda-Carpenter asymmetry parameter α₀',
+            units='',  # TODO
+            value_spec=AttributeSpec(
+                default=0.01,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.asym_alpha_0']),
+        )
+        self._asym_alpha_1 = Parameter(
+            name='asym_alpha_1',
+            description='Ikeda-Carpenter asymmetry parameter α₁',
+            units='',  # TODO
+            value_spec=AttributeSpec(
+                default=0.02,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.asym_alpha_1']),
+        )
+
+    @property
+    def asym_alpha_0(self):
+        return self._asym_alpha_0
+
+    @asym_alpha_0.setter
+    def asym_alpha_0(self, value):
+        self._asym_alpha_0.value = value
+
+    @property
+    def asym_alpha_1(self):
+        return self._asym_alpha_1
+
+    @asym_alpha_1.setter
+    def asym_alpha_1(self, value):
+        self._asym_alpha_1.value = value
diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/total.py b/src/easydiffraction/datablocks/experiment/categories/peak/total.py
new file mode 100644
index 00000000..a1166c1a
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/peak/total.py
@@ -0,0 +1,36 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Total-scattering (PDF) peak profile classes."""
+
+from easydiffraction.core.metadata import CalculatorSupport
+from easydiffraction.core.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
+from easydiffraction.datablocks.experiment.categories.peak.base import PeakBase
+from easydiffraction.datablocks.experiment.categories.peak.factory import PeakFactory
+from easydiffraction.datablocks.experiment.categories.peak.total_mixins import TotalBroadeningMixin
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import CalculatorEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
+
+
+@PeakFactory.register
+class TotalGaussianDampedSinc(
+    PeakBase,
+    TotalBroadeningMixin,
+):
+    """Gaussian-damped sinc peak for total scattering (PDF)."""
+
+    type_info = TypeInfo(
+        tag='gaussian-damped-sinc',
+        description='Gaussian-damped sinc for pair distribution function analysis',
+    )
+    compatibility = Compatibility(
+        scattering_type=frozenset({ScatteringTypeEnum.TOTAL}),
+        beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH, BeamModeEnum.TIME_OF_FLIGHT}),
+    )
+    calculator_support = CalculatorSupport(
+        calculators=frozenset({CalculatorEnum.PDFFIT}),
+    )
+
+    def __init__(self) -> None:
+        super().__init__()
diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/total_mixins.py b/src/easydiffraction/datablocks/experiment/categories/peak/total_mixins.py
new file mode 100644
index 00000000..139e37dd
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/categories/peak/total_mixins.py
@@ -0,0 +1,137 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Total scattering / pair distribution function (PDF) peak-profile
+component classes.
+
+This module provides classes that add broadening and asymmetry
+parameters. They are composed into concrete peak classes elsewhere via
+multiple inheritance.
+"""
+
+from easydiffraction.core.validation import AttributeSpec
+from easydiffraction.core.validation import RangeValidator
+from easydiffraction.core.variable import Parameter
+from easydiffraction.io.cif.handler import CifHandler
+
+
+class TotalBroadeningMixin:
+    """PDF broadening/damping/sharpening parameters."""
+
+    def __init__(self):
+        super().__init__()
+
+        self._damp_q = Parameter(
+            name='damp_q',
+            description='Instrumental Q-resolution damping factor '
+            '(affects high-r PDF peak amplitude)',
+            units='Å⁻¹',
+            value_spec=AttributeSpec(
+                default=0.05,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.damp_q']),
+        )
+        self._broad_q = Parameter(
+            name='broad_q',
+            description='Quadratic PDF peak broadening coefficient '
+            '(thermal and model uncertainty contribution)',
+            units='Å⁻²',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.broad_q']),
+        )
+        self._cutoff_q = Parameter(
+            name='cutoff_q',
+            description='Q-value cutoff applied to model PDF for Fourier '
+            'transform (controls real-space resolution)',
+            units='Å⁻¹',
+            value_spec=AttributeSpec(
+                default=25.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.cutoff_q']),
+        )
+        self._sharp_delta_1 = Parameter(
+            name='sharp_delta_1',
+            description='PDF peak sharpening coefficient (1/r dependence)',
+            units='Å',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.sharp_delta_1']),
+        )
+        self._sharp_delta_2 = Parameter(
+            name='sharp_delta_2',
+            description='PDF peak sharpening coefficient (1/r² dependence)',
+            units='Ų',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.sharp_delta_2']),
+        )
+        self._damp_particle_diameter = Parameter(
+            name='damp_particle_diameter',
+            description='Particle diameter for spherical envelope damping correction in PDF',
+            units='Å',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_peak.damp_particle_diameter']),
+        )
+
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
+    @property
+    def damp_q(self):
+        return self._damp_q
+
+    @damp_q.setter
+    def damp_q(self, value):
+        self._damp_q.value = value
+
+    @property
+    def broad_q(self):
+        return self._broad_q
+
+    @broad_q.setter
+    def broad_q(self, value):
+        self._broad_q.value = value
+
+    @property
+    def cutoff_q(self) -> Parameter:
+        return self._cutoff_q
+
+    @cutoff_q.setter
+    def cutoff_q(self, value):
+        self._cutoff_q.value = value
+
+    @property
+    def sharp_delta_1(self) -> Parameter:
+        return self._sharp_delta_1
+
+    @sharp_delta_1.setter
+    def sharp_delta_1(self, value):
+        self._sharp_delta_1.value = value
+
+    @property
+    def sharp_delta_2(self):
+        return self._sharp_delta_2
+
+    @sharp_delta_2.setter
+    def sharp_delta_2(self, value):
+        self._sharp_delta_2.value = value
+
+    @property
+    def damp_particle_diameter(self):
+        return self._damp_particle_diameter
+
+    @damp_particle_diameter.setter
+    def damp_particle_diameter(self, value):
+        self._damp_particle_diameter.value = value
diff --git a/src/easydiffraction/datablocks/experiment/collection.py b/src/easydiffraction/datablocks/experiment/collection.py
new file mode 100644
index 00000000..854b77ad
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/collection.py
@@ -0,0 +1,125 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Collection of experiment data blocks."""
+
+from typeguard import typechecked
+
+from easydiffraction.core.datablock import DatablockCollection
+from easydiffraction.datablocks.experiment.item.base import ExperimentBase
+from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory
+from easydiffraction.utils.logging import console
+
+
+class Experiments(DatablockCollection):
+    """Collection of Experiment data blocks.
+
+    Provides convenience constructors for common creation patterns and
+    helper methods for simple presentation of collection contents.
+    """
+
+    def __init__(self) -> None:
+        super().__init__(item_type=ExperimentBase)
+
+    # ------------------------------------------------------------------
+    # Public methods
+    # ------------------------------------------------------------------
+
+    # TODO: Make abstract in DatablockCollection?
+    @typechecked
+    def create(
+        self,
+        *,
+        name: str,
+        sample_form: str | None = None,
+        beam_mode: str | None = None,
+        radiation_probe: str | None = None,
+        scattering_type: str | None = None,
+    ) -> None:
+        """Add an experiment without associating a data file.
+
+        Args:
+            name: Experiment identifier.
+            sample_form: Sample form (e.g. ``'powder'``).
+            beam_mode: Beam mode (e.g. ``'constant wavelength'``).
+            radiation_probe: Radiation probe (e.g. ``'neutron'``).
+            scattering_type: Scattering type (e.g. ``'bragg'``).
+        """
+        experiment = ExperimentFactory.from_scratch(
+            name=name,
+            sample_form=sample_form,
+            beam_mode=beam_mode,
+            radiation_probe=radiation_probe,
+            scattering_type=scattering_type,
+        )
+        self.add(experiment)
+
+    # TODO: Move to DatablockCollection?
+    @typechecked
+    def add_from_cif_str(
+        self,
+        cif_str: str,
+    ) -> None:
+        """Add an experiment from a CIF string.
+
+        Args:
+            cif_str: Full CIF document as a string.
+        """
+        experiment = ExperimentFactory.from_cif_str(cif_str)
+        self.add(experiment)
+
+    # TODO: Move to DatablockCollection?
+    @typechecked
+    def add_from_cif_path(
+        self,
+        cif_path: str,
+    ) -> None:
+        """Add an experiment from a CIF file path.
+
+        Args:
+            cif_path(str): Path to a CIF document.
+        """
+        experiment = ExperimentFactory.from_cif_path(cif_path)
+        self.add(experiment)
+
+    @typechecked
+    def add_from_data_path(
+        self,
+        *,
+        name: str,
+        data_path: str,
+        sample_form: str | None = None,
+        beam_mode: str | None = None,
+        radiation_probe: str | None = None,
+        scattering_type: str | None = None,
+    ) -> None:
+        """Add an experiment from a data file path.
+
+        Args:
+            name: Experiment identifier.
+            data_path: Path to the measured data file.
+            sample_form: Sample form (e.g. ``'powder'``).
+            beam_mode: Beam mode (e.g. ``'constant wavelength'``).
+            radiation_probe: Radiation probe (e.g. ``'neutron'``).
+            scattering_type: Scattering type (e.g. ``'bragg'``).
+        """
+        experiment = ExperimentFactory.from_data_path(
+            name=name,
+            data_path=data_path,
+            sample_form=sample_form,
+            beam_mode=beam_mode,
+            radiation_probe=radiation_probe,
+            scattering_type=scattering_type,
+        )
+        self.add(experiment)
+
+    # TODO: Move to DatablockCollection?
+    def show_names(self) -> None:
+        """List all experiment names in the collection."""
+        console.paragraph('Defined experiments' + ' 🔬')
+        console.print(self.names)
+
+    # TODO: Move to DatablockCollection?
+    def show_params(self) -> None:
+        """Show parameters of all experiments in the collection."""
+        for experiment in self.values():
+            experiment.show_params()
diff --git a/src/easydiffraction/datablocks/experiment/item/__init__.py b/src/easydiffraction/datablocks/experiment/item/__init__.py
new file mode 100644
index 00000000..ffe5775d
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/item/__init__.py
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.datablocks.experiment.item.base import ExperimentBase
+from easydiffraction.datablocks.experiment.item.base import PdExperimentBase
+from easydiffraction.datablocks.experiment.item.bragg_pd import BraggPdExperiment
+from easydiffraction.datablocks.experiment.item.bragg_sc import CwlScExperiment
+from easydiffraction.datablocks.experiment.item.bragg_sc import TofScExperiment
+from easydiffraction.datablocks.experiment.item.total_pd import TotalPdExperiment
diff --git a/src/easydiffraction/datablocks/experiment/item/base.py b/src/easydiffraction/datablocks/experiment/item/base.py
new file mode 100644
index 00000000..24c6ee6e
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/item/base.py
@@ -0,0 +1,672 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Base classes for experiment datablock items."""
+
+from __future__ import annotations
+
+from abc import abstractmethod
+from typing import TYPE_CHECKING
+from typing import Any
+from typing import List
+
+from easydiffraction.core.datablock import DatablockItem
+from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory
+from easydiffraction.datablocks.experiment.categories.excluded_regions.factory import (
+    ExcludedRegionsFactory,
+)
+from easydiffraction.datablocks.experiment.categories.extinction.factory import ExtinctionFactory
+from easydiffraction.datablocks.experiment.categories.instrument.factory import InstrumentFactory
+from easydiffraction.datablocks.experiment.categories.linked_crystal.factory import (
+    LinkedCrystalFactory,
+)
+from easydiffraction.datablocks.experiment.categories.linked_phases.factory import (
+    LinkedPhasesFactory,
+)
+from easydiffraction.datablocks.experiment.categories.peak.factory import PeakFactory
+from easydiffraction.io.cif.serialize import experiment_to_cif
+from easydiffraction.utils.logging import console
+from easydiffraction.utils.logging import log
+from easydiffraction.utils.utils import render_cif
+
+if TYPE_CHECKING:
+    from easydiffraction.datablocks.experiment.categories.experiment_type import ExperimentType
+    from easydiffraction.datablocks.structure.collection import Structures
+
+
+class ExperimentBase(DatablockItem):
+    """Base class for all experiment datablock items with only core
+    attributes.
+    """
+
+    def __init__(
+        self,
+        *,
+        name: str,
+        type: ExperimentType,
+    ):
+        super().__init__()
+        self._name = name
+        self._type = type
+        self._calculator = None
+        self._calculator_type: str | None = None
+        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 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 as CIF text."""
+        experiment_cif = super().as_cif
+        paragraph_title: str = f"Experiment 🔬 '{self.name}' as cif"
+        console.paragraph(paragraph_title)
+        render_cif(experiment_cif)
+
+    @abstractmethod
+    def _load_ascii_data_to_experiment(self, data_path: str) -> None:
+        """Load ASCII data from file into the experiment data category.
+
+        Args:
+            data_path: Path to the ASCII file to load.
+        """
+        raise NotImplementedError()
+
+    # ------------------------------------------------------------------
+    #  Calculator (switchable-category pattern)
+    # ------------------------------------------------------------------
+
+    @property
+    def calculator(self):
+        """The active calculator instance for this experiment.
+
+        Auto-resolved on first access from the experiment's data
+        category ``calculator_support`` and
+        ``CalculatorFactory._default_rules``.
+        """
+        if self._calculator is None:
+            self._resolve_calculator()
+        return self._calculator
+
+    @property
+    def calculator_type(self) -> str:
+        """Tag of the active calculator backend (e.g. ``'cryspy'``)."""
+        if self._calculator_type is None:
+            self._resolve_calculator()
+        return self._calculator_type
+
+    @calculator_type.setter
+    def calculator_type(self, tag: str) -> None:
+        """Switch to a different calculator backend.
+
+        Args:
+            tag: Calculator tag (e.g. ``'cryspy'``, ``'crysfml'``,
+                ``'pdffit'``).
+        """
+        from easydiffraction.analysis.calculators.factory import CalculatorFactory
+
+        supported = self._supported_calculator_tags()
+        if tag not in supported:
+            log.warning(
+                f"Unsupported calculator '{tag}' for experiment "
+                f"'{self.name}'. Supported: {supported}. "
+                f"For more information, use 'show_supported_calculator_types()'",
+            )
+            return
+        self._calculator = CalculatorFactory.create(tag)
+        self._calculator_type = tag
+        console.paragraph(f"Calculator for experiment '{self.name}' changed to")
+        console.print(tag)
+
+    def show_supported_calculator_types(self) -> None:
+        """Print a table of calculator backends supported by this
+        experiment.
+        """
+        from easydiffraction.analysis.calculators.factory import CalculatorFactory
+
+        supported_tags = self._supported_calculator_tags()
+        all_classes = CalculatorFactory._supported_map()
+        columns_headers = ['Type', 'Description']
+        columns_alignment = ['left', 'left']
+        columns_data = [
+            [cls.type_info.tag, cls.type_info.description]
+            for tag, cls in all_classes.items()
+            if tag in supported_tags
+        ]
+        from easydiffraction.utils.utils import render_table
+
+        console.paragraph('Supported calculator types')
+        render_table(
+            columns_headers=columns_headers,
+            columns_alignment=columns_alignment,
+            columns_data=columns_data,
+        )
+
+    def show_current_calculator_type(self) -> None:
+        """Print the name of the currently active calculator."""
+        console.paragraph('Current calculator type')
+        console.print(self.calculator_type)
+
+    def _resolve_calculator(self) -> None:
+        """Auto-resolve the default calculator from the data category's
+        ``calculator_support`` and
+        ``CalculatorFactory._default_rules``.
+        """
+        from easydiffraction.analysis.calculators.factory import CalculatorFactory
+
+        tag = CalculatorFactory.default_tag(
+            scattering_type=self.type.scattering_type.value,
+        )
+        supported = self._supported_calculator_tags()
+        if supported and tag not in supported:
+            tag = supported[0]
+        self._calculator = CalculatorFactory.create(tag)
+        self._calculator_type = tag
+
+    def _supported_calculator_tags(self) -> list[str]:
+        """Return calculator tags supported by this experiment.
+
+        Intersects the data category's ``calculator_support`` with
+        calculators whose engines are importable.
+        """
+        from easydiffraction.analysis.calculators.factory import CalculatorFactory
+
+        available = CalculatorFactory.supported_tags()
+        data = getattr(self, '_data', None)
+        if data is not None:
+            data_support = getattr(data, 'calculator_support', None)
+            if data_support and data_support.calculators:
+                return [t for t in available if t in data_support.calculators]
+        return available
+
+
+class ScExperimentBase(ExperimentBase):
+    """Base class for all single crystal experiments."""
+
+    def __init__(
+        self,
+        *,
+        name: str,
+        type: ExperimentType,
+    ) -> None:
+        super().__init__(name=name, type=type)
+
+        self._extinction_type: str = ExtinctionFactory.default_tag()
+        self._extinction = ExtinctionFactory.create(self._extinction_type)
+        self._linked_crystal_type: str = LinkedCrystalFactory.default_tag()
+        self._linked_crystal = LinkedCrystalFactory.create(self._linked_crystal_type)
+        self._instrument_type: str = InstrumentFactory.default_tag(
+            scattering_type=self.type.scattering_type.value,
+            beam_mode=self.type.beam_mode.value,
+            sample_form=self.type.sample_form.value,
+        )
+        self._instrument = InstrumentFactory.create(self._instrument_type)
+        self._data_type: str = DataFactory.default_tag(
+            sample_form=self.type.sample_form.value,
+            beam_mode=self.type.beam_mode.value,
+            scattering_type=self.type.scattering_type.value,
+        )
+        self._data = DataFactory.create(self._data_type)
+
+    @abstractmethod
+    def _load_ascii_data_to_experiment(self, data_path: str) -> None:
+        """Load single crystal data from an ASCII file.
+
+        Args:
+            data_path: Path to data file with columns compatible with
+                the beam mode.
+        """
+        pass
+
+    # ------------------------------------------------------------------
+    #  Extinction (switchable-category pattern)
+    # ------------------------------------------------------------------
+
+    @property
+    def extinction(self):
+        """Active extinction correction model."""
+        return self._extinction
+
+    @property
+    def extinction_type(self) -> str:
+        """Tag of the active extinction correction model."""
+        return self._extinction_type
+
+    @extinction_type.setter
+    def extinction_type(self, new_type: str) -> None:
+        """Switch to a different extinction correction model.
+
+        Args:
+            new_type: Extinction tag (e.g. ``'shelx'``).
+        """
+        supported_tags = ExtinctionFactory.supported_tags()
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported extinction type '{new_type}'. "
+                f'Supported: {supported_tags}. '
+                f"For more information, use 'show_supported_extinction_types()'",
+            )
+            return
+
+        self._extinction = ExtinctionFactory.create(new_type)
+        self._extinction_type = new_type
+        console.paragraph(f"Extinction type for experiment '{self.name}' changed to")
+        console.print(new_type)
+
+    def show_supported_extinction_types(self) -> None:
+        """Print a table of supported extinction correction models."""
+        ExtinctionFactory.show_supported()
+
+    def show_current_extinction_type(self) -> None:
+        """Print the currently used extinction correction model."""
+        console.paragraph('Current extinction type')
+        console.print(self.extinction_type)
+
+    # ------------------------------------------------------------------
+    #  Linked crystal (switchable-category pattern)
+    # ------------------------------------------------------------------
+
+    @property
+    def linked_crystal(self):
+        """Linked crystal model for this experiment."""
+        return self._linked_crystal
+
+    @property
+    def linked_crystal_type(self) -> str:
+        """Tag of the active linked-crystal reference type."""
+        return self._linked_crystal_type
+
+    @linked_crystal_type.setter
+    def linked_crystal_type(self, new_type: str) -> None:
+        """Switch to a different linked-crystal reference type.
+
+        Args:
+            new_type: Linked-crystal tag (e.g. ``'default'``).
+        """
+        supported_tags = LinkedCrystalFactory.supported_tags()
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported linked crystal type '{new_type}'. "
+                f'Supported: {supported_tags}. '
+                f"For more information, use 'show_supported_linked_crystal_types()'",
+            )
+            return
+
+        self._linked_crystal = LinkedCrystalFactory.create(new_type)
+        self._linked_crystal_type = new_type
+        console.paragraph(f"Linked crystal type for experiment '{self.name}' changed to")
+        console.print(new_type)
+
+    def show_supported_linked_crystal_types(self) -> None:
+        """Print a table of supported linked-crystal reference types."""
+        LinkedCrystalFactory.show_supported()
+
+    def show_current_linked_crystal_type(self) -> None:
+        """Print the currently used linked-crystal reference type."""
+        console.paragraph('Current linked crystal type')
+        console.print(self.linked_crystal_type)
+
+    # ------------------------------------------------------------------
+    #  Instrument (switchable-category pattern)
+    # ------------------------------------------------------------------
+
+    @property
+    def instrument(self):
+        """Active instrument model for this experiment."""
+        return self._instrument
+
+    @property
+    def instrument_type(self) -> str:
+        """Tag of the active instrument type."""
+        return self._instrument_type
+
+    @instrument_type.setter
+    def instrument_type(self, new_type: str) -> None:
+        """Switch to a different instrument type.
+
+        Args:
+            new_type: Instrument tag (e.g. ``'cwl-sc'``).
+        """
+        supported = InstrumentFactory.supported_for(
+            scattering_type=self.type.scattering_type.value,
+            beam_mode=self.type.beam_mode.value,
+            sample_form=self.type.sample_form.value,
+        )
+        supported_tags = [k.type_info.tag for k in supported]
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported instrument type '{new_type}'. "
+                f'Supported: {supported_tags}. '
+                f"For more information, use 'show_supported_instrument_types()'",
+            )
+            return
+        self._instrument = InstrumentFactory.create(new_type)
+        self._instrument_type = new_type
+        console.paragraph(f"Instrument type for experiment '{self.name}' changed to")
+        console.print(new_type)
+
+    def show_supported_instrument_types(self) -> None:
+        """Print a table of supported instrument types."""
+        InstrumentFactory.show_supported(
+            scattering_type=self.type.scattering_type.value,
+            beam_mode=self.type.beam_mode.value,
+            sample_form=self.type.sample_form.value,
+        )
+
+    def show_current_instrument_type(self) -> None:
+        """Print the currently used instrument type."""
+        console.paragraph('Current instrument type')
+        console.print(self.instrument_type)
+
+    # ------------------------------------------------------------------
+    #  Data (switchable-category pattern)
+    # ------------------------------------------------------------------
+
+    @property
+    def data(self):
+        """Data collection for this experiment."""
+        return self._data
+
+    @property
+    def data_type(self) -> str:
+        """Tag of the active data collection type."""
+        return self._data_type
+
+    @data_type.setter
+    def data_type(self, new_type: str) -> None:
+        """Switch to a different data collection type.
+
+        Args:
+            new_type: Data tag (e.g. ``'bragg-sc'``).
+        """
+        supported_tags = DataFactory.supported_tags()
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported data type '{new_type}'. "
+                f'Supported: {supported_tags}. '
+                f"For more information, use 'show_supported_data_types()'",
+            )
+            return
+        self._data = DataFactory.create(new_type)
+        self._data_type = new_type
+        console.paragraph(f"Data type for experiment '{self.name}' changed to")
+        console.print(new_type)
+
+    def show_supported_data_types(self) -> None:
+        """Print a table of supported data collection types."""
+        DataFactory.show_supported()
+
+    def show_current_data_type(self) -> None:
+        """Print the currently used data collection type."""
+        console.paragraph('Current data type')
+        console.print(self.data_type)
+
+
+class PdExperimentBase(ExperimentBase):
+    """Base class for all powder experiments."""
+
+    def __init__(
+        self,
+        *,
+        name: str,
+        type: ExperimentType,
+    ) -> None:
+        super().__init__(name=name, type=type)
+
+        self._linked_phases_type: str = LinkedPhasesFactory.default_tag()
+        self._linked_phases = LinkedPhasesFactory.create(self._linked_phases_type)
+        self._excluded_regions_type: str = ExcludedRegionsFactory.default_tag()
+        self._excluded_regions = ExcludedRegionsFactory.create(self._excluded_regions_type)
+        self._peak_profile_type: str = PeakFactory.default_tag(
+            scattering_type=self.type.scattering_type.value,
+            beam_mode=self.type.beam_mode.value,
+        )
+        self._data_type: str = DataFactory.default_tag(
+            sample_form=self.type.sample_form.value,
+            beam_mode=self.type.beam_mode.value,
+            scattering_type=self.type.scattering_type.value,
+        )
+        self._data = DataFactory.create(self._data_type)
+        self._peak = PeakFactory.create(self._peak_profile_type)
+
+    def _get_valid_linked_phases(
+        self,
+        structures: Structures,
+    ) -> List[Any]:
+        """Get valid linked phases for this experiment.
+
+        Args:
+            structures: Collection of structures.
+
+        Returns:
+            A list of valid linked phases.
+        """
+        if not self.linked_phases:
+            print('Warning: No linked phases defined. Returning empty pattern.')
+            return []
+
+        valid_linked_phases = []
+        for linked_phase in self.linked_phases:
+            if linked_phase._identity.category_entry_name not in structures.names:
+                print(
+                    f"Warning: Linked phase '{linked_phase.id.value}' not "
+                    f'found in Structures {structures.names}. Skipping it.'
+                )
+                continue
+            valid_linked_phases.append(linked_phase)
+
+        if not valid_linked_phases:
+            print(
+                'Warning: None of the linked phases found in Structures. Returning empty pattern.'
+            )
+
+        return valid_linked_phases
+
+    @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 linked_phases(self):
+        """Collection of phases linked to this experiment."""
+        return self._linked_phases
+
+    @property
+    def linked_phases_type(self) -> str:
+        """Tag of the active linked-phases collection type."""
+        return self._linked_phases_type
+
+    @linked_phases_type.setter
+    def linked_phases_type(self, new_type: str) -> None:
+        """Switch to a different linked-phases collection type.
+
+        Args:
+            new_type: Linked-phases tag (e.g. ``'default'``).
+        """
+        supported_tags = LinkedPhasesFactory.supported_tags()
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported linked phases type '{new_type}'. "
+                f'Supported: {supported_tags}. '
+                f"For more information, use 'show_supported_linked_phases_types()'",
+            )
+            return
+
+        self._linked_phases = LinkedPhasesFactory.create(new_type)
+        self._linked_phases_type = new_type
+        console.paragraph(f"Linked phases type for experiment '{self.name}' changed to")
+        console.print(new_type)
+
+    def show_supported_linked_phases_types(self) -> None:
+        """Print a table of supported linked-phases collection types."""
+        LinkedPhasesFactory.show_supported()
+
+    def show_current_linked_phases_type(self) -> None:
+        """Print the currently used linked-phases collection type."""
+        console.paragraph('Current linked phases type')
+        console.print(self.linked_phases_type)
+
+    @property
+    def excluded_regions(self):
+        """Collection of excluded regions for the x-grid."""
+        return self._excluded_regions
+
+    @property
+    def excluded_regions_type(self) -> str:
+        """Tag of the active excluded-regions collection type."""
+        return self._excluded_regions_type
+
+    @excluded_regions_type.setter
+    def excluded_regions_type(self, new_type: str) -> None:
+        """Switch to a different excluded-regions collection type.
+
+        Args:
+            new_type: Excluded-regions tag (e.g. ``'default'``).
+        """
+        supported_tags = ExcludedRegionsFactory.supported_tags()
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported excluded regions type '{new_type}'. "
+                f'Supported: {supported_tags}. '
+                f"For more information, use 'show_supported_excluded_regions_types()'",
+            )
+            return
+
+        self._excluded_regions = ExcludedRegionsFactory.create(new_type)
+        self._excluded_regions_type = new_type
+        console.paragraph(f"Excluded regions type for experiment '{self.name}' changed to")
+        console.print(new_type)
+
+    def show_supported_excluded_regions_types(self) -> None:
+        """Print a table of supported excluded-regions collection
+        types.
+        """
+        ExcludedRegionsFactory.show_supported()
+
+    def show_current_excluded_regions_type(self) -> None:
+        """Print the currently used excluded-regions collection type."""
+        console.paragraph('Current excluded regions type')
+        console.print(self.excluded_regions_type)
+
+    # ------------------------------------------------------------------
+    #  Data (switchable-category pattern)
+    # ------------------------------------------------------------------
+
+    @property
+    def data(self):
+        """Data collection for this experiment."""
+        return self._data
+
+    @property
+    def data_type(self) -> str:
+        """Tag of the active data collection type."""
+        return self._data_type
+
+    @data_type.setter
+    def data_type(self, new_type: str) -> None:
+        """Switch to a different data collection type.
+
+        Args:
+            new_type: Data tag (e.g. ``'bragg-pd-cwl'``).
+        """
+        supported_tags = DataFactory.supported_tags()
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported data type '{new_type}'. "
+                f'Supported: {supported_tags}. '
+                f"For more information, use 'show_supported_data_types()'",
+            )
+            return
+        self._data = DataFactory.create(new_type)
+        self._data_type = new_type
+        console.paragraph(f"Data type for experiment '{self.name}' changed to")
+        console.print(new_type)
+
+    def show_supported_data_types(self) -> None:
+        """Print a table of supported data collection types."""
+        DataFactory.show_supported()
+
+    def show_current_data_type(self) -> None:
+        """Print the currently used data collection type."""
+        console.paragraph('Current data type')
+        console.print(self.data_type)
+
+    @property
+    def peak(self):
+        """Peak category object with profile parameters and mixins."""
+        return self._peak
+
+    @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):
+        """Change the active peak profile type, if supported.
+
+        Args:
+            new_type: New profile type as tag string.
+        """
+        supported = PeakFactory.supported_for(
+            scattering_type=self.type.scattering_type.value,
+            beam_mode=self.type.beam_mode.value,
+        )
+        supported_tags = [k.type_info.tag for k in supported]
+
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported peak profile '{new_type}'. "
+                f'Supported peak profiles: {supported_tags}. '
+                f"For more information, use 'show_supported_peak_profile_types()'",
+            )
+            return
+
+        if self._peak is not None:
+            log.warning(
+                'Switching peak profile type discards existing peak parameters.',
+            )
+
+        self._peak = PeakFactory.create(new_type)
+        self._peak_profile_type = new_type
+        console.paragraph(f"Peak profile type for experiment '{self.name}' changed to")
+        console.print(new_type)
+
+    def show_supported_peak_profile_types(self):
+        """Print available peak profile types for this experiment."""
+        PeakFactory.show_supported(
+            scattering_type=self.type.scattering_type.value,
+            beam_mode=self.type.beam_mode.value,
+        )
+
+    def show_current_peak_profile_type(self):
+        """Print the currently selected peak profile type."""
+        console.paragraph('Current peak profile type')
+        console.print(self.peak_profile_type)
diff --git a/src/easydiffraction/datablocks/experiment/item/bragg_pd.py b/src/easydiffraction/datablocks/experiment/item/bragg_pd.py
new file mode 100644
index 00000000..258085c5
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/item/bragg_pd.py
@@ -0,0 +1,203 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import numpy as np
+
+from easydiffraction.core.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
+from easydiffraction.datablocks.experiment.categories.background.factory import BackgroundFactory
+from easydiffraction.datablocks.experiment.categories.instrument.factory import InstrumentFactory
+from easydiffraction.datablocks.experiment.item.base import PdExperimentBase
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
+from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory
+from easydiffraction.utils.logging import console
+from easydiffraction.utils.logging import log
+
+if TYPE_CHECKING:
+    from easydiffraction.datablocks.experiment.categories.experiment_type import ExperimentType
+
+
+@ExperimentFactory.register
+class BraggPdExperiment(PdExperimentBase):
+    """Standard (Bragg) Powder Diffraction experiment class with
+    specific attributes.
+    """
+
+    type_info = TypeInfo(
+        tag='bragg-pd',
+        description='Bragg powder diffraction experiment',
+    )
+    compatibility = Compatibility(
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG}),
+        sample_form=frozenset({SampleFormEnum.POWDER}),
+        beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH, BeamModeEnum.TIME_OF_FLIGHT}),
+    )
+
+    def __init__(
+        self,
+        *,
+        name: str,
+        type: ExperimentType,
+    ) -> None:
+        super().__init__(name=name, type=type)
+
+        self._instrument_type: str = InstrumentFactory.default_tag(
+            scattering_type=self.type.scattering_type.value,
+            beam_mode=self.type.beam_mode.value,
+            sample_form=self.type.sample_form.value,
+        )
+        self._instrument = InstrumentFactory.create(self._instrument_type)
+        self._background_type: str = BackgroundFactory.default_tag()
+        self._background = BackgroundFactory.create(self._background_type)
+
+    def _load_ascii_data_to_experiment(self, data_path: str) -> None:
+        """Load (x, y, sy) data from an ASCII file into the data
+        category.
+
+        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)``.
+
+        If ``sy`` has values smaller than ``0.0001``, they are replaced
+        with ``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
+        # TODO: Not used if loading from cif file?
+        sy = np.where(sy < 0.0001, 1.0, sy)
+
+        # Set the experiment data
+        self.data._create_items_set_xcoord_and_id(x)
+        self.data._set_intensity_meas(y)
+        self.data._set_intensity_meas_su(sy)
+
+        console.paragraph('Data loaded successfully')
+        console.print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}")
+
+    # ------------------------------------------------------------------
+    #  Instrument (switchable-category pattern)
+    # ------------------------------------------------------------------
+
+    @property
+    def instrument(self):
+        """Active instrument model for this experiment."""
+        return self._instrument
+
+    @property
+    def instrument_type(self) -> str:
+        """Tag of the active instrument type."""
+        return self._instrument_type
+
+    @instrument_type.setter
+    def instrument_type(self, new_type: str) -> None:
+        """Switch to a different instrument type.
+
+        Args:
+            new_type: Instrument tag (e.g. ``'cwl-pd'``).
+        """
+        supported = InstrumentFactory.supported_for(
+            scattering_type=self.type.scattering_type.value,
+            beam_mode=self.type.beam_mode.value,
+            sample_form=self.type.sample_form.value,
+        )
+        supported_tags = [k.type_info.tag for k in supported]
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported instrument type '{new_type}'. "
+                f'Supported: {supported_tags}. '
+                f"For more information, use 'show_supported_instrument_types()'",
+            )
+            return
+        self._instrument = InstrumentFactory.create(new_type)
+        self._instrument_type = new_type
+        console.paragraph(f"Instrument type for experiment '{self.name}' changed to")
+        console.print(new_type)
+
+    def show_supported_instrument_types(self) -> None:
+        """Print a table of supported instrument types."""
+        InstrumentFactory.show_supported(
+            scattering_type=self.type.scattering_type.value,
+            beam_mode=self.type.beam_mode.value,
+            sample_form=self.type.sample_form.value,
+        )
+
+    def show_current_instrument_type(self) -> None:
+        """Print the currently used instrument type."""
+        console.paragraph('Current instrument type')
+        console.print(self.instrument_type)
+
+    # ------------------------------------------------------------------
+    #  Background (switchable-category pattern)
+    # ------------------------------------------------------------------
+
+    @property
+    def background_type(self):
+        """Current background type enum value."""
+        return self._background_type
+
+    @background_type.setter
+    def background_type(self, new_type):
+        """Set a new background type and recreate background object."""
+        if self._background_type == new_type:
+            console.paragraph(f"Background type for experiment '{self.name}' already set to")
+            console.print(new_type)
+            return
+
+        supported_tags = BackgroundFactory.supported_tags()
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported background type '{new_type}'. "
+                f'Supported: {supported_tags}. '
+                f"For more information, use 'show_supported_background_types()'",
+            )
+            return
+
+        if len(self._background) > 0:
+            log.warning(
+                f'Switching background type discards {len(self._background)} '
+                f'existing background point(s).',
+            )
+
+        self._background = BackgroundFactory.create(new_type)
+        self._background_type = new_type
+        console.paragraph(f"Background type for experiment '{self.name}' changed to")
+        console.print(new_type)
+
+    @property
+    def background(self):
+        return self._background
+
+    def show_supported_background_types(self):
+        """Print a table of supported background types."""
+        BackgroundFactory.show_supported()
+
+    def show_current_background_type(self):
+        """Print the currently used background type."""
+        console.paragraph('Current background type')
+        console.print(self.background_type)
diff --git a/src/easydiffraction/experiments/experiment/bragg_sc.py b/src/easydiffraction/datablocks/experiment/item/bragg_sc.py
similarity index 75%
rename from src/easydiffraction/experiments/experiment/bragg_sc.py
rename to src/easydiffraction/datablocks/experiment/item/bragg_sc.py
index 60ade068..e8142e0e 100644
--- a/src/easydiffraction/experiments/experiment/bragg_sc.py
+++ b/src/easydiffraction/datablocks/experiment/item/bragg_sc.py
@@ -7,19 +7,36 @@
 
 import numpy as np
 
-from easydiffraction.experiments.experiment.base import ScExperimentBase
+from easydiffraction.core.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
+from easydiffraction.datablocks.experiment.item.base import ScExperimentBase
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
+from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory
 from easydiffraction.utils.logging import console
 from easydiffraction.utils.logging import log
 
 if TYPE_CHECKING:
-    from easydiffraction.experiments.categories.experiment_type import ExperimentType
+    from easydiffraction.datablocks.experiment.categories.experiment_type import ExperimentType
 
 
+@ExperimentFactory.register
 class CwlScExperiment(ScExperimentBase):
     """Standard (Bragg) constant wavelength single srystal experiment
     class with specific attributes.
     """
 
+    type_info = TypeInfo(
+        tag='bragg-sc-cwl',
+        description='Bragg CWL single-crystal experiment',
+    )
+    compatibility = Compatibility(
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG}),
+        sample_form=frozenset({SampleFormEnum.SINGLE_CRYSTAL}),
+        beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH}),
+    )
+
     def __init__(
         self,
         *,
@@ -68,11 +85,22 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None:
         console.print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(indices_h)}")
 
 
+@ExperimentFactory.register
 class TofScExperiment(ScExperimentBase):
     """Standard (Bragg) time-of-flight single srystal experiment class
     with specific attributes.
     """
 
+    type_info = TypeInfo(
+        tag='bragg-sc-tof',
+        description='Bragg TOF single-crystal experiment',
+    )
+    compatibility = Compatibility(
+        scattering_type=frozenset({ScatteringTypeEnum.BRAGG}),
+        sample_form=frozenset({SampleFormEnum.SINGLE_CRYSTAL}),
+        beam_mode=frozenset({BeamModeEnum.TIME_OF_FLIGHT}),
+    )
+
     def __init__(
         self,
         *,
diff --git a/src/easydiffraction/experiments/experiment/enums.py b/src/easydiffraction/datablocks/experiment/item/enums.py
similarity index 88%
rename from src/easydiffraction/experiments/experiment/enums.py
rename to src/easydiffraction/datablocks/experiment/item/enums.py
index b15220fe..8d0f153e 100644
--- a/src/easydiffraction/experiments/experiment/enums.py
+++ b/src/easydiffraction/datablocks/experiment/item/enums.py
@@ -74,6 +74,20 @@ def description(self) -> str:
             return 'Time-of-flight (TOF) diffraction.'
 
 
+class CalculatorEnum(str, Enum):
+    """Known calculation engine identifiers."""
+
+    CRYSPY = 'cryspy'
+    CRYSFML = 'crysfml'
+    PDFFIT = 'pdffit'
+
+
+# TODO: Can, instead of hardcoding here, this info be auto-extracted
+#  from the actual peak profile classes defined in peak/cwl.py, tof.py,
+#  total.py? So that their Enum variable, string representation and
+#  description are defined in the respective classes?
+# TODO: Can supported values be defined based on the structure of peak/?
+# TODO: Can the same be reused for other enums in this file?
 class PeakProfileTypeEnum(str, Enum):
     """Available peak profile types per scattering and beam mode."""
 
diff --git a/src/easydiffraction/datablocks/experiment/item/factory.py b/src/easydiffraction/datablocks/experiment/item/factory.py
new file mode 100644
index 00000000..3e5aebb5
--- /dev/null
+++ b/src/easydiffraction/datablocks/experiment/item/factory.py
@@ -0,0 +1,236 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Factory for creating experiment instances from various inputs.
+
+Provides individual class methods for each creation pathway:
+``from_cif_path``, ``from_cif_str``, ``from_data_path``, and
+``from_scratch``.
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from typeguard import typechecked
+
+from easydiffraction.core.factory import FactoryBase
+from easydiffraction.datablocks.experiment.categories.experiment_type import ExperimentType
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
+from easydiffraction.io.cif.parse import document_from_path
+from easydiffraction.io.cif.parse import document_from_string
+from easydiffraction.io.cif.parse import name_from_block
+from easydiffraction.io.cif.parse import pick_sole_block
+from easydiffraction.utils.logging import log
+
+if TYPE_CHECKING:
+    import gemmi
+
+    from easydiffraction.datablocks.experiment.item.base import ExperimentBase
+
+
+class ExperimentFactory(FactoryBase):
+    """Creates Experiment instances with only relevant attributes."""
+
+    _default_rules = {
+        frozenset({
+            ('scattering_type', ScatteringTypeEnum.BRAGG),
+            ('sample_form', SampleFormEnum.POWDER),
+        }): 'bragg-pd',
+        frozenset({
+            ('scattering_type', ScatteringTypeEnum.TOTAL),
+            ('sample_form', SampleFormEnum.POWDER),
+        }): 'total-pd',
+        frozenset({
+            ('scattering_type', ScatteringTypeEnum.BRAGG),
+            ('sample_form', SampleFormEnum.SINGLE_CRYSTAL),
+            ('beam_mode', BeamModeEnum.CONSTANT_WAVELENGTH),
+        }): 'bragg-sc-cwl',
+        frozenset({
+            ('scattering_type', ScatteringTypeEnum.BRAGG),
+            ('sample_form', SampleFormEnum.SINGLE_CRYSTAL),
+            ('beam_mode', BeamModeEnum.TIME_OF_FLIGHT),
+        }): 'bragg-sc-tof',
+    }
+
+    # TODO: Add to core/factory.py?
+    def __init__(self):
+        log.error(
+            'Experiment objects must be created using class methods such as '
+            '`ExperimentFactory.from_cif_str(...)`, etc.'
+        )
+
+    # ------------------------------------------------------------------
+    # Private helper methods
+    # ------------------------------------------------------------------
+
+    @classmethod
+    @typechecked
+    def _create_experiment_type(
+        cls,
+        sample_form: str | None = None,
+        beam_mode: str | None = None,
+        radiation_probe: str | None = None,
+        scattering_type: str | None = None,
+    ) -> ExperimentType:
+        """Construct an ExperimentType, using defaults for omitted
+        values.
+        """
+        # Note: validation of input values is done via Descriptor setter
+        # methods
+
+        et = ExperimentType()
+
+        if sample_form is not None:
+            et._set_sample_form(sample_form)
+        if beam_mode is not None:
+            et._set_beam_mode(beam_mode)
+        if radiation_probe is not None:
+            et._set_radiation_probe(radiation_probe)
+        if scattering_type is not None:
+            et._set_scattering_type(scattering_type)
+
+        return et
+
+    @classmethod
+    @typechecked
+    def _resolve_class(cls, expt_type: ExperimentType):
+        """Look up the experiment class from the type enums."""
+        tag = cls.default_tag(
+            scattering_type=expt_type.scattering_type.value,
+            sample_form=expt_type.sample_form.value,
+            beam_mode=expt_type.beam_mode.value,
+        )
+        return cls._supported_map()[tag]
+
+    @classmethod
+    # TODO: @typechecked fails to find gemmi?
+    def _from_gemmi_block(
+        cls,
+        block: gemmi.cif.Block,
+    ) -> ExperimentBase:
+        """Build a model instance from a single CIF block."""
+        name = name_from_block(block)
+
+        expt_type = ExperimentType()
+        for param in expt_type.parameters:
+            param.from_cif(block)
+
+        expt_class = cls._resolve_class(expt_type)
+        expt_obj = expt_class(name=name, type=expt_type)
+
+        for category in expt_obj.categories:
+            category.from_cif(block)
+
+        return expt_obj
+
+    # ------------------------------------------------------------------
+    # Public methods
+    # ------------------------------------------------------------------
+
+    @classmethod
+    @typechecked
+    def from_scratch(
+        cls,
+        *,
+        name: str,
+        sample_form: str | None = None,
+        beam_mode: str | None = None,
+        radiation_probe: str | None = None,
+        scattering_type: str | None = None,
+    ) -> ExperimentBase:
+        """Create an experiment without measured data.
+
+        Args:
+            name: Experiment identifier.
+            sample_form: Sample form (e.g. ``'powder'``).
+            beam_mode: Beam mode (e.g. ``'constant wavelength'``).
+            radiation_probe: Radiation probe (e.g. ``'neutron'``).
+            scattering_type: Scattering type (e.g. ``'bragg'``).
+
+        Returns:
+            An experiment instance with only metadata.
+        """
+        expt_type = cls._create_experiment_type(
+            sample_form=sample_form,
+            beam_mode=beam_mode,
+            radiation_probe=radiation_probe,
+            scattering_type=scattering_type,
+        )
+        expt_class = cls._resolve_class(expt_type)
+        expt_obj = expt_class(name=name, type=expt_type)
+        return expt_obj
+
+    # TODO: add minimal default configuration for missing parameters
+    @classmethod
+    @typechecked
+    def from_cif_str(
+        cls,
+        cif_str: str,
+    ) -> ExperimentBase:
+        """Create an experiment from a CIF string.
+
+        Args:
+            cif_str: Full CIF document as a string.
+
+        Returns:
+            A populated experiment instance.
+        """
+        doc = document_from_string(cif_str)
+        block = pick_sole_block(doc)
+        return cls._from_gemmi_block(block)
+
+    # TODO: Read content and call self.from_cif_str
+    @classmethod
+    @typechecked
+    def from_cif_path(
+        cls,
+        cif_path: str,
+    ) -> ExperimentBase:
+        """Create an experiment from a CIF file path.
+
+        Args:
+            cif_path: Path to a CIF file.
+
+        Returns:
+            A populated experiment instance.
+        """
+        doc = document_from_path(cif_path)
+        block = pick_sole_block(doc)
+        return cls._from_gemmi_block(block)
+
+    @classmethod
+    @typechecked
+    def from_data_path(
+        cls,
+        *,
+        name: str,
+        data_path: str,
+        sample_form: str | None = None,
+        beam_mode: str | None = None,
+        radiation_probe: str | None = None,
+        scattering_type: str | None = None,
+    ) -> ExperimentBase:
+        """Create an experiment from a raw data ASCII file.
+
+        Args:
+            name: Experiment identifier.
+            data_path: Path to the measured data file.
+            sample_form: Sample form (e.g. ``'powder'``).
+            beam_mode: Beam mode (e.g. ``'constant wavelength'``).
+            radiation_probe: Radiation probe (e.g. ``'neutron'``).
+            scattering_type: Scattering type (e.g. ``'bragg'``).
+
+        Returns:
+            An experiment instance with measured data attached.
+        """
+        expt_obj = cls.from_scratch(
+            name=name,
+            sample_form=sample_form,
+            beam_mode=beam_mode,
+            radiation_probe=radiation_probe,
+            scattering_type=scattering_type,
+        )
+        expt_obj._load_ascii_data_to_experiment(data_path)
+        return expt_obj
diff --git a/src/easydiffraction/experiments/experiment/total_pd.py b/src/easydiffraction/datablocks/experiment/item/total_pd.py
similarity index 64%
rename from src/easydiffraction/experiments/experiment/total_pd.py
rename to src/easydiffraction/datablocks/experiment/item/total_pd.py
index 6262bd62..881dd0ce 100644
--- a/src/easydiffraction/experiments/experiment/total_pd.py
+++ b/src/easydiffraction/datablocks/experiment/item/total_pd.py
@@ -7,16 +7,33 @@
 
 import numpy as np
 
-from easydiffraction.experiments.experiment.base import PdExperimentBase
+from easydiffraction.core.metadata import Compatibility
+from easydiffraction.core.metadata import TypeInfo
+from easydiffraction.datablocks.experiment.item.base import PdExperimentBase
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
+from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory
 from easydiffraction.utils.logging import console
 
 if TYPE_CHECKING:
-    from easydiffraction.experiments.categories.experiment_type import ExperimentType
+    from easydiffraction.datablocks.experiment.categories.experiment_type import ExperimentType
 
 
+@ExperimentFactory.register
 class TotalPdExperiment(PdExperimentBase):
     """PDF experiment class with specific attributes."""
 
+    type_info = TypeInfo(
+        tag='total-pd',
+        description='Total scattering (PDF) powder experiment',
+    )
+    compatibility = Compatibility(
+        scattering_type=frozenset({ScatteringTypeEnum.TOTAL}),
+        sample_form=frozenset({SampleFormEnum.POWDER}),
+        beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH, BeamModeEnum.TIME_OF_FLIGHT}),
+    )
+
     def __init__(
         self,
         name: str,
diff --git a/src/easydiffraction/experiments/categories/instrument/__init__.py b/src/easydiffraction/datablocks/structure/__init__.py
similarity index 100%
rename from src/easydiffraction/experiments/categories/instrument/__init__.py
rename to src/easydiffraction/datablocks/structure/__init__.py
diff --git a/src/easydiffraction/experiments/categories/peak/__init__.py b/src/easydiffraction/datablocks/structure/categories/__init__.py
similarity index 100%
rename from src/easydiffraction/experiments/categories/peak/__init__.py
rename to src/easydiffraction/datablocks/structure/categories/__init__.py
diff --git a/src/easydiffraction/datablocks/structure/categories/atom_sites/__init__.py b/src/easydiffraction/datablocks/structure/categories/atom_sites/__init__.py
new file mode 100644
index 00000000..cb4c1750
--- /dev/null
+++ b/src/easydiffraction/datablocks/structure/categories/atom_sites/__init__.py
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.datablocks.structure.categories.atom_sites.default import AtomSite
+from easydiffraction.datablocks.structure.categories.atom_sites.default import AtomSites
diff --git a/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py b/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py
new file mode 100644
index 00000000..76d890d5
--- /dev/null
+++ b/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py
@@ -0,0 +1,398 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Atom site category.
+
+Defines :class:`AtomSite` items and :class:`AtomSites` collection used
+in crystallographic structures.
+"""
+
+from __future__ import annotations
+
+from cryspy.A_functions_base.database import DATABASE
+
+from easydiffraction.core.category import CategoryCollection
+from easydiffraction.core.category import CategoryItem
+from easydiffraction.core.metadata import TypeInfo
+from easydiffraction.core.validation import AttributeSpec
+from easydiffraction.core.validation import MembershipValidator
+from easydiffraction.core.validation import RangeValidator
+from easydiffraction.core.validation import RegexValidator
+from easydiffraction.core.variable import Parameter
+from easydiffraction.core.variable import StringDescriptor
+from easydiffraction.crystallography import crystallography as ecr
+from easydiffraction.datablocks.structure.categories.atom_sites.factory import AtomSitesFactory
+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) -> None:
+        """Initialise the atom site with default descriptor values."""
+        super().__init__()
+
+        self._label = StringDescriptor(
+            name='label',
+            description='Unique identifier for the atom site.',
+            value_spec=AttributeSpec(
+                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?
+                validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'),
+            ),
+            cif_handler=CifHandler(names=['_atom_site.label']),
+        )
+        self._type_symbol = StringDescriptor(
+            name='type_symbol',
+            description='Chemical symbol of the atom at this site.',
+            value_spec=AttributeSpec(
+                default='Tb',
+                validator=MembershipValidator(allowed=self._type_symbol_allowed_values),
+            ),
+            cif_handler=CifHandler(names=['_atom_site.type_symbol']),
+        )
+        self._fract_x = Parameter(
+            name='fract_x',
+            description='Fractional x-coordinate of the atom site within the unit cell.',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_atom_site.fract_x']),
+        )
+        self._fract_y = Parameter(
+            name='fract_y',
+            description='Fractional y-coordinate of the atom site within the unit cell.',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_atom_site.fract_y']),
+        )
+        self._fract_z = Parameter(
+            name='fract_z',
+            description='Fractional z-coordinate of the atom site within the unit cell.',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_atom_site.fract_z']),
+        )
+        self._wyckoff_letter = StringDescriptor(
+            name='wyckoff_letter',
+            description='Wyckoff letter indicating the symmetry of the '
+            'atom site within the space group.',
+            value_spec=AttributeSpec(
+                default=self._wyckoff_letter_default_value,
+                validator=MembershipValidator(allowed=self._wyckoff_letter_allowed_values),
+            ),
+            cif_handler=CifHandler(
+                names=[
+                    '_atom_site.Wyckoff_letter',
+                    '_atom_site.Wyckoff_symbol',
+                ]
+            ),
+        )
+        self._occupancy = Parameter(
+            name='occupancy',
+            description='Occupancy of the atom site, representing the '
+            'fraction of the site occupied by the atom type.',
+            value_spec=AttributeSpec(
+                default=1.0,
+                validator=RangeValidator(),
+            ),
+            cif_handler=CifHandler(names=['_atom_site.occupancy']),
+        )
+        self._b_iso = Parameter(
+            name='b_iso',
+            description='Isotropic atomic displacement parameter (ADP) for the atom site.',
+            units='Ų',
+            value_spec=AttributeSpec(
+                default=0.0,
+                validator=RangeValidator(ge=0.0),
+            ),
+            cif_handler=CifHandler(names=['_atom_site.B_iso_or_equiv']),
+        )
+        self._adp_type = StringDescriptor(
+            name='adp_type',
+            description='Type of atomic displacement parameter (ADP) '
+            'used (e.g., Biso, Uiso, Uani, Bani).',
+            value_spec=AttributeSpec(
+                default='Biso',
+                validator=MembershipValidator(allowed=['Biso']),
+            ),
+            cif_handler=CifHandler(names=['_atom_site.adp_type']),
+        )
+
+        self._identity.category_code = 'atom_site'
+        self._identity.category_entry_name = lambda: str(self.label.value)
+
+    # ------------------------------------------------------------------
+    #  Private helper methods
+    # ------------------------------------------------------------------
+
+    @property
+    def _type_symbol_allowed_values(self) -> list[str]:
+        """Return chemical symbols accepted by *cryspy*.
+
+        Returns:
+            list[str]: Unique element/isotope symbols from the database.
+        """
+        return list({key[1] for key in DATABASE['Isotopes']})
+
+    @property
+    def _wyckoff_letter_allowed_values(self) -> list[str]:
+        """Return allowed Wyckoff-letter symbols.
+
+        Returns:
+            list[str]: Currently a hard-coded placeholder list.
+        """
+        # TODO: Need to now current space group. How to access it? Via
+        #  parent Cell? Then letters =
+        #  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) -> str:
+        """Return the default Wyckoff letter.
+
+        Returns:
+            str: First element of the allowed values list.
+        """
+        # TODO: What to pass as default?
+        return self._wyckoff_letter_allowed_values[0]
+
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
+    @property
+    def label(self) -> StringDescriptor:
+        """Unique label for this atom site.
+
+        Returns:
+            StringDescriptor: Descriptor holding the site label.
+        """
+        return self._label
+
+    @label.setter
+    def label(self, value: str) -> None:
+        """Set the atom-site label.
+
+        Args:
+            value (str): New label string.
+        """
+        self._label.value = value
+
+    @property
+    def type_symbol(self) -> StringDescriptor:
+        """Chemical element or isotope symbol.
+
+        Returns:
+            StringDescriptor: Descriptor holding the type symbol.
+        """
+        return self._type_symbol
+
+    @type_symbol.setter
+    def type_symbol(self, value: str) -> None:
+        """Set the chemical element or isotope symbol.
+
+        Args:
+            value (str): New type symbol (must be in the *cryspy*
+                database).
+        """
+        self._type_symbol.value = value
+
+    @property
+    def adp_type(self) -> StringDescriptor:
+        """Type of atomic displacement parameter (e.g. ``'Biso'``).
+
+        Returns:
+            StringDescriptor: Descriptor holding the ADP type.
+        """
+        return self._adp_type
+
+    @adp_type.setter
+    def adp_type(self, value: str) -> None:
+        """Set the ADP type.
+
+        Args:
+            value (str): New ADP type string.
+        """
+        self._adp_type.value = value
+
+    @property
+    def wyckoff_letter(self) -> StringDescriptor:
+        """Wyckoff letter for the symmetry site.
+
+        Returns:
+            StringDescriptor: Descriptor holding the Wyckoff letter.
+        """
+        return self._wyckoff_letter
+
+    @wyckoff_letter.setter
+    def wyckoff_letter(self, value: str) -> None:
+        """Set the Wyckoff letter.
+
+        Args:
+            value (str): New Wyckoff letter.
+        """
+        self._wyckoff_letter.value = value
+
+    @property
+    def fract_x(self) -> Parameter:
+        """Fractional *x*-coordinate within the unit cell.
+
+        Returns:
+            Parameter: Descriptor for the *x* coordinate.
+        """
+        return self._fract_x
+
+    @fract_x.setter
+    def fract_x(self, value: float) -> None:
+        """Set the fractional *x*-coordinate.
+
+        Args:
+            value (float): New *x* coordinate.
+        """
+        self._fract_x.value = value
+
+    @property
+    def fract_y(self) -> Parameter:
+        """Fractional *y*-coordinate within the unit cell.
+
+        Returns:
+            Parameter: Descriptor for the *y* coordinate.
+        """
+        return self._fract_y
+
+    @fract_y.setter
+    def fract_y(self, value: float) -> None:
+        """Set the fractional *y*-coordinate.
+
+        Args:
+            value (float): New *y* coordinate.
+        """
+        self._fract_y.value = value
+
+    @property
+    def fract_z(self) -> Parameter:
+        """Fractional *z*-coordinate within the unit cell.
+
+        Returns:
+            Parameter: Descriptor for the *z* coordinate.
+        """
+        return self._fract_z
+
+    @fract_z.setter
+    def fract_z(self, value: float) -> None:
+        """Set the fractional *z*-coordinate.
+
+        Args:
+            value (float): New *z* coordinate.
+        """
+        self._fract_z.value = value
+
+    @property
+    def occupancy(self) -> Parameter:
+        """Site occupancy fraction.
+
+        Returns:
+            Parameter: Descriptor for the occupancy (0–1).
+        """
+        return self._occupancy
+
+    @occupancy.setter
+    def occupancy(self, value: float) -> None:
+        """Set the site occupancy.
+
+        Args:
+            value (float): New occupancy fraction.
+        """
+        self._occupancy.value = value
+
+    @property
+    def b_iso(self) -> Parameter:
+        r"""Isotropic atomic displacement parameter (*B*-factor).
+
+        Returns:
+            Parameter: Descriptor for *B*\_iso (Ų).
+        """
+        return self._b_iso
+
+    @b_iso.setter
+    def b_iso(self, value: float) -> None:
+        r"""Set the isotropic displacement parameter.
+
+        Args:
+            value (float): New *B*\_iso value in Ų.
+        """
+        self._b_iso.value = value
+
+
+@AtomSitesFactory.register
+class AtomSites(CategoryCollection):
+    """Collection of :class:`AtomSite` instances."""
+
+    type_info = TypeInfo(
+        tag='default',
+        description='Atom sites collection',
+    )
+
+    def __init__(self) -> None:
+        """Initialise an empty atom-sites collection."""
+        super().__init__(item_type=AtomSite)
+
+    # ------------------------------------------------------------------
+    #  Private helper methods
+    # ------------------------------------------------------------------
+
+    def _apply_atomic_coordinates_symmetry_constraints(self) -> None:
+        """Apply symmetry rules to fractional coordinates of every site.
+
+        Uses the parent structure's space-group symbol, IT coordinate
+        system code and each atom's Wyckoff letter.  Atoms without a
+        Wyckoff letter are silently skipped.
+        """
+        structure = self._parent
+        space_group_name = structure.space_group.name_h_m.value
+        space_group_coord_code = structure.space_group.it_coordinate_system_code.value
+        for atom in self._items:
+            dummy_atom = {
+                'fract_x': atom.fract_x.value,
+                'fract_y': atom.fract_y.value,
+                'fract_z': atom.fract_z.value,
+            }
+            wl = atom.wyckoff_letter.value
+            if not wl:
+                # TODO: Decide how to handle this case
+                continue
+            ecr.apply_atom_site_symmetry_constraints(
+                atom_site=dummy_atom,
+                name_hm=space_group_name,
+                coord_code=space_group_coord_code,
+                wyckoff_letter=wl,
+            )
+            atom.fract_x.value = dummy_atom['fract_x']
+            atom.fract_y.value = dummy_atom['fract_y']
+            atom.fract_z.value = dummy_atom['fract_z']
+
+    def _update(
+        self,
+        called_by_minimizer: bool = False,
+    ) -> None:
+        """Recalculate atom sites after a change.
+
+        Args:
+            called_by_minimizer (bool): Whether the update was triggered
+                by the fitting minimizer. Currently unused.
+        """
+        del called_by_minimizer
+
+        self._apply_atomic_coordinates_symmetry_constraints()
diff --git a/src/easydiffraction/datablocks/structure/categories/atom_sites/factory.py b/src/easydiffraction/datablocks/structure/categories/atom_sites/factory.py
new file mode 100644
index 00000000..e233d0bd
--- /dev/null
+++ b/src/easydiffraction/datablocks/structure/categories/atom_sites/factory.py
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Atom-sites factory — delegates entirely to ``FactoryBase``."""
+
+from __future__ import annotations
+
+from easydiffraction.core.factory import FactoryBase
+
+
+class AtomSitesFactory(FactoryBase):
+    """Create atom-sites collections by tag."""
+
+    _default_rules = {
+        frozenset(): 'default',
+    }
diff --git a/src/easydiffraction/sample_models/categories/__init__.py b/src/easydiffraction/datablocks/structure/categories/cell/__init__.py
similarity index 65%
rename from src/easydiffraction/sample_models/categories/__init__.py
rename to src/easydiffraction/datablocks/structure/categories/cell/__init__.py
index 429f2648..08773b3e 100644
--- a/src/easydiffraction/sample_models/categories/__init__.py
+++ b/src/easydiffraction/datablocks/structure/categories/cell/__init__.py
@@ -1,2 +1,4 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.datablocks.structure.categories.cell.default import Cell
diff --git a/src/easydiffraction/datablocks/structure/categories/cell/default.py b/src/easydiffraction/datablocks/structure/categories/cell/default.py
new file mode 100644
index 00000000..e07e20e0
--- /dev/null
+++ b/src/easydiffraction/datablocks/structure/categories/cell/default.py
@@ -0,0 +1,255 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Unit cell parameters category for structures."""
+
+from __future__ import annotations
+
+from easydiffraction.core.category import CategoryItem
+from easydiffraction.core.metadata import TypeInfo
+from easydiffraction.core.validation import AttributeSpec
+from easydiffraction.core.validation import RangeValidator
+from easydiffraction.core.variable import Parameter
+from easydiffraction.crystallography import crystallography as ecr
+from easydiffraction.datablocks.structure.categories.cell.factory import CellFactory
+from easydiffraction.io.cif.handler import CifHandler
+
+
+@CellFactory.register
+class Cell(CategoryItem):
+    """Unit cell with lengths *a*, *b*, *c* and angles *alpha*, *beta*,
+    *gamma*.
+
+    All six lattice parameters are exposed as :class:`Parameter`
+    descriptors supporting validation, fitting and CIF serialization.
+    """
+
+    type_info = TypeInfo(
+        tag='default',
+        description='Unit cell parameters',
+    )
+
+    def __init__(self) -> None:
+        """Initialise the unit cell with default parameter values."""
+        super().__init__()
+
+        self._length_a = Parameter(
+            name='length_a',
+            description='Length of the a axis of the unit cell.',
+            units='Å',
+            value_spec=AttributeSpec(
+                default=10.0,
+                validator=RangeValidator(ge=0, le=1000),
+            ),
+            cif_handler=CifHandler(names=['_cell.length_a']),
+        )
+        self._length_b = Parameter(
+            name='length_b',
+            description='Length of the b axis of the unit cell.',
+            units='Å',
+            value_spec=AttributeSpec(
+                default=10.0,
+                validator=RangeValidator(ge=0, le=1000),
+            ),
+            cif_handler=CifHandler(names=['_cell.length_b']),
+        )
+        self._length_c = Parameter(
+            name='length_c',
+            description='Length of the c axis of the unit cell.',
+            units='Å',
+            value_spec=AttributeSpec(
+                default=10.0,
+                validator=RangeValidator(ge=0, le=1000),
+            ),
+            cif_handler=CifHandler(names=['_cell.length_c']),
+        )
+        self._angle_alpha = Parameter(
+            name='angle_alpha',
+            description='Angle between edges b and c.',
+            units='deg',
+            value_spec=AttributeSpec(
+                default=90.0,
+                validator=RangeValidator(ge=0, le=180),
+            ),
+            cif_handler=CifHandler(names=['_cell.angle_alpha']),
+        )
+        self._angle_beta = Parameter(
+            name='angle_beta',
+            description='Angle between edges a and c.',
+            units='deg',
+            value_spec=AttributeSpec(
+                default=90.0,
+                validator=RangeValidator(ge=0, le=180),
+            ),
+            cif_handler=CifHandler(names=['_cell.angle_beta']),
+        )
+        self._angle_gamma = Parameter(
+            name='angle_gamma',
+            description='Angle between edges a and b.',
+            units='deg',
+            value_spec=AttributeSpec(
+                default=90.0,
+                validator=RangeValidator(ge=0, le=180),
+            ),
+            cif_handler=CifHandler(names=['_cell.angle_gamma']),
+        )
+
+        self._identity.category_code = 'cell'
+
+    # ------------------------------------------------------------------
+    #  Private helper methods
+    # ------------------------------------------------------------------
+
+    def _apply_cell_symmetry_constraints(self) -> None:
+        """Apply symmetry constraints to cell parameters in place.
+
+        Uses the parent structure's space-group symbol to determine
+        which lattice parameters are dependent and sets them
+        accordingly.
+        """
+        dummy_cell = {
+            'lattice_a': self.length_a.value,
+            'lattice_b': self.length_b.value,
+            'lattice_c': self.length_c.value,
+            'angle_alpha': self.angle_alpha.value,
+            'angle_beta': self.angle_beta.value,
+            'angle_gamma': self.angle_gamma.value,
+        }
+        space_group_name = self._parent.space_group.name_h_m.value
+
+        ecr.apply_cell_symmetry_constraints(
+            cell=dummy_cell,
+            name_hm=space_group_name,
+        )
+
+        self.length_a.value = dummy_cell['lattice_a']
+        self.length_b.value = dummy_cell['lattice_b']
+        self.length_c.value = dummy_cell['lattice_c']
+        self.angle_alpha.value = dummy_cell['angle_alpha']
+        self.angle_beta.value = dummy_cell['angle_beta']
+        self.angle_gamma.value = dummy_cell['angle_gamma']
+
+    def _update(
+        self,
+        called_by_minimizer: bool = False,
+    ) -> None:
+        """Recalculate cell parameters after a change.
+
+        Args:
+            called_by_minimizer (bool): Whether the update was triggered
+                by the fitting minimizer. Currently unused.
+        """
+        del called_by_minimizer  # TODO: ???
+
+        self._apply_cell_symmetry_constraints()
+
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
+    @property
+    def length_a(self) -> Parameter:
+        """Length of the *a* axis.
+
+        Returns:
+            Parameter: Descriptor for lattice parameter *a* (Å).
+        """
+        return self._length_a
+
+    @length_a.setter
+    def length_a(self, value: float) -> None:
+        """Set the length of the *a* axis.
+
+        Args:
+            value (float): New length in ångströms.
+        """
+        self._length_a.value = value
+
+    @property
+    def length_b(self) -> Parameter:
+        """Length of the *b* axis.
+
+        Returns:
+            Parameter: Descriptor for lattice parameter *b* (Å).
+        """
+        return self._length_b
+
+    @length_b.setter
+    def length_b(self, value: float) -> None:
+        """Set the length of the *b* axis.
+
+        Args:
+            value (float): New length in ångströms.
+        """
+        self._length_b.value = value
+
+    @property
+    def length_c(self) -> Parameter:
+        """Length of the *c* axis.
+
+        Returns:
+            Parameter: Descriptor for lattice parameter *c* (Å).
+        """
+        return self._length_c
+
+    @length_c.setter
+    def length_c(self, value: float) -> None:
+        """Set the length of the *c* axis.
+
+        Args:
+            value (float): New length in ångströms.
+        """
+        self._length_c.value = value
+
+    @property
+    def angle_alpha(self) -> Parameter:
+        """Angle between edges *b* and *c*.
+
+        Returns:
+            Parameter: Descriptor for angle *α* (degrees).
+        """
+        return self._angle_alpha
+
+    @angle_alpha.setter
+    def angle_alpha(self, value: float) -> None:
+        """Set the angle between edges *b* and *c*.
+
+        Args:
+            value (float): New angle in degrees.
+        """
+        self._angle_alpha.value = value
+
+    @property
+    def angle_beta(self) -> Parameter:
+        """Angle between edges *a* and *c*.
+
+        Returns:
+            Parameter: Descriptor for angle *β* (degrees).
+        """
+        return self._angle_beta
+
+    @angle_beta.setter
+    def angle_beta(self, value: float) -> None:
+        """Set the angle between edges *a* and *c*.
+
+        Args:
+            value (float): New angle in degrees.
+        """
+        self._angle_beta.value = value
+
+    @property
+    def angle_gamma(self) -> Parameter:
+        """Angle between edges *a* and *b*.
+
+        Returns:
+            Parameter: Descriptor for angle *γ* (degrees).
+        """
+        return self._angle_gamma
+
+    @angle_gamma.setter
+    def angle_gamma(self, value: float) -> None:
+        """Set the angle between edges *a* and *b*.
+
+        Args:
+            value (float): New angle in degrees.
+        """
+        self._angle_gamma.value = value
diff --git a/src/easydiffraction/datablocks/structure/categories/cell/factory.py b/src/easydiffraction/datablocks/structure/categories/cell/factory.py
new file mode 100644
index 00000000..c5fde941
--- /dev/null
+++ b/src/easydiffraction/datablocks/structure/categories/cell/factory.py
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Cell factory — delegates entirely to ``FactoryBase``."""
+
+from __future__ import annotations
+
+from easydiffraction.core.factory import FactoryBase
+
+
+class CellFactory(FactoryBase):
+    """Create unit-cell categories by tag."""
+
+    _default_rules = {
+        frozenset(): 'default',
+    }
diff --git a/src/easydiffraction/sample_models/sample_model/__init__.py b/src/easydiffraction/datablocks/structure/categories/space_group/__init__.py
similarity index 61%
rename from src/easydiffraction/sample_models/sample_model/__init__.py
rename to src/easydiffraction/datablocks/structure/categories/space_group/__init__.py
index 429f2648..daf02947 100644
--- a/src/easydiffraction/sample_models/sample_model/__init__.py
+++ b/src/easydiffraction/datablocks/structure/categories/space_group/__init__.py
@@ -1,2 +1,4 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.datablocks.structure.categories.space_group.default import SpaceGroup
diff --git a/src/easydiffraction/datablocks/structure/categories/space_group/default.py b/src/easydiffraction/datablocks/structure/categories/space_group/default.py
new file mode 100644
index 00000000..7076d272
--- /dev/null
+++ b/src/easydiffraction/datablocks/structure/categories/space_group/default.py
@@ -0,0 +1,167 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Space group category for crystallographic structures."""
+
+from __future__ import annotations
+
+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.metadata import TypeInfo
+from easydiffraction.core.validation import AttributeSpec
+from easydiffraction.core.validation import MembershipValidator
+from easydiffraction.core.variable import StringDescriptor
+from easydiffraction.datablocks.structure.categories.space_group.factory import SpaceGroupFactory
+from easydiffraction.io.cif.handler import CifHandler
+
+
+@SpaceGroupFactory.register
+class SpaceGroup(CategoryItem):
+    """Space group with Hermann–Mauguin symbol and IT coordinate system
+    code.
+
+    Holds the space-group symbol (``name_h_m``) and the International
+    Tables coordinate-system qualifier (``it_coordinate_system_code``).
+    Changing the symbol automatically resets the coordinate-system code
+    to the first allowed value for the new group.
+    """
+
+    type_info = TypeInfo(
+        tag='default',
+        description='Space group symmetry',
+    )
+
+    def __init__(self) -> None:
+        """Initialise the space group with default values."""
+        super().__init__()
+
+        self._name_h_m = StringDescriptor(
+            name='name_h_m',
+            description='Hermann-Mauguin symbol of the space group.',
+            value_spec=AttributeSpec(
+                default='P 1',
+                validator=MembershipValidator(
+                    allowed=lambda: self._name_h_m_allowed_values,
+                ),
+            ),
+            cif_handler=CifHandler(
+                # TODO: Keep only version with "." and automate ...
+                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(
+            name='it_coordinate_system_code',
+            description='A qualifier identifying which setting in IT is used.',
+            value_spec=AttributeSpec(
+                default=lambda: self._it_coordinate_system_code_default_value,
+                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'
+
+    # ------------------------------------------------------------------
+    #  Private helper methods
+    # ------------------------------------------------------------------
+
+    def _reset_it_coordinate_system_code(self) -> None:
+        """Reset the IT coordinate system code to the default for the
+        current group.
+        """
+        self._it_coordinate_system_code.value = self._it_coordinate_system_code_default_value
+
+    @property
+    def _name_h_m_allowed_values(self) -> list[str]:
+        """Return the list of recognised Hermann–Mauguin short symbols.
+
+        Returns:
+            list[str]: All short H-M symbols known to *cryspy*.
+        """
+        return ACCESIBLE_NAME_HM_SHORT
+
+    @property
+    def _it_coordinate_system_code_allowed_values(self) -> list[str]:
+        """Return allowed IT coordinate system codes for the current
+        group.
+
+        Returns:
+            list[str]: Coordinate-system codes, or ``['']`` when none
+                are defined.
+        """
+        name = self.name_h_m.value
+        it_number = get_it_number_by_name_hm_short(name)
+        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) -> str:
+        """Return the default IT coordinate system code.
+
+        Returns:
+            str: First element of the allowed codes list.
+        """
+        return self._it_coordinate_system_code_allowed_values[0]
+
+    # ------------------------------------------------------------------
+    #  Public properties
+    # ------------------------------------------------------------------
+
+    @property
+    def name_h_m(self) -> StringDescriptor:
+        """Hermann–Mauguin symbol of the space group.
+
+        Returns:
+            StringDescriptor: Descriptor holding the H-M symbol.
+        """
+        return self._name_h_m
+
+    @name_h_m.setter
+    def name_h_m(self, value: str) -> None:
+        """Set the Hermann–Mauguin symbol and reset the coordinate-
+        system code.
+
+        Args:
+            value (str): New H-M symbol (must be a recognised short
+                symbol).
+        """
+        self._name_h_m.value = value
+        self._reset_it_coordinate_system_code()
+
+    @property
+    def it_coordinate_system_code(self) -> StringDescriptor:
+        """International Tables coordinate-system code.
+
+        Returns:
+            StringDescriptor: Descriptor holding the IT code.
+        """
+        return self._it_coordinate_system_code
+
+    @it_coordinate_system_code.setter
+    def it_coordinate_system_code(self, value: str) -> None:
+        """Set the IT coordinate-system code.
+
+        Args:
+            value (str): New coordinate-system code (must be allowed for
+                the current space group).
+        """
+        self._it_coordinate_system_code.value = value
diff --git a/src/easydiffraction/datablocks/structure/categories/space_group/factory.py b/src/easydiffraction/datablocks/structure/categories/space_group/factory.py
new file mode 100644
index 00000000..87807cef
--- /dev/null
+++ b/src/easydiffraction/datablocks/structure/categories/space_group/factory.py
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Space-group factory — delegates entirely to ``FactoryBase``."""
+
+from __future__ import annotations
+
+from easydiffraction.core.factory import FactoryBase
+
+
+class SpaceGroupFactory(FactoryBase):
+    """Create space-group categories by tag."""
+
+    _default_rules = {
+        frozenset(): 'default',
+    }
diff --git a/src/easydiffraction/datablocks/structure/collection.py b/src/easydiffraction/datablocks/structure/collection.py
new file mode 100644
index 00000000..ecc8f26e
--- /dev/null
+++ b/src/easydiffraction/datablocks/structure/collection.py
@@ -0,0 +1,82 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Collection of structure data blocks."""
+
+from typeguard import typechecked
+
+from easydiffraction.core.datablock import DatablockCollection
+from easydiffraction.datablocks.structure.item.base import Structure
+from easydiffraction.datablocks.structure.item.factory import StructureFactory
+from easydiffraction.utils.logging import console
+
+
+class Structures(DatablockCollection):
+    """Ordered collection of :class:`Structure` instances.
+
+    Provides convenience ``add_from_*`` methods that mirror the
+    :class:`StructureFactory` classmethods plus a bare :meth:`add` for
+    inserting pre-built structures.
+    """
+
+    def __init__(self) -> None:
+        """Initialise an empty structures collection."""
+        super().__init__(item_type=Structure)
+
+    # ------------------------------------------------------------------
+    # Public methods
+    # ------------------------------------------------------------------
+
+    # TODO: Make abstract in DatablockCollection?
+    @typechecked
+    def create(
+        self,
+        *,
+        name: str,
+    ) -> None:
+        """Create a minimal structure and add it to the collection.
+
+        Args:
+            name (str): Identifier for the new structure.
+        """
+        structure = StructureFactory.from_scratch(name=name)
+        self.add(structure)
+
+    # TODO: Move to DatablockCollection?
+    @typechecked
+    def add_from_cif_str(
+        self,
+        cif_str: str,
+    ) -> None:
+        """Create a structure from CIF content and add it.
+
+        Args:
+            cif_str (str): CIF file content as a string.
+        """
+        structure = StructureFactory.from_cif_str(cif_str)
+        self.add(structure)
+
+    # TODO: Move to DatablockCollection?
+    @typechecked
+    def add_from_cif_path(
+        self,
+        cif_path: str,
+    ) -> None:
+        """Create a structure from a CIF file and add it.
+
+        Args:
+            cif_path (str): Filesystem path to a CIF file.
+        """
+        structure = StructureFactory.from_cif_path(cif_path)
+        self.add(structure)
+
+    # TODO: Move to DatablockCollection?
+    def show_names(self) -> None:
+        """List all structure names in the collection."""
+        console.paragraph('Defined structures' + ' 🧩')
+        console.print(self.names)
+
+    # TODO: Move to DatablockCollection?
+    def show_params(self) -> None:
+        """Show parameters of all structures in the collection."""
+        for structure in self.values():
+            structure.show_params()
diff --git a/src/easydiffraction/sample_models/__init__.py b/src/easydiffraction/datablocks/structure/item/__init__.py
similarity index 100%
rename from src/easydiffraction/sample_models/__init__.py
rename to src/easydiffraction/datablocks/structure/item/__init__.py
diff --git a/src/easydiffraction/datablocks/structure/item/base.py b/src/easydiffraction/datablocks/structure/item/base.py
new file mode 100644
index 00000000..cd517785
--- /dev/null
+++ b/src/easydiffraction/datablocks/structure/item/base.py
@@ -0,0 +1,233 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Structure datablock item."""
+
+from typeguard import typechecked
+
+from easydiffraction.core.datablock import DatablockItem
+from easydiffraction.datablocks.structure.categories.atom_sites import AtomSites
+from easydiffraction.datablocks.structure.categories.atom_sites.factory import AtomSitesFactory
+from easydiffraction.datablocks.structure.categories.cell import Cell
+from easydiffraction.datablocks.structure.categories.cell.factory import CellFactory
+from easydiffraction.datablocks.structure.categories.space_group import SpaceGroup
+from easydiffraction.datablocks.structure.categories.space_group.factory import SpaceGroupFactory
+from easydiffraction.utils.logging import console
+from easydiffraction.utils.logging import log
+from easydiffraction.utils.utils import render_cif
+
+
+class Structure(DatablockItem):
+    """Structure datablock item."""
+
+    def __init__(
+        self,
+        *,
+        name: str,
+    ) -> None:
+        super().__init__()
+        self._name = name
+        self._cell_type: str = CellFactory.default_tag()
+        self._cell = CellFactory.create(self._cell_type)
+        self._space_group_type: str = SpaceGroupFactory.default_tag()
+        self._space_group = SpaceGroupFactory.create(self._space_group_type)
+        self._atom_sites_type: str = AtomSitesFactory.default_tag()
+        self._atom_sites = AtomSitesFactory.create(self._atom_sites_type)
+        self._identity.datablock_entry_name = lambda: self.name
+
+    # ------------------------------------------------------------------
+    # Public properties
+    # ------------------------------------------------------------------
+
+    @property
+    def name(self) -> str:
+        """Name identifier for this structure.
+
+        Returns:
+            str: The structure's name.
+        """
+        return self._name
+
+    @name.setter
+    @typechecked
+    def name(self, new: str) -> None:
+        """Set the name identifier for this structure.
+
+        Args:
+            new (str): New name string.
+        """
+        self._name = new
+
+    # ------------------------------------------------------------------
+    #  Cell (switchable-category pattern)
+    # ------------------------------------------------------------------
+
+    @property
+    def cell(self) -> Cell:
+        """Unit-cell category for this structure."""
+        return self._cell
+
+    @cell.setter
+    @typechecked
+    def cell(self, new: Cell) -> None:
+        """Replace the unit-cell category for this structure.
+
+        Args:
+            new (Cell): New unit-cell instance.
+        """
+        self._cell = new
+
+    @property
+    def cell_type(self) -> str:
+        """Tag of the active unit-cell type."""
+        return self._cell_type
+
+    @cell_type.setter
+    def cell_type(self, new_type: str) -> None:
+        """Switch to a different unit-cell type.
+
+        Args:
+            new_type: Cell tag (e.g. ``'default'``).
+        """
+        supported_tags = CellFactory.supported_tags()
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported cell type '{new_type}'. "
+                f'Supported: {supported_tags}. '
+                f"For more information, use 'show_supported_cell_types()'",
+            )
+            return
+        self._cell = CellFactory.create(new_type)
+        self._cell_type = new_type
+        console.paragraph(f"Cell type for structure '{self.name}' changed to")
+        console.print(new_type)
+
+    def show_supported_cell_types(self) -> None:
+        """Print a table of supported unit-cell types."""
+        CellFactory.show_supported()
+
+    def show_current_cell_type(self) -> None:
+        """Print the currently used unit-cell type."""
+        console.paragraph('Current cell type')
+        console.print(self.cell_type)
+
+    # ------------------------------------------------------------------
+    #  Space group (switchable-category pattern)
+    # ------------------------------------------------------------------
+
+    @property
+    def space_group(self) -> SpaceGroup:
+        """Space-group category for this structure."""
+        return self._space_group
+
+    @space_group.setter
+    @typechecked
+    def space_group(self, new: SpaceGroup) -> None:
+        """Replace the space-group category for this structure.
+
+        Args:
+            new (SpaceGroup): New space-group instance.
+        """
+        self._space_group = new
+
+    @property
+    def space_group_type(self) -> str:
+        """Tag of the active space-group type."""
+        return self._space_group_type
+
+    @space_group_type.setter
+    def space_group_type(self, new_type: str) -> None:
+        """Switch to a different space-group type.
+
+        Args:
+            new_type: Space-group tag (e.g. ``'default'``).
+        """
+        supported_tags = SpaceGroupFactory.supported_tags()
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported space group type '{new_type}'. "
+                f'Supported: {supported_tags}. '
+                f"For more information, use 'show_supported_space_group_types()'",
+            )
+            return
+        self._space_group = SpaceGroupFactory.create(new_type)
+        self._space_group_type = new_type
+        console.paragraph(f"Space group type for structure '{self.name}' changed to")
+        console.print(new_type)
+
+    def show_supported_space_group_types(self) -> None:
+        """Print a table of supported space-group types."""
+        SpaceGroupFactory.show_supported()
+
+    def show_current_space_group_type(self) -> None:
+        """Print the currently used space-group type."""
+        console.paragraph('Current space group type')
+        console.print(self.space_group_type)
+
+    # ------------------------------------------------------------------
+    #  Atom sites (switchable-category pattern)
+    # ------------------------------------------------------------------
+
+    @property
+    def atom_sites(self) -> AtomSites:
+        """Atom-sites collection for this structure."""
+        return self._atom_sites
+
+    @atom_sites.setter
+    @typechecked
+    def atom_sites(self, new: AtomSites) -> None:
+        """Replace the atom-sites collection for this structure.
+
+        Args:
+            new (AtomSites): New atom-sites collection.
+        """
+        self._atom_sites = new
+
+    @property
+    def atom_sites_type(self) -> str:
+        """Tag of the active atom-sites collection type."""
+        return self._atom_sites_type
+
+    @atom_sites_type.setter
+    def atom_sites_type(self, new_type: str) -> None:
+        """Switch to a different atom-sites collection type.
+
+        Args:
+            new_type: Atom-sites tag (e.g. ``'default'``).
+        """
+        supported_tags = AtomSitesFactory.supported_tags()
+        if new_type not in supported_tags:
+            log.warning(
+                f"Unsupported atom sites type '{new_type}'. "
+                f'Supported: {supported_tags}. '
+                f"For more information, use 'show_supported_atom_sites_types()'",
+            )
+            return
+        self._atom_sites = AtomSitesFactory.create(new_type)
+        self._atom_sites_type = new_type
+        console.paragraph(f"Atom sites type for structure '{self.name}' changed to")
+        console.print(new_type)
+
+    def show_supported_atom_sites_types(self) -> None:
+        """Print a table of supported atom-sites collection types."""
+        AtomSitesFactory.show_supported()
+
+    def show_current_atom_sites_type(self) -> None:
+        """Print the currently used atom-sites collection type."""
+        console.paragraph('Current atom sites type')
+        console.print(self.atom_sites_type)
+
+    # ------------------------------------------------------------------
+    # Public methods
+    # ------------------------------------------------------------------
+
+    def show(self) -> None:
+        """Display an ASCII projection of the structure on a 2D
+        plane.
+        """
+        console.paragraph(f"Structure 🧩 '{self.name}'")
+        console.print('Not implemented yet.')
+
+    def show_as_cif(self) -> None:
+        """Render the CIF text for this structure in the terminal."""
+        console.paragraph(f"Structure 🧩 '{self.name}' as cif")
+        render_cif(self.as_cif)
diff --git a/src/easydiffraction/datablocks/structure/item/factory.py b/src/easydiffraction/datablocks/structure/item/factory.py
new file mode 100644
index 00000000..a2067dff
--- /dev/null
+++ b/src/easydiffraction/datablocks/structure/item/factory.py
@@ -0,0 +1,116 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+"""Factory for creating structure instances from various inputs.
+
+Provides individual class methods for each creation pathway:
+``from_scratch``, ``from_cif_path``, or ``from_cif_str``.
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from typeguard import typechecked
+
+from easydiffraction.datablocks.structure.item.base import Structure
+from easydiffraction.io.cif.parse import document_from_path
+from easydiffraction.io.cif.parse import document_from_string
+from easydiffraction.io.cif.parse import name_from_block
+from easydiffraction.io.cif.parse import pick_sole_block
+from easydiffraction.utils.logging import log
+
+if TYPE_CHECKING:
+    import gemmi
+
+
+class StructureFactory:
+    """Create :class:`Structure` instances from supported inputs."""
+
+    def __init__(self):
+        log.error(
+            'Structure objects must be created using class methods such as '
+            '`StructureFactory.from_cif_str(...)`, etc.'
+        )
+
+    # ------------------------------------------------------------------
+    # Private helper methods
+    # ------------------------------------------------------------------
+
+    @classmethod
+    # TODO: @typechecked fails to find gemmi?
+    def _from_gemmi_block(
+        cls,
+        block: gemmi.cif.Block,
+    ) -> Structure:
+        """Build a structure from a single *gemmi* CIF block.
+
+        Args:
+            block (gemmi.cif.Block): Parsed CIF data block.
+
+        Returns:
+            Structure: A fully populated structure instance.
+        """
+        name = name_from_block(block)
+        structure = Structure(name=name)
+        for category in structure.categories:
+            category.from_cif(block)
+        return structure
+
+    # ------------------------------------------------------------------
+    # Public methods
+    # ------------------------------------------------------------------
+
+    @classmethod
+    @typechecked
+    def from_scratch(
+        cls,
+        *,
+        name: str,
+    ) -> Structure:
+        """Create a minimal default structure.
+
+        Args:
+            name (str): Identifier for the new structure.
+
+        Returns:
+            Structure: An empty structure with default categories.
+        """
+        return Structure(name=name)
+
+    # TODO: add minimal default configuration for missing parameters
+    @classmethod
+    @typechecked
+    def from_cif_str(
+        cls,
+        cif_str: str,
+    ) -> Structure:
+        """Create a structure by parsing a CIF string.
+
+        Args:
+            cif_str (str): Raw CIF content.
+
+        Returns:
+            Structure: A populated structure instance.
+        """
+        doc = document_from_string(cif_str)
+        block = pick_sole_block(doc)
+        return cls._from_gemmi_block(block)
+
+    # TODO: Read content and call self.from_cif_str
+    @classmethod
+    @typechecked
+    def from_cif_path(
+        cls,
+        cif_path: str,
+    ) -> Structure:
+        """Create a structure by reading and parsing a CIF file.
+
+        Args:
+            cif_path (str): Filesystem path to a CIF file.
+
+        Returns:
+            Structure: A populated structure instance.
+        """
+        doc = document_from_path(cif_path)
+        block = pick_sole_block(doc)
+        return cls._from_gemmi_block(block)
diff --git a/src/easydiffraction/display/base.py b/src/easydiffraction/display/base.py
index c3babcff..d97f7be9 100644
--- a/src/easydiffraction/display/base.py
+++ b/src/easydiffraction/display/base.py
@@ -12,7 +12,7 @@
 
 import pandas as pd
 
-from easydiffraction.core.singletons import SingletonBase
+from easydiffraction.core.singleton import SingletonBase
 from easydiffraction.utils.logging import console
 from easydiffraction.utils.logging import log
 
diff --git a/src/easydiffraction/display/plotters/base.py b/src/easydiffraction/display/plotters/base.py
index 85892950..7220dfeb 100644
--- a/src/easydiffraction/display/plotters/base.py
+++ b/src/easydiffraction/display/plotters/base.py
@@ -8,9 +8,9 @@
 
 import numpy as np
 
-from easydiffraction.experiments.experiment.enums import BeamModeEnum
-from easydiffraction.experiments.experiment.enums import SampleFormEnum
-from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
 
 DEFAULT_HEIGHT = 25
 DEFAULT_MIN = -np.inf
diff --git a/src/easydiffraction/experiments/categories/background/factory.py b/src/easydiffraction/experiments/categories/background/factory.py
deleted file mode 100644
index 716260f4..00000000
--- a/src/easydiffraction/experiments/categories/background/factory.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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}'. "
-                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/data/factory.py b/src/easydiffraction/experiments/categories/data/factory.py
deleted file mode 100644
index a7d4df0a..00000000
--- a/src/easydiffraction/experiments/categories/data/factory.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-from typing import Optional
-
-from easydiffraction.experiments.categories.data.bragg_pd import PdCwlData
-from easydiffraction.experiments.categories.data.bragg_pd import PdTofData
-from easydiffraction.experiments.categories.data.bragg_sc import ReflnData
-from easydiffraction.experiments.categories.data.total_pd import TotalData
-from easydiffraction.experiments.experiment.enums import BeamModeEnum
-from easydiffraction.experiments.experiment.enums import SampleFormEnum
-from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum
-
-if TYPE_CHECKING:
-    from easydiffraction.core.category import CategoryCollection
-
-
-class DataFactory:
-    """Factory for creating diffraction data collections."""
-
-    _supported = {
-        SampleFormEnum.POWDER: {
-            ScatteringTypeEnum.BRAGG: {
-                BeamModeEnum.CONSTANT_WAVELENGTH: PdCwlData,
-                BeamModeEnum.TIME_OF_FLIGHT: PdTofData,
-            },
-            ScatteringTypeEnum.TOTAL: {
-                BeamModeEnum.CONSTANT_WAVELENGTH: TotalData,
-                BeamModeEnum.TIME_OF_FLIGHT: TotalData,
-            },
-        },
-        SampleFormEnum.SINGLE_CRYSTAL: {
-            ScatteringTypeEnum.BRAGG: {
-                BeamModeEnum.CONSTANT_WAVELENGTH: ReflnData,
-                BeamModeEnum.TIME_OF_FLIGHT: ReflnData,
-            },
-        },
-    }
-
-    @classmethod
-    def create(
-        cls,
-        *,
-        sample_form: Optional[SampleFormEnum] = None,
-        beam_mode: Optional[BeamModeEnum] = None,
-        scattering_type: Optional[ScatteringTypeEnum] = None,
-    ) -> CategoryCollection:
-        """Create a data collection for the given configuration."""
-        if sample_form is None:
-            sample_form = SampleFormEnum.default()
-        if beam_mode is None:
-            beam_mode = BeamModeEnum.default()
-        if scattering_type is None:
-            scattering_type = ScatteringTypeEnum.default()
-
-        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_scattering_types = list(cls._supported[sample_form].keys())
-        if scattering_type not in supported_scattering_types:
-            raise ValueError(
-                f"Unsupported scattering type: '{scattering_type}' for sample form: "
-                f"'{sample_form}'.\n Supported scattering types: '{supported_scattering_types}'"
-            )
-        supported_beam_modes = list(cls._supported[sample_form][scattering_type].keys())
-        if beam_mode not in supported_beam_modes:
-            raise ValueError(
-                f"Unsupported beam mode: '{beam_mode}' for sample form: "
-                f"'{sample_form}' and scattering type '{scattering_type}'.\n"
-                f"Supported beam modes: '{supported_beam_modes}'"
-            )
-
-        data_class = cls._supported[sample_form][scattering_type][beam_mode]
-        data_obj = data_class()
-
-        return data_obj
diff --git a/src/easydiffraction/experiments/categories/instrument/factory.py b/src/easydiffraction/experiments/categories/instrument/factory.py
deleted file mode 100644
index d1d5982c..00000000
--- a/src/easydiffraction/experiments/categories/instrument/factory.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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 SampleFormEnum
-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
-    SF = SampleFormEnum
-
-    @classmethod
-    def _supported_map(cls) -> dict:
-        # Lazy import to avoid circulars
-        from easydiffraction.experiments.categories.instrument.cwl import CwlPdInstrument
-        from easydiffraction.experiments.categories.instrument.cwl import CwlScInstrument
-        from easydiffraction.experiments.categories.instrument.tof import TofPdInstrument
-        from easydiffraction.experiments.categories.instrument.tof import TofScInstrument
-
-        return {
-            cls.ST.BRAGG: {
-                cls.BM.CONSTANT_WAVELENGTH: {
-                    cls.SF.POWDER: CwlPdInstrument,
-                    cls.SF.SINGLE_CRYSTAL: CwlScInstrument,
-                },
-                cls.BM.TIME_OF_FLIGHT: {
-                    cls.SF.POWDER: TofPdInstrument,
-                    cls.SF.SINGLE_CRYSTAL: TofScInstrument,
-                },
-            }
-        }
-
-    @classmethod
-    def create(
-        cls,
-        scattering_type: Optional[ScatteringTypeEnum] = None,
-        beam_mode: Optional[BeamModeEnum] = None,
-        sample_form: Optional[SampleFormEnum] = None,
-    ) -> InstrumentBase:
-        if beam_mode is None:
-            beam_mode = BeamModeEnum.default()
-        if scattering_type is None:
-            scattering_type = ScatteringTypeEnum.default()
-        if sample_form is None:
-            sample_form = SampleFormEnum.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}'
-            )
-
-        supported_sample_forms = list(supported[scattering_type][beam_mode].keys())
-        if sample_form not in supported_sample_forms:
-            raise ValueError(
-                f"Unsupported sample form: '{sample_form}' for scattering type: "
-                f"'{scattering_type}' and beam mode: '{beam_mode}'.\n "
-                f'Supported sample forms: {supported_sample_forms}'
-            )
-
-        instrument_class: Type[InstrumentBase] = supported[scattering_type][beam_mode][sample_form]
-        return instrument_class()
diff --git a/src/easydiffraction/experiments/categories/peak/cwl.py b/src/easydiffraction/experiments/categories/peak/cwl.py
deleted file mode 100644
index 76777a44..00000000
--- a/src/easydiffraction/experiments/categories/peak/cwl.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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
deleted file mode 100644
index 47d48636..00000000
--- a/src/easydiffraction/experiments/categories/peak/cwl_mixins.py
+++ /dev/null
@@ -1,332 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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.
-    """
-
-    # TODO: Rename to cwl. Check other mixins for naming consistency.
-    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
deleted file mode 100644
index a0aabf10..00000000
--- a/src/easydiffraction/experiments/categories/peak/factory.py
+++ /dev/null
@@ -1,130 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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
deleted file mode 100644
index b38c4548..00000000
--- a/src/easydiffraction/experiments/categories/peak/tof.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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
deleted file mode 100644
index ca459754..00000000
--- a/src/easydiffraction/experiments/categories/peak/tof_mixins.py
+++ /dev/null
@@ -1,293 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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
deleted file mode 100644
index e1f7c28c..00000000
--- a/src/easydiffraction/experiments/categories/peak/total.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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
deleted file mode 100644
index 03907ffa..00000000
--- a/src/easydiffraction/experiments/categories/peak/total_mixins.py
+++ /dev/null
@@ -1,181 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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/experiment/__init__.py b/src/easydiffraction/experiments/experiment/__init__.py
deleted file mode 100644
index dee12f85..00000000
--- a/src/easydiffraction/experiments/experiment/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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 CwlScExperiment
-from easydiffraction.experiments.experiment.bragg_sc import TofScExperiment
-from easydiffraction.experiments.experiment.total_pd import TotalPdExperiment
-
-__all__ = [
-    'ExperimentBase',
-    'PdExperimentBase',
-    'BraggPdExperiment',
-    'TotalPdExperiment',
-    'CwlScExperiment',
-    'TofScExperiment',
-]
diff --git a/src/easydiffraction/experiments/experiment/base.py b/src/easydiffraction/experiments/experiment/base.py
deleted file mode 100644
index 27a75c37..00000000
--- a/src/easydiffraction/experiments/experiment/base.py
+++ /dev/null
@@ -1,317 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-from abc import abstractmethod
-from typing import TYPE_CHECKING
-from typing import Any
-from typing import List
-
-from easydiffraction.core.datablock import DatablockItem
-from easydiffraction.experiments.categories.data.factory import DataFactory
-from easydiffraction.experiments.categories.excluded_regions import ExcludedRegions
-from easydiffraction.experiments.categories.extinction import Extinction
-from easydiffraction.experiments.categories.instrument.factory import InstrumentFactory
-from easydiffraction.experiments.categories.linked_crystal import LinkedCrystal
-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.io.cif.serialize import experiment_to_cif
-from easydiffraction.utils.logging import console
-from easydiffraction.utils.logging import log
-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
-    from easydiffraction.sample_models.sample_models import SampleModels
-
-
-class ExperimentBase(DatablockItem):
-    """Base class for all experiments with only core attributes.
-
-    Wraps experiment type and instrument.
-    """
-
-    def __init__(
-        self,
-        *,
-        name: str,
-        type: ExperimentType,
-    ):
-        super().__init__()
-        self._name = name
-        self._type = type
-        # TODO: Should return default calculator based on experiment
-        #  type
-        from easydiffraction.analysis.calculators.factory import CalculatorFactory
-
-        self._calculator = CalculatorFactory.create_calculator('cryspy')
-        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 calculator(self):
-        """Calculator engine used for pattern calculations."""
-        return self._calculator
-
-    @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 as CIF text."""
-        experiment_cif = super().as_cif
-        paragraph_title: str = f"Experiment 🔬 '{self.name}' as cif"
-        console.paragraph(paragraph_title)
-        render_cif(experiment_cif)
-
-    @abstractmethod
-    def _load_ascii_data_to_experiment(self, data_path: str) -> None:
-        """Load ASCII data from file into the experiment data category.
-
-        Args:
-            data_path: Path to the ASCII file to load.
-        """
-        raise NotImplementedError()
-
-
-class ScExperimentBase(ExperimentBase):
-    """Base class for all single crystal experiments."""
-
-    def __init__(
-        self,
-        *,
-        name: str,
-        type: ExperimentType,
-    ) -> None:
-        super().__init__(name=name, type=type)
-
-        self._linked_crystal: LinkedCrystal = LinkedCrystal()
-        self._extinction: Extinction = Extinction()
-        self._instrument = InstrumentFactory.create(
-            scattering_type=self.type.scattering_type.value,
-            beam_mode=self.type.beam_mode.value,
-            sample_form=self.type.sample_form.value,
-        )
-        self._data = DataFactory.create(
-            sample_form=self.type.sample_form.value,
-            beam_mode=self.type.beam_mode.value,
-            scattering_type=self.type.scattering_type.value,
-        )
-
-    @abstractmethod
-    def _load_ascii_data_to_experiment(self, data_path: str) -> None:
-        """Load single crystal data from an ASCII file.
-
-        Args:
-            data_path: Path to data file with columns compatible with
-                the beam mode.
-        """
-        pass
-
-    @property
-    def linked_crystal(self):
-        """Linked crystal model for this experiment."""
-        return self._linked_crystal
-
-    @property
-    def extinction(self):
-        return self._extinction
-
-    @property
-    def instrument(self):
-        return self._instrument
-
-    @property
-    def data(self):
-        return self._data
-
-
-class PdExperimentBase(ExperimentBase):
-    """Base class for all powder experiments."""
-
-    def __init__(
-        self,
-        *,
-        name: str,
-        type: ExperimentType,
-    ) -> None:
-        super().__init__(name=name, type=type)
-
-        self._linked_phases: LinkedPhases = LinkedPhases()
-        self._excluded_regions: ExcludedRegions = ExcludedRegions()
-        self._peak_profile_type: PeakProfileTypeEnum = PeakProfileTypeEnum.default(
-            self.type.scattering_type.value,
-            self.type.beam_mode.value,
-        )
-        self._data = DataFactory.create(
-            sample_form=self.type.sample_form.value,
-            beam_mode=self.type.beam_mode.value,
-            scattering_type=self.type.scattering_type.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,
-        )
-
-    def _get_valid_linked_phases(
-        self,
-        sample_models: SampleModels,
-    ) -> List[Any]:
-        """Get valid linked phases for this experiment.
-
-        Args:
-            sample_models: Collection of sample models.
-
-        Returns:
-            A list of valid linked phases.
-        """
-        if not self.linked_phases:
-            print('Warning: No linked phases defined. Returning empty pattern.')
-            return []
-
-        valid_linked_phases = []
-        for linked_phase in self.linked_phases:
-            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.names}. Skipping it.'
-                )
-                continue
-            valid_linked_phases.append(linked_phase)
-
-        if not valid_linked_phases:
-            print(
-                'Warning: None of the linked phases found in Sample '
-                'Models. Returning empty pattern.'
-            )
-
-        return valid_linked_phases
-
-    @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 linked_phases(self):
-        """Collection of phases linked to this experiment."""
-        return self._linked_phases
-
-    @property
-    def excluded_regions(self):
-        """Collection of excluded regions for the x-grid."""
-        return self._excluded_regions
-
-    @property
-    def data(self):
-        return self._data
-
-    @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 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:
-                log.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:
-            log.warning(
-                f"Unsupported peak profile '{new_type.value}', "
-                f'Supported peak profiles: {supported_types}',
-                "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
-        console.paragraph(f"Peak profile type for experiment '{self.name}' changed to")
-        console.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()])
-
-        console.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."""
-        console.paragraph('Current peak profile type')
-        console.print(self.peak_profile_type)
diff --git a/src/easydiffraction/experiments/experiment/bragg_pd.py b/src/easydiffraction/experiments/experiment/bragg_pd.py
deleted file mode 100644
index b6d5466c..00000000
--- a/src/easydiffraction/experiments/experiment/bragg_pd.py
+++ /dev/null
@@ -1,142 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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.categories.instrument.factory import InstrumentFactory
-from easydiffraction.experiments.experiment.base import PdExperimentBase
-from easydiffraction.utils.logging import console
-from easydiffraction.utils.logging import log
-from easydiffraction.utils.utils import render_table
-
-if TYPE_CHECKING:
-    from easydiffraction.experiments.categories.experiment_type import ExperimentType
-
-
-class BraggPdExperiment(PdExperimentBase):
-    """Standard (Bragg) Powder Diffraction experiment class with
-    specific attributes.
-    """
-
-    def __init__(
-        self,
-        *,
-        name: str,
-        type: ExperimentType,
-    ) -> None:
-        super().__init__(name=name, type=type)
-
-        self._instrument = InstrumentFactory.create(
-            scattering_type=self.type.scattering_type.value,
-            beam_mode=self.type.beam_mode.value,
-            sample_form=self.type.sample_form.value,
-        )
-        self._background_type: BackgroundTypeEnum = BackgroundTypeEnum.default()
-        self._background = BackgroundFactory.create(background_type=self.background_type)
-
-    def _load_ascii_data_to_experiment(self, data_path: str) -> None:
-        """Load (x, y, sy) data from an ASCII file into the data
-        category.
-
-        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)``.
-
-        If ``sy`` has values smaller than ``0.0001``, they are replaced
-        with ``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
-        # TODO: Not used if loading from cif file?
-        sy = np.where(sy < 0.0001, 1.0, sy)
-
-        # Set the experiment data
-        self.data._create_items_set_xcoord_and_id(x)
-        self.data._set_intensity_meas(y)
-        self.data._set_intensity_meas_su(sy)
-
-        console.paragraph('Data loaded successfully')
-        console.print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}")
-
-    @property
-    def instrument(self):
-        return self._instrument
-
-    @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())
-            log.warning(
-                f"Unknown background type '{new_type}'. "
-                f'Supported background types: {[bt.value for bt in supported_types]}. '
-                f"For more information, use 'show_supported_background_types()'"
-            )
-            return
-        self.background = BackgroundFactory.create(new_type)
-        self._background_type = new_type
-        console.paragraph(f"Background type for experiment '{self.name}' changed to")
-        console.print(new_type)
-
-    @property
-    def background(self):
-        return self._background
-
-    @background.setter
-    def background(self, value):
-        self._background = value
-
-    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()])
-
-        console.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."""
-        console.paragraph('Current background type')
-        console.print(self.background_type)
diff --git a/src/easydiffraction/experiments/experiment/factory.py b/src/easydiffraction/experiments/experiment/factory.py
deleted file mode 100644
index b1edc64e..00000000
--- a/src/easydiffraction/experiments/experiment/factory.py
+++ /dev/null
@@ -1,213 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
-# SPDX-License-Identifier: BSD-3-Clause
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-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 CwlScExperiment
-from easydiffraction.experiments.experiment import TofScExperiment
-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
-from easydiffraction.io.cif.parse import document_from_path
-from easydiffraction.io.cif.parse import document_from_string
-from easydiffraction.io.cif.parse import name_from_block
-from easydiffraction.io.cif.parse import pick_sole_block
-
-if TYPE_CHECKING:
-    import gemmi
-
-    from easydiffraction.experiments.experiment.base import ExperimentBase
-
-
-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: {
-                BeamModeEnum.CONSTANT_WAVELENGTH: BraggPdExperiment,
-                BeamModeEnum.TIME_OF_FLIGHT: BraggPdExperiment,
-            },
-            SampleFormEnum.SINGLE_CRYSTAL: {
-                BeamModeEnum.CONSTANT_WAVELENGTH: CwlScExperiment,
-                BeamModeEnum.TIME_OF_FLIGHT: TofScExperiment,
-            },
-        },
-        ScatteringTypeEnum.TOTAL: {
-            SampleFormEnum.POWDER: {
-                BeamModeEnum.CONSTANT_WAVELENGTH: TotalPdExperiment,
-                BeamModeEnum.TIME_OF_FLIGHT: TotalPdExperiment,
-            },
-        },
-    }
-
-    @classmethod
-    def _make_experiment_type(cls, kwargs):
-        """Helper to construct an ExperimentType from keyword arguments,
-        using defaults as needed.
-        """
-        # TODO: Defaults are already in the experiment type...
-        # TODO: Merging with experiment_type_from_block from
-        #  io.cif.parse
-        return ExperimentType(
-            sample_form=kwargs.get('sample_form', SampleFormEnum.default().value),
-            beam_mode=kwargs.get('beam_mode', BeamModeEnum.default().value),
-            radiation_probe=kwargs.get('radiation_probe', RadiationProbeEnum.default().value),
-            scattering_type=kwargs.get('scattering_type', ScatteringTypeEnum.default().value),
-        )
-
-    # TODO: Move to a common CIF utility module? io.cif.parse?
-    @classmethod
-    def _create_from_gemmi_block(
-        cls,
-        block: gemmi.cif.Block,
-    ) -> ExperimentBase:
-        """Build a model instance from a single CIF block."""
-        name = name_from_block(block)
-
-        # TODO: move to io.cif.parse?
-        expt_type = ExperimentType()
-        for param in expt_type.parameters:
-            param.from_cif(block)
-
-        # Create experiment instance of appropriate class
-        # TODO: make helper method to create experiment from type
-        scattering_type = expt_type.scattering_type.value
-        sample_form = expt_type.sample_form.value
-        beam_mode = expt_type.beam_mode.value
-        expt_class = cls._SUPPORTED[scattering_type][sample_form][beam_mode]
-        expt_obj = expt_class(name=name, type=expt_type)
-
-        # Read all categories from CIF block
-        # TODO: move to io.cif.parse?
-        for category in expt_obj.categories:
-            category.from_cif(block)
-
-        return expt_obj
-
-    @classmethod
-    def _create_from_cif_path(
-        cls,
-        cif_path: str,
-    ) -> ExperimentBase:
-        """Create an experiment from a CIF file path."""
-        doc = document_from_path(cif_path)
-        block = pick_sole_block(doc)
-        return cls._create_from_gemmi_block(block)
-
-    @classmethod
-    def _create_from_cif_str(
-        cls,
-        cif_str: str,
-    ) -> ExperimentBase:
-        """Create an experiment from a CIF string."""
-        doc = document_from_string(cif_str)
-        block = pick_sole_block(doc)
-        return cls._create_from_gemmi_block(block)
-
-    @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
-        beam_mode = expt_type.beam_mode.value
-        expt_class = cls._SUPPORTED[scattering_type][sample_form][beam_mode]
-        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
-        beam_mode = expt_type.beam_mode.value
-        expt_class = cls._SUPPORTED[scattering_type][sample_form][beam_mode]
-        expt_name = kwargs['name']
-        expt_obj = expt_class(name=expt_name, type=expt_type)
-        return expt_obj
-
-    @classmethod
-    def create(cls, **kwargs):
-        """Create an `ExperimentBase` using a validated argument
-        combination.
-        """
-        # TODO: move to FactoryBase
-        # Check for valid argument combinations
-        user_args = {k for k, v in kwargs.items() if v is not None}
-        cls._validate_args(
-            present=user_args,
-            allowed_specs=cls._ALLOWED_ARG_SPECS,
-            factory_name=cls.__name__,  # TODO: move to FactoryBase
-        )
-
-        # 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)
diff --git a/src/easydiffraction/experiments/experiments.py b/src/easydiffraction/experiments/experiments.py
deleted file mode 100644
index 9f58a3b7..00000000
--- a/src/easydiffraction/experiments/experiments.py
+++ /dev/null
@@ -1,134 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
-# SPDX-License-Identifier: BSD-3-Clause
-
-from typeguard import typechecked
-
-from easydiffraction.core.datablock import DatablockCollection
-from easydiffraction.experiments.experiment.base import ExperimentBase
-from easydiffraction.experiments.experiment.factory import ExperimentFactory
-from easydiffraction.utils.logging import console
-
-
-class Experiments(DatablockCollection):
-    """Collection of Experiment data blocks.
-
-    Provides convenience constructors for common creation patterns and
-    helper methods for simple presentation of collection contents.
-    """
-
-    def __init__(self) -> None:
-        super().__init__(item_type=ExperimentBase)
-
-    # --------------------
-    # Add / Remove methods
-    # --------------------
-
-    # TODO: Move to DatablockCollection?
-    # TODO: Disallow args and only allow kwargs?
-    def add(self, **kwargs):
-        experiment = kwargs.pop('experiment', None)
-
-        if experiment is None:
-            experiment = ExperimentFactory.create(**kwargs)
-
-        self._add(experiment)
-
-    # @typechecked
-    # def add_from_cif_path(self, cif_path: str):
-    #    """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 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,
-    #    data_path: str,
-    #    sample_form: str = SampleFormEnum.default().value,
-    #    beam_mode: str = BeamModeEnum.default().value,
-    #    radiation_probe: str = RadiationProbeEnum.default().value,
-    #    scattering_type: str = ScatteringTypeEnum.default().value,
-    # ):
-    #    """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,
-    #        beam_mode=beam_mode,
-    #        radiation_probe=radiation_probe,
-    #        scattering_type=scattering_type,
-    #    )
-    #    self.add(experiment)
-
-    # @typechecked
-    # def add_without_data(
-    #    self,
-    #    name: str,
-    #    sample_form: str = SampleFormEnum.default().value,
-    #    beam_mode: str = BeamModeEnum.default().value,
-    #    radiation_probe: str = RadiationProbeEnum.default().value,
-    #    scattering_type: str = ScatteringTypeEnum.default().value,
-    # ):
-    #    """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(experiment)
-
-    # TODO: Move to DatablockCollection?
-    @typechecked
-    def remove(self, name: str) -> None:
-        """Remove an experiment by name if it exists."""
-        if name in self:
-            del self[name]
-
-    # ------------
-    # Show methods
-    # ------------
-
-    # TODO: Move to DatablockCollection?
-    def show_names(self) -> None:
-        """Print the list of experiment names."""
-        console.paragraph('Defined experiments' + ' 🔬')
-        console.print(self.names)
-
-    # TODO: Move to DatablockCollection?
-    def show_params(self) -> None:
-        """Print parameters for each experiment in the collection."""
-        for exp in self.values():
-            exp.show_params()
diff --git a/src/easydiffraction/io/cif/serialize.py b/src/easydiffraction/io/cif/serialize.py
index b6c9dae5..971b08c4 100644
--- a/src/easydiffraction/io/cif/serialize.py
+++ b/src/easydiffraction/io/cif/serialize.py
@@ -19,7 +19,7 @@
 
     from easydiffraction.core.category import CategoryCollection
     from easydiffraction.core.category import CategoryItem
-    from easydiffraction.core.parameters import GenericDescriptorBase
+    from easydiffraction.core.variable import GenericDescriptorBase
 
 
 def format_value(value) -> str:
@@ -189,8 +189,8 @@ def project_to_cif(project) -> str:
     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, 'structures', None):
+        parts.append(project.structures.as_cif)
     if getattr(project, 'experiments', None):
         parts.append(project.experiments.as_cif)
     if getattr(project, 'analysis', None):
@@ -209,13 +209,16 @@ 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(analysis.fit_mode.as_cif)
     lines.append('')
     lines.append(analysis.aliases.as_cif)
     lines.append('')
     lines.append(analysis.constraints.as_cif)
+    jfe_cif = analysis.joint_fit_experiments.as_cif
+    if jfe_cif:
+        lines.append('')
+        lines.append(jfe_cif)
     return '\n'.join(lines)
 
 
diff --git a/src/easydiffraction/project/project.py b/src/easydiffraction/project/project.py
index 5d0a53ca..f7a3f487 100644
--- a/src/easydiffraction/project/project.py
+++ b/src/easydiffraction/project/project.py
@@ -10,12 +10,12 @@
 
 from easydiffraction.analysis.analysis import Analysis
 from easydiffraction.core.guard import GuardedBase
+from easydiffraction.datablocks.experiment.collection import Experiments
+from easydiffraction.datablocks.structure.collection import Structures
 from easydiffraction.display.plotting import Plotter
 from easydiffraction.display.tables import TableRenderer
-from easydiffraction.experiments.experiments import Experiments
 from easydiffraction.io.cif.serialize import project_to_cif
 from easydiffraction.project.project_info import ProjectInfo
-from easydiffraction.sample_models.sample_models import SampleModels
 from easydiffraction.summary.summary import Summary
 from easydiffraction.utils.logging import console
 from easydiffraction.utils.logging import log
@@ -24,8 +24,7 @@
 class Project(GuardedBase):
     """Central API for managing a diffraction data analysis project.
 
-    Provides access to sample models, experiments, analysis, and
-    summary.
+    Provides access to structures, experiments, analysis, and summary.
     """
 
     # ------------------------------------------------------------------
@@ -40,7 +39,7 @@ def __init__(
         super().__init__()
 
         self._info: ProjectInfo = ProjectInfo(name, title, description)
-        self._sample_models = SampleModels()
+        self._structures = Structures()
         self._experiments = Experiments()
         self._tabler = TableRenderer.get()
         self._plotter = Plotter()
@@ -56,11 +55,11 @@ def __str__(self) -> str:
         """Human-readable representation."""
         class_name = self.__class__.__name__
         project_name = self.name
-        sample_models_count = len(self.sample_models)
+        structures_count = len(self.structures)
         experiments_count = len(self.experiments)
         return (
             f"{class_name} '{project_name}' "
-            f'({sample_models_count} sample models, '
+            f'({structures_count} structures, '
             f'{experiments_count} experiments)'
         )
 
@@ -85,14 +84,14 @@ def full_name(self) -> str:
         return self.name
 
     @property
-    def sample_models(self) -> SampleModels:
-        """Collection of sample models in the project."""
-        return self._sample_models
+    def structures(self) -> Structures:
+        """Collection of structures in the project."""
+        return self._structures
 
-    @sample_models.setter
+    @structures.setter
     @typechecked
-    def sample_models(self, sample_models: SampleModels) -> None:
-        self._sample_models = sample_models
+    def structures(self, structures: Structures) -> None:
+        self._structures = structures
 
     @property
     def experiments(self):
@@ -126,9 +125,8 @@ def summary(self):
 
     @property
     def parameters(self):
-        """Return parameters from all components (TBD)."""
-        # To be implemented: return all parameters in the project
-        return []
+        """Return parameters from all structures and experiments."""
+        return self.structures.parameters + self.experiments.parameters
 
     @property
     def as_cif(self):
@@ -143,14 +141,10 @@ def as_cif(self):
     def load(self, dir_path: str) -> None:
         """Load a project from a given directory.
 
-        Loads project info, sample models, experiments, etc.
+        Loads project info, structures, experiments, etc.
         """
-        console.paragraph('Loading project 📦 from')
-        console.print(dir_path)
-        self._info.path = dir_path
         # TODO: load project components from files inside dir_path
-        console.print('Loading project is not implemented yet.')
-        self._saved = True
+        raise NotImplementedError('Project.load() is not implemented yet.')
 
     def save(self) -> None:
         """Save the project into the existing project directory."""
@@ -169,17 +163,17 @@ def save(self) -> None:
             f.write(self._info.as_cif())
             console.print('├── 📄 project.cif')
 
-        # Save sample models
-        sm_dir = self._info.path / 'sample_models'
+        # Save structures
+        sm_dir = self._info.path / 'structures'
         sm_dir.mkdir(parents=True, exist_ok=True)
-        # Iterate over sample model objects (MutableMapping iter gives
+        # Iterate over structure objects (MutableMapping iter gives
         # keys)
-        for model in self.sample_models.values():
-            file_name: str = f'{model.name}.cif'
+        for structure in self.structures.values():
+            file_name: str = f'{structure.name}.cif'
             file_path = sm_dir / file_name
-            console.print('├── 📁 sample_models')
+            console.print('├── 📁 structures')
             with file_path.open('w') as f:
-                f.write(model.as_cif)
+                f.write(structure.as_cif)
                 console.print(f'│   └── 📄 {file_name}')
 
         # Save experiments
@@ -223,8 +217,8 @@ def save_as(
     # ------------------------------------------
 
     def _update_categories(self, expt_name) -> None:
-        for sample_model in self.sample_models:
-            sample_model._update_categories()
+        for structure in self.structures:
+            structure._update_categories()
         self.analysis._update_categories()
         experiment = self.experiments[expt_name]
         experiment._update_categories()
diff --git a/src/easydiffraction/sample_models/categories/atom_sites.py b/src/easydiffraction/sample_models/categories/atom_sites.py
deleted file mode 100644
index 955c62df..00000000
--- a/src/easydiffraction/sample_models/categories/atom_sites.py
+++ /dev/null
@@ -1,334 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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.crystallography import crystallography as ecr
-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(ge=0.0),
-            ),
-            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: str(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)
-
-    def _apply_atomic_coordinates_symmetry_constraints(self):
-        """Apply symmetry rules to fractional coordinates of atom
-        sites.
-        """
-        sample_model = self._parent
-        space_group_name = sample_model.space_group.name_h_m.value
-        space_group_coord_code = sample_model.space_group.it_coordinate_system_code.value
-        for atom in self._items:
-            dummy_atom = {
-                'fract_x': atom.fract_x.value,
-                'fract_y': atom.fract_y.value,
-                'fract_z': atom.fract_z.value,
-            }
-            wl = atom.wyckoff_letter.value
-            if not wl:
-                # TODO: Decide how to handle this case
-                #  For now, we just skip applying constraints if wyckoff
-                #  letter is not set. Alternatively, could raise an
-                #  error or warning
-                #  print(f"Warning: Wyckoff letter is not ...")
-                #  raise ValueError("Wyckoff letter is not ...")
-                continue
-            ecr.apply_atom_site_symmetry_constraints(
-                atom_site=dummy_atom,
-                name_hm=space_group_name,
-                coord_code=space_group_coord_code,
-                wyckoff_letter=wl,
-            )
-            atom.fract_x.value = dummy_atom['fract_x']
-            atom.fract_y.value = dummy_atom['fract_y']
-            atom.fract_z.value = dummy_atom['fract_z']
-
-    def _update(self, called_by_minimizer=False):
-        """Update atom sites by applying symmetry constraints."""
-        del called_by_minimizer
-
-        self._apply_atomic_coordinates_symmetry_constraints()
diff --git a/src/easydiffraction/sample_models/categories/cell.py b/src/easydiffraction/sample_models/categories/cell.py
deleted file mode 100644
index 80c0b144..00000000
--- a/src/easydiffraction/sample_models/categories/cell.py
+++ /dev/null
@@ -1,188 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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.crystallography import crystallography as ecr
-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
-
-    def _apply_cell_symmetry_constraints(self):
-        """Apply symmetry constraints to cell parameters."""
-        dummy_cell = {
-            'lattice_a': self.length_a.value,
-            'lattice_b': self.length_b.value,
-            'lattice_c': self.length_c.value,
-            'angle_alpha': self.angle_alpha.value,
-            'angle_beta': self.angle_beta.value,
-            'angle_gamma': self.angle_gamma.value,
-        }
-        space_group_name = self._parent.space_group.name_h_m.value
-
-        ecr.apply_cell_symmetry_constraints(
-            cell=dummy_cell,
-            name_hm=space_group_name,
-        )
-
-        self.length_a.value = dummy_cell['lattice_a']
-        self.length_b.value = dummy_cell['lattice_b']
-        self.length_c.value = dummy_cell['lattice_c']
-        self.angle_alpha.value = dummy_cell['angle_alpha']
-        self.angle_beta.value = dummy_cell['angle_beta']
-        self.angle_gamma.value = dummy_cell['angle_gamma']
-
-    def _update(self, called_by_minimizer=False):
-        """Update cell parameters by applying symmetry constraints."""
-        del called_by_minimizer
-
-        self._apply_cell_symmetry_constraints()
diff --git a/src/easydiffraction/sample_models/categories/space_group.py b/src/easydiffraction/sample_models/categories/space_group.py
deleted file mode 100644
index 3e726b17..00000000
--- a/src/easydiffraction/sample_models/categories/space_group.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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/sample_model/base.py b/src/easydiffraction/sample_models/sample_model/base.py
deleted file mode 100644
index 7ae135de..00000000
--- a/src/easydiffraction/sample_models/sample_model/base.py
+++ /dev/null
@@ -1,183 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
-# SPDX-License-Identifier: BSD-3-Clause
-
-from easydiffraction.core.datablock import DatablockItem
-from easydiffraction.crystallography import crystallography as ecr
-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.logging import console
-from easydiffraction.utils.utils import render_cif
-
-
-class SampleModelBase(DatablockItem):
-    """Base sample model and container for structural information.
-
-    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.
-    """
-
-    def __init__(
-        self,
-        *,
-        name,
-    ) -> None:
-        super().__init__()
-        self._name = name
-        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 name(self) -> str:
-        """Model name.
-
-        Returns:
-            The user-facing identifier for this model.
-        """
-        return self._name
-
-    @name.setter
-    def name(self, new: str) -> None:
-        """Update model name."""
-        self._name = new
-
-    @property
-    def cell(self) -> Cell:
-        """Unit-cell category object."""
-        return self._cell
-
-    @cell.setter
-    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
-
-    @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) -> AtomSites:
-        """Atom-sites collection for this model."""
-        return self._atom_sites
-
-    @atom_sites.setter
-    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,
-            'lattice_c': self.cell.length_c.value,
-            'angle_alpha': self.cell.angle_alpha.value,
-            'angle_beta': self.cell.angle_beta.value,
-            'angle_gamma': self.cell.angle_gamma.value,
-        }
-        space_group_name = self.space_group.name_h_m.value
-        ecr.apply_cell_symmetry_constraints(cell=dummy_cell, name_hm=space_group_name)
-        self.cell.length_a.value = dummy_cell['lattice_a']
-        self.cell.length_b.value = dummy_cell['lattice_b']
-        self.cell.length_c.value = dummy_cell['lattice_c']
-        self.cell.angle_alpha.value = dummy_cell['angle_alpha']
-        self.cell.angle_beta.value = dummy_cell['angle_beta']
-        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:
-            dummy_atom = {
-                'fract_x': atom.fract_x.value,
-                'fract_y': atom.fract_y.value,
-                'fract_z': atom.fract_z.value,
-            }
-            wl = atom.wyckoff_letter.value
-            if not wl:
-                # TODO: Decide how to handle this case
-                #  For now, we just skip applying constraints if wyckoff
-                #  letter is not set. Alternatively, could raise an
-                #  error or warning
-                #  print(f"Warning: Wyckoff letter is not ...")
-                #  raise ValueError("Wyckoff letter is not ...")
-                continue
-            ecr.apply_atom_site_symmetry_constraints(
-                atom_site=dummy_atom,
-                name_hm=space_group_name,
-                coord_code=space_group_coord_code,
-                wyckoff_letter=wl,
-            )
-            atom.fract_x.value = dummy_atom['fract_x']
-            atom.fract_y.value = dummy_atom['fract_y']
-            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()
-
-    # ------------
-    # Show methods
-    # ------------
-
-    def show_structure(self):
-        """Show an ASCII projection of the structure on a 2D plane."""
-        console.paragraph(f"Sample model 🧩 '{self.name}' structure view")
-        console.print('Not implemented yet.')
-
-    def show_params(self):
-        """Display structural parameters (space group, cell, atom
-        sites).
-        """
-        console.print(f'\nSampleModel ID: {self.name}')
-        console.print(f'Space group: {self.space_group.name_h_m}')
-        console.print(f'Cell parameters: {self.cell.as_dict}')
-        console.print('Atom sites:')
-        self.atom_sites.show()
-
-    def show_as_cif(self) -> None:
-        """Render the CIF text for this model in a terminal-friendly
-        view.
-        """
-        cif_text: str = self.as_cif
-        paragraph_title: str = f"Sample model 🧩 '{self.name}' as cif"
-        console.paragraph(paragraph_title)
-        render_cif(cif_text)
diff --git a/src/easydiffraction/sample_models/sample_model/factory.py b/src/easydiffraction/sample_models/sample_model/factory.py
deleted file mode 100644
index 7a03b0f0..00000000
--- a/src/easydiffraction/sample_models/sample_model/factory.py
+++ /dev/null
@@ -1,103 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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
-
-from typing import TYPE_CHECKING
-
-from easydiffraction.core.factory import FactoryBase
-from easydiffraction.io.cif.parse import document_from_path
-from easydiffraction.io.cif.parse import document_from_string
-from easydiffraction.io.cif.parse import name_from_block
-from easydiffraction.io.cif.parse import pick_sole_block
-from easydiffraction.sample_models.sample_model.base import SampleModelBase
-
-if TYPE_CHECKING:
-    import gemmi
-
-
-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_from_gemmi_block(
-        cls,
-        block: gemmi.cif.Block,
-    ) -> SampleModelBase:
-        """Build a model instance from a single CIF block."""
-        name = name_from_block(block)
-        sample_model = SampleModelBase(name=name)
-        for category in sample_model.categories:
-            category.from_cif(block)
-        return sample_model
-
-    @classmethod
-    def _create_from_cif_path(
-        cls,
-        cif_path: str,
-    ) -> SampleModelBase:
-        """Create a model by reading and parsing a CIF file."""
-        doc = document_from_path(cif_path)
-        block = pick_sole_block(doc)
-        return cls._create_from_gemmi_block(block)
-
-    @classmethod
-    def _create_from_cif_str(
-        cls,
-        cif_str: str,
-    ) -> SampleModelBase:
-        """Create a model by parsing a CIF string."""
-        doc = document_from_string(cif_str)
-        block = pick_sole_block(doc)
-        return cls._create_from_gemmi_block(block)
-
-    @classmethod
-    def _create_minimal(
-        cls,
-        name: str,
-    ) -> SampleModelBase:
-        """Create a minimal default model with just a name."""
-        return SampleModelBase(name=name)
-
-    @classmethod
-    def create(cls, **kwargs):
-        """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.
-        """
-        # TODO: move to FactoryBase
-        # Check for valid argument combinations
-        user_args = {k for k, v in kwargs.items() if v is not None}
-        cls._validate_args(
-            present=user_args,
-            allowed_specs=cls._ALLOWED_ARG_SPECS,
-            factory_name=cls.__name__,  # TODO: move to FactoryBase
-        )
-
-        # 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 'name' in kwargs:
-            return cls._create_minimal(kwargs['name'])
diff --git a/src/easydiffraction/sample_models/sample_models.py b/src/easydiffraction/sample_models/sample_models.py
deleted file mode 100644
index bb85d2e9..00000000
--- a/src/easydiffraction/sample_models/sample_models.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
-# SPDX-License-Identifier: BSD-3-Clause
-
-from typeguard import typechecked
-
-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.logging import console
-
-
-class SampleModels(DatablockCollection):
-    """Collection manager for multiple SampleModel instances."""
-
-    def __init__(self) -> None:
-        super().__init__(item_type=SampleModelBase)
-
-    # --------------------
-    # Add / Remove methods
-    # --------------------
-
-    # TODO: Move to DatablockCollection?
-    # TODO: Disallow args and only allow kwargs?
-    def add(self, **kwargs):
-        sample_model = kwargs.pop('sample_model', None)
-
-        if sample_model is None:
-            sample_model = SampleModelFactory.create(**kwargs)
-
-        self._add(sample_model)
-
-    # @typechecked
-    # def add_from_cif_path(self, cif_path: str) -> None:
-    #    """Create and add a model from a CIF file path.#
-    #
-    #    Args:
-    #        cif_path: Path to a CIF file.
-    #    """
-    #    sample_model = SampleModelFactory.create(cif_path=cif_path)
-    #    self.add(sample_model)
-
-    # @typechecked
-    # def add_from_cif_str(self, cif_str: str) -> None:
-    #    """Create and add a model from CIF content (string).
-    #
-    #    Args:
-    #        cif_str: CIF file content.
-    #    """
-    #    sample_model = SampleModelFactory.create(cif_str=cif_str)
-    #    self.add(sample_model)
-
-    # @typechecked
-    # def add_minimal(self, name: str) -> None:
-    #    """Create and add a minimal model (defaults, no atoms).
-    #
-    #    Args:
-    #        name: Identifier to assign to the new model.
-    #    """
-    #    sample_model = SampleModelFactory.create(name=name)
-    #    self.add(sample_model)
-
-    # TODO: Move to DatablockCollection?
-    @typechecked
-    def remove(self, name: str) -> None:
-        """Remove a sample model by its ID.
-
-        Args:
-            name: ID of the model to remove.
-        """
-        if name in self:
-            del self[name]
-
-    # ------------
-    # Show methods
-    # ------------
-
-    # TODO: Move to DatablockCollection?
-    def show_names(self) -> None:
-        """List all model names in the collection."""
-        console.paragraph('Defined sample models' + ' 🧩')
-        console.print(self.names)
-
-    # TODO: Move to DatablockCollection?
-    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/summary.py b/src/easydiffraction/summary/summary.py
index 7d28c875..7e72d825 100644
--- a/src/easydiffraction/summary/summary.py
+++ b/src/easydiffraction/summary/summary.py
@@ -57,7 +57,7 @@ def show_crystallographic_data(self) -> None:
         """
         console.section('Crystallographic data')
 
-        for model in self.project.sample_models.values():
+        for model in self.project.structures.values():
             console.paragraph('Phase datablock')
             console.print(f'🧩 {model.name}')
 
@@ -180,7 +180,8 @@ def show_fitting_details(self) -> None:
         console.section('Fitting')
 
         console.paragraph('Calculation engine')
-        console.print(self.project.analysis.current_calculator)
+        for expt in self.project.experiments.values():
+            console.print(f'  {expt.name}: {expt.calculator_type}')
 
         console.paragraph('Minimization engine')
         console.print(self.project.analysis.current_minimizer)
diff --git a/src/easydiffraction/utils/__init__.py b/src/easydiffraction/utils/__init__.py
index e40e0816..d193bf2f 100644
--- a/src/easydiffraction/utils/__init__.py
+++ b/src/easydiffraction/utils/__init__.py
@@ -3,8 +3,3 @@
 
 from easydiffraction.utils.utils import _is_dev_version
 from easydiffraction.utils.utils import stripped_package_version
-
-__all__ = [
-    '_is_dev_version',
-    'stripped_package_version',
-]
diff --git a/tests/integration/fitting/test_multi.py b/tests/integration/fitting/test_multi.py
new file mode 100644
index 00000000..60e78f0a
--- /dev/null
+++ b/tests/integration/fitting/test_multi.py
@@ -0,0 +1,236 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+import tempfile
+
+from numpy.testing import assert_almost_equal
+
+import easydiffraction as ed
+from easydiffraction import ExperimentFactory
+from easydiffraction import Project
+from easydiffraction import StructureFactory
+from easydiffraction import download_data
+
+TEMP_DIR = tempfile.gettempdir()
+
+
+def test_single_fit_neutron_pd_tof_mcstas_lbco_si() -> None:
+    # Set structures
+    model_1 = StructureFactory.from_scratch(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.create(
+        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.create(
+        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.create(
+        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.create(
+        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 = StructureFactory.from_scratch(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.create(
+        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_path = download_data(id=8, destination=TEMP_DIR)
+    expt = ExperimentFactory.from_data_path(
+        name='mcstas',
+        data_path=data_path,
+        beam_mode='time-of-flight',
+    )
+    expt.instrument.setup_twotheta_bank = 94.90931761529106
+    expt.instrument.calib_d_to_tof_offset = 0.0
+    expt.instrument.calib_d_to_tof_linear = 58724.76869981215
+    expt.instrument.calib_d_to_tof_quad = -0.00001
+    expt.peak_profile_type = 'pseudo-voigt * ikeda-carpenter'
+    expt.peak.broad_gauss_sigma_0 = 45137
+    expt.peak.broad_gauss_sigma_1 = -52394
+    expt.peak.broad_gauss_sigma_2 = 22998
+    expt.peak.broad_mix_beta_0 = 0.0055
+    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.create(id='lbco', scale=4.0)
+    expt.linked_phases.create(id='si', scale=0.2)
+    for x in range(45000, 115000, 5000):
+        expt.background.create(id=str(x), x=x, y=0.2)
+
+    # Create project
+    project = Project()
+    project.structures.add(model_1)
+    project.structures.add(model_2)
+    project.experiments.add(expt)
+
+    # Exclude regions from fitting
+    project.experiments['mcstas'].excluded_regions.create(start=108000, end=200000)
+
+    # Prepare for fitting
+    project.analysis.current_minimizer = 'lmfit'
+
+    # Select fitting parameters
+    model_1.cell.length_a.free = True
+    model_1.atom_sites['La'].b_iso.free = True
+    model_1.atom_sites['Ba'].b_iso.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.atom_sites['Si'].b_iso.free = True
+    expt.linked_phases['lbco'].scale.free = True
+    expt.linked_phases['si'].scale.free = True
+    expt.peak.broad_gauss_sigma_0.free = True
+    expt.peak.broad_gauss_sigma_1.free = True
+    expt.peak.broad_gauss_sigma_2.free = True
+    expt.peak.asym_alpha_1.free = True
+    expt.peak.broad_mix_beta_0.free = True
+    expt.peak.broad_mix_beta_1.free = True
+    for point in expt.background:
+        point.y.free = True
+
+    # Perform fit
+    project.analysis.fit()
+
+    # Compare fit quality
+    assert_almost_equal(
+        project.analysis.fit_results.reduced_chi_square,
+        desired=2.87,
+        decimal=1,
+    )
+
+
+def _test_joint_fit_bragg_pdf_neutron_pd_tof_si() -> None:
+    # Set structure (shared between Bragg and PDF experiments)
+    model = StructureFactory.from_scratch(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.431
+    model.atom_sites.create(
+        label='Si',
+        type_symbol='Si',
+        fract_x=0.125,
+        fract_y=0.125,
+        fract_z=0.125,
+        b_iso=0.5,
+    )
+
+    # Set Bragg experiment (SEPD, TOF)
+    bragg_data_path = download_data(id=7, destination=TEMP_DIR)
+    bragg_expt = ExperimentFactory.from_data_path(
+        name='sepd',
+        data_path=bragg_data_path,
+        beam_mode='time-of-flight',
+    )
+    bragg_expt.instrument.setup_twotheta_bank = 144.845
+    bragg_expt.instrument.calib_d_to_tof_offset = 0.0
+    bragg_expt.instrument.calib_d_to_tof_linear = 7476.91
+    bragg_expt.instrument.calib_d_to_tof_quad = -1.54
+    bragg_expt.peak_profile_type = 'pseudo-voigt * ikeda-carpenter'
+    bragg_expt.peak.broad_gauss_sigma_0 = 3.0
+    bragg_expt.peak.broad_gauss_sigma_1 = 40.0
+    bragg_expt.peak.broad_gauss_sigma_2 = 2.0
+    bragg_expt.peak.broad_mix_beta_0 = 0.04221
+    bragg_expt.peak.broad_mix_beta_1 = 0.00946
+    bragg_expt.peak.asym_alpha_0 = 0.0
+    bragg_expt.peak.asym_alpha_1 = 0.5971
+    bragg_expt.linked_phases.create(id='si', scale=10.0)
+    for x in range(0, 35000, 5000):
+        bragg_expt.background.create(id=str(x), x=x, y=200)
+
+    # Set PDF experiment (NOMAD, TOF)
+    pdf_data_path = ed.download_data(id=5, destination=TEMP_DIR)
+    pdf_expt = ExperimentFactory.from_data_path(
+        name='nomad',
+        data_path=pdf_data_path,
+        beam_mode='time-of-flight',
+        scattering_type='total',
+    )
+    pdf_expt.peak.damp_q = 0.02
+    pdf_expt.peak.broad_q = 0.03
+    pdf_expt.peak.cutoff_q = 35.0
+    pdf_expt.peak.sharp_delta_1 = 0.0
+    pdf_expt.peak.sharp_delta_2 = 4.0
+    pdf_expt.peak.damp_particle_diameter = 0
+    pdf_expt.linked_phases.create(id='si', scale=1.0)
+
+    # Create project
+    project = Project()
+    project.structures.add(model)
+    project.experiments.add(bragg_expt)
+    project.experiments.add(pdf_expt)
+
+    # Prepare for fitting
+    project.analysis.fit_mode.mode = 'joint'
+    project.analysis.current_minimizer = 'lmfit'
+
+    # Select fitting parameters — shared structure
+    model.cell.length_a.free = True
+    model.atom_sites['Si'].b_iso.free = True
+
+    # Select fitting parameters — Bragg experiment
+    bragg_expt.linked_phases['si'].scale.free = True
+    bragg_expt.instrument.calib_d_to_tof_offset.free = True
+    for point in bragg_expt.background:
+        point.y.free = True
+
+    # Select fitting parameters — PDF experiment
+    pdf_expt.linked_phases['si'].scale.free = True
+    pdf_expt.peak.damp_q.free = True
+    pdf_expt.peak.broad_q.free = True
+    pdf_expt.peak.sharp_delta_1.free = True
+    pdf_expt.peak.sharp_delta_2.free = True
+
+    # Perform fit
+    project.analysis.fit()
+
+    # Compare fit quality
+    assert_almost_equal(
+        project.analysis.fit_results.reduced_chi_square,
+        desired=8978.39,
+        decimal=-2,
+    )
+
+
+if __name__ == '__main__':
+    test_single_fit_neutron_pd_tof_mcstas_lbco_si()
+    # test_joint_fit_bragg_pdf_neutron_pd_tof_si()
diff --git a/tests/integration/fitting/test_pair-distribution-function.py b/tests/integration/fitting/test_pair-distribution-function.py
index 6af36e52..823fd420 100644
--- a/tests/integration/fitting/test_pair-distribution-function.py
+++ b/tests/integration/fitting/test_pair-distribution-function.py
@@ -14,13 +14,13 @@
 def test_single_fit_pdf_xray_pd_cw_nacl() -> None:
     project = ed.Project()
 
-    # Set sample model
-    project.sample_models.add(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(
+    # Set structure
+    project.structures.create(name='nacl')
+    structure = project.structures['nacl']
+    structure.space_group.name_h_m = 'F m -3 m'
+    structure.space_group.it_coordinate_system_code = '1'
+    structure.cell.length_a = 5.6018
+    structure.atom_sites.create(
         label='Na',
         type_symbol='Na',
         fract_x=0,
@@ -29,7 +29,7 @@ def test_single_fit_pdf_xray_pd_cw_nacl() -> None:
         wyckoff_letter='a',
         b_iso=1.1053,
     )
-    sample_model.atom_sites.add(
+    structure.atom_sites.create(
         label='Cl',
         type_symbol='Cl',
         fract_x=0.5,
@@ -41,7 +41,7 @@ def test_single_fit_pdf_xray_pd_cw_nacl() -> None:
 
     # Set experiment
     data_path = ed.download_data(id=4, destination=TEMP_DIR)
-    project.experiments.add(
+    project.experiments.add_from_data_path(
         name='xray_pdf',
         data_path=data_path,
         sample_form='powder',
@@ -57,18 +57,17 @@ 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.create(id='nacl', scale=0.4254)
 
     # Select fitting parameters
-    sample_model.cell.length_a.free = True
-    sample_model.atom_sites['Na'].b_iso.free = True
-    sample_model.atom_sites['Cl'].b_iso.free = True
+    structure.cell.length_a.free = True
+    structure.atom_sites['Na'].b_iso.free = True
+    structure.atom_sites['Cl'].b_iso.free = True
     experiment.linked_phases['nacl'].scale.free = True
     experiment.peak.damp_q.free = True
     experiment.peak.sharp_delta_2.free = True
 
     # Perform fit
-    project.analysis.current_calculator = 'pdffit'
     project.analysis.fit()
 
     # Compare fit quality
@@ -80,13 +79,13 @@ def test_single_fit_pdf_xray_pd_cw_nacl() -> None:
 def test_single_fit_pdf_neutron_pd_cw_ni():
     project = ed.Project()
 
-    # Set sample model
-    project.sample_models.add(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(
+    # Set structure
+    project.structures.create(name='ni')
+    structure = project.structures['ni']
+    structure.space_group.name_h_m.value = 'F m -3 m'
+    structure.space_group.it_coordinate_system_code = '1'
+    structure.cell.length_a = 3.526
+    structure.atom_sites.create(
         label='Ni',
         type_symbol='Ni',
         fract_x=0,
@@ -98,7 +97,7 @@ def test_single_fit_pdf_neutron_pd_cw_ni():
 
     # Set experiment
     data_path = ed.download_data(id=6, destination=TEMP_DIR)
-    project.experiments.add(
+    project.experiments.add_from_data_path(
         name='pdf',
         data_path=data_path,
         sample_form='powder',
@@ -113,17 +112,16 @@ 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.create(id='ni', scale=0.9892)
 
     # Select fitting parameters
-    sample_model.cell.length_a.free = True
-    sample_model.atom_sites['Ni'].b_iso.free = True
+    structure.cell.length_a.free = True
+    structure.atom_sites['Ni'].b_iso.free = True
     experiment.linked_phases['ni'].scale.free = True
     experiment.peak.broad_q.free = True
     experiment.peak.sharp_delta_2.free = True
 
     # Perform fit
-    project.analysis.current_calculator = 'pdffit'
     project.analysis.fit()
 
     # Compare fit quality
@@ -134,13 +132,13 @@ def test_single_fit_pdf_neutron_pd_cw_ni():
 def test_single_fit_pdf_neutron_pd_tof_si():
     project = ed.Project()
 
-    # Set sample model
-    project.sample_models.add(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(
+    # Set structure
+    project.structures.create(name='si')
+    structure = project.structures['si']
+    structure.space_group.name_h_m.value = 'F d -3 m'
+    structure.space_group.it_coordinate_system_code = '1'
+    structure.cell.length_a = 5.4306
+    structure.atom_sites.create(
         label='Si',
         type_symbol='Si',
         fract_x=0,
@@ -152,7 +150,7 @@ def test_single_fit_pdf_neutron_pd_tof_si():
 
     # Set experiment
     data_path = ed.download_data(id=5, destination=TEMP_DIR)
-    project.experiments.add(
+    project.experiments.add_from_data_path(
         name='nomad',
         data_path=data_path,
         sample_form='powder',
@@ -167,11 +165,11 @@ 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.create(id='si', scale=1.2728)
 
     # Select fitting parameters
-    project.sample_models['si'].cell.length_a.free = True
-    project.sample_models['si'].atom_sites['Si'].b_iso.free = True
+    project.structures['si'].cell.length_a.free = True
+    project.structures['si'].atom_sites['Si'].b_iso.free = True
     experiment.linked_phases['si'].scale.free = True
     experiment.peak.damp_q.free = True
     experiment.peak.broad_q.free = True
@@ -179,7 +177,6 @@ def test_single_fit_pdf_neutron_pd_tof_si():
     experiment.peak.sharp_delta_2.free = True
 
     # Perform fit
-    project.analysis.current_calculator = 'pdffit'
     project.analysis.fit()
 
     # Compare fit quality
diff --git a/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py b/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py
index f8d7fd6d..49e7b4b1 100644
--- a/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py
+++ b/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py
@@ -8,18 +8,18 @@
 
 from easydiffraction import ExperimentFactory
 from easydiffraction import Project
-from easydiffraction import SampleModelFactory
+from easydiffraction import StructureFactory
 from easydiffraction import download_data
 
 TEMP_DIR = tempfile.gettempdir()
 
 
 def test_single_fit_neutron_pd_cwl_lbco() -> None:
-    # Set sample model
-    model = SampleModelFactory.create(name='lbco')
+    # Set structure
+    model = StructureFactory.from_scratch(name='lbco')
     model.space_group.name_h_m = 'P m -3 m'
     model.cell.length_a = 3.88
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='La',
         type_symbol='La',
         fract_x=0,
@@ -29,7 +29,7 @@ def test_single_fit_neutron_pd_cwl_lbco() -> None:
         occupancy=0.5,
         b_iso=0.1,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='Ba',
         type_symbol='Ba',
         fract_x=0,
@@ -39,7 +39,7 @@ def test_single_fit_neutron_pd_cwl_lbco() -> None:
         occupancy=0.5,
         b_iso=0.1,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='Co',
         type_symbol='Co',
         fract_x=0.5,
@@ -48,7 +48,7 @@ def test_single_fit_neutron_pd_cwl_lbco() -> None:
         wyckoff_letter='b',
         b_iso=0.1,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='O',
         type_symbol='O',
         fract_x=0,
@@ -61,7 +61,7 @@ def test_single_fit_neutron_pd_cwl_lbco() -> None:
     # Set experiment
     data_path = download_data(id=3, destination=TEMP_DIR)
 
-    expt = ExperimentFactory.create(
+    expt = ExperimentFactory.from_data_path(
         name='hrpt',
         data_path=data_path,
     )
@@ -75,19 +75,18 @@ def test_single_fit_neutron_pd_cwl_lbco() -> None:
     expt.peak.broad_lorentz_x = 0
     expt.peak.broad_lorentz_y = 0
 
-    expt.linked_phases.add(id='lbco', scale=5.0)
+    expt.linked_phases.create(id='lbco', scale=5.0)
 
-    expt.background.add(id='1', x=10, y=170)
-    expt.background.add(id='2', x=165, y=170)
+    expt.background.create(id='1', x=10, y=170)
+    expt.background.create(id='2', x=165, y=170)
 
     # Create project
     project = Project()
-    project.sample_models.add(sample_model=model)
-    project.experiments.add(experiment=expt)
+    project.structures.add(model)
+    project.experiments.add(expt)
 
     # Prepare for fitting
-    project.analysis.current_calculator = 'cryspy'
-    project.analysis.current_minimizer = 'lmfit (leastsq)'
+    project.analysis.current_minimizer = 'lmfit'
 
     # ------------ 1st fitting ------------
 
@@ -147,8 +146,8 @@ 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 = SampleModelFactory.create(name='lbco')
+    # Set structure
+    model = StructureFactory.from_scratch(name='lbco')
 
     space_group = model.space_group
     space_group.name_h_m = 'P m -3 m'
@@ -157,7 +156,7 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None:
     cell.length_a = 3.8909
 
     atom_sites = model.atom_sites
-    atom_sites.add(
+    atom_sites.create(
         label='La',
         type_symbol='La',
         fract_x=0,
@@ -167,7 +166,7 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None:
         b_iso=1.0,
         occupancy=0.5,
     )
-    atom_sites.add(
+    atom_sites.create(
         label='Ba',
         type_symbol='Ba',
         fract_x=0,
@@ -177,7 +176,7 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None:
         b_iso=1.0,
         occupancy=0.5,
     )
-    atom_sites.add(
+    atom_sites.create(
         label='Co',
         type_symbol='Co',
         fract_x=0.5,
@@ -186,7 +185,7 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None:
         wyckoff_letter='b',
         b_iso=1.0,
     )
-    atom_sites.add(
+    atom_sites.create(
         label='O',
         type_symbol='O',
         fract_x=0,
@@ -199,7 +198,7 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None:
     # Set experiment
     data_path = download_data(id=3, destination=TEMP_DIR)
 
-    expt = ExperimentFactory.create(
+    expt = ExperimentFactory.from_data_path(
         name='hrpt',
         data_path=data_path,
     )
@@ -216,27 +215,26 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None:
     peak.broad_lorentz_y = 0.0797
 
     background = expt.background
-    background.add(id='10', x=10, y=174.3)
-    background.add(id='20', x=20, y=159.8)
-    background.add(id='30', x=30, y=167.9)
-    background.add(id='50', x=50, y=166.1)
-    background.add(id='70', x=70, y=172.3)
-    background.add(id='90', x=90, y=171.1)
-    background.add(id='110', x=110, y=172.4)
-    background.add(id='130', x=130, y=182.5)
-    background.add(id='150', x=150, y=173.0)
-    background.add(id='165', x=165, y=171.1)
-
-    expt.linked_phases.add(id='lbco', scale=9.0976)
+    background.create(id='10', x=10, y=174.3)
+    background.create(id='20', x=20, y=159.8)
+    background.create(id='30', x=30, y=167.9)
+    background.create(id='50', x=50, y=166.1)
+    background.create(id='70', x=70, y=172.3)
+    background.create(id='90', x=90, y=171.1)
+    background.create(id='110', x=110, y=172.4)
+    background.create(id='130', x=130, y=182.5)
+    background.create(id='150', x=150, y=173.0)
+    background.create(id='165', x=165, y=171.1)
+
+    expt.linked_phases.create(id='lbco', scale=9.0976)
 
     # Create project
     project = Project()
-    project.sample_models.add(sample_model=model)
-    project.experiments.add(experiment=expt)
+    project.structures.add(model)
+    project.experiments.add(expt)
 
     # Prepare for fitting
-    project.analysis.current_calculator = 'cryspy'
-    project.analysis.current_minimizer = 'lmfit (leastsq)'
+    project.analysis.current_minimizer = 'lmfit'
 
     # ------------ 1st fitting ------------
 
@@ -277,14 +275,26 @@ 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.create(
+        label='biso_La',
+        param_uid=atom_sites['La'].b_iso.uid,
+    )
+    project.analysis.aliases.create(
+        label='biso_Ba',
+        param_uid=atom_sites['Ba'].b_iso.uid,
+    )
+    project.analysis.aliases.create(
+        label='occ_La',
+        param_uid=atom_sites['La'].occupancy.uid,
+    )
+    project.analysis.aliases.create(
+        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.create(lhs_alias='biso_Ba', rhs_expr='biso_La')
+    project.analysis.constraints.create(lhs_alias='occ_Ba', rhs_expr='1 - occ_La')
 
     # Apply constraints
     project.analysis.apply_constraints()
@@ -309,13 +319,13 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None:
 
 
 def test_fit_neutron_pd_cwl_hs() -> None:
-    # Set sample model
-    model = SampleModelFactory.create(name='hs')
+    # Set structure
+    model = StructureFactory.from_scratch(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(
+    model.atom_sites.create(
         label='Zn',
         type_symbol='Zn',
         fract_x=0,
@@ -324,7 +334,7 @@ def test_fit_neutron_pd_cwl_hs() -> None:
         wyckoff_letter='b',
         b_iso=0.1,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='Cu',
         type_symbol='Cu',
         fract_x=0.5,
@@ -333,7 +343,7 @@ def test_fit_neutron_pd_cwl_hs() -> None:
         wyckoff_letter='e',
         b_iso=1.2,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='O',
         type_symbol='O',
         fract_x=0.206,
@@ -342,7 +352,7 @@ def test_fit_neutron_pd_cwl_hs() -> None:
         wyckoff_letter='h',
         b_iso=0.7,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='Cl',
         type_symbol='Cl',
         fract_x=0,
@@ -351,7 +361,7 @@ def test_fit_neutron_pd_cwl_hs() -> None:
         wyckoff_letter='c',
         b_iso=1.1,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='H',
         type_symbol='2H',
         fract_x=0.132,
@@ -364,7 +374,7 @@ def test_fit_neutron_pd_cwl_hs() -> None:
     # Set experiment
     data_path = download_data(id=11, destination=TEMP_DIR)
 
-    expt = ExperimentFactory.create(name='hrpt', data_path=data_path)
+    expt = ExperimentFactory.from_data_path(name='hrpt', data_path=data_path)
 
     expt.instrument.setup_wavelength = 1.89
     expt.instrument.calib_twotheta_offset = 0.0
@@ -375,26 +385,25 @@ def test_fit_neutron_pd_cwl_hs() -> None:
     expt.peak.broad_lorentz_x = 0.2927
     expt.peak.broad_lorentz_y = 0
 
-    expt.background.add(id='1', x=4.4196, y=648.413)
-    expt.background.add(id='2', x=6.6207, y=523.788)
-    expt.background.add(id='3', x=10.4918, y=454.938)
-    expt.background.add(id='4', x=15.4634, y=435.913)
-    expt.background.add(id='5', x=45.6041, y=472.972)
-    expt.background.add(id='6', x=74.6844, y=486.606)
-    expt.background.add(id='7', x=103.4187, y=472.409)
-    expt.background.add(id='8', x=121.6311, y=496.734)
-    expt.background.add(id='9', x=159.4116, y=473.146)
+    expt.background.create(id='1', x=4.4196, y=648.413)
+    expt.background.create(id='2', x=6.6207, y=523.788)
+    expt.background.create(id='3', x=10.4918, y=454.938)
+    expt.background.create(id='4', x=15.4634, y=435.913)
+    expt.background.create(id='5', x=45.6041, y=472.972)
+    expt.background.create(id='6', x=74.6844, y=486.606)
+    expt.background.create(id='7', x=103.4187, y=472.409)
+    expt.background.create(id='8', x=121.6311, y=496.734)
+    expt.background.create(id='9', x=159.4116, y=473.146)
 
-    expt.linked_phases.add(id='hs', scale=0.492)
+    expt.linked_phases.create(id='hs', scale=0.492)
 
     # Create project
     project = Project()
-    project.sample_models.add(sample_model=model)
-    project.experiments.add(experiment=expt)
+    project.structures.add(model)
+    project.experiments.add(expt)
 
     # Prepare for fitting
-    project.analysis.current_calculator = 'cryspy'
-    project.analysis.current_minimizer = 'lmfit (leastsq)'
+    project.analysis.current_minimizer = 'lmfit'
 
     # ------------ 1st fitting ------------
 
diff --git a/tests/integration/fitting/test_powder-diffraction_joint-fit.py b/tests/integration/fitting/test_powder-diffraction_joint-fit.py
index 904b2e1c..cc3e600e 100644
--- a/tests/integration/fitting/test_powder-diffraction_joint-fit.py
+++ b/tests/integration/fitting/test_powder-diffraction_joint-fit.py
@@ -8,7 +8,7 @@
 
 from easydiffraction import ExperimentFactory
 from easydiffraction import Project
-from easydiffraction import SampleModelFactory
+from easydiffraction import StructureFactory
 from easydiffraction import download_data
 
 TEMP_DIR = tempfile.gettempdir()
@@ -16,13 +16,13 @@
 
 @pytest.mark.fast
 def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None:
-    # Set sample model
-    model = SampleModelFactory.create(name='pbso4')
+    # Set structure
+    model = StructureFactory.from_scratch(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(
+    model.atom_sites.create(
         label='Pb',
         type_symbol='Pb',
         fract_x=0.1876,
@@ -31,7 +31,7 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None:
         wyckoff_letter='c',
         b_iso=1.37,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='S',
         type_symbol='S',
         fract_x=0.0654,
@@ -40,7 +40,7 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None:
         wyckoff_letter='c',
         b_iso=0.3777,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='O1',
         type_symbol='O',
         fract_x=0.9082,
@@ -49,7 +49,7 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None:
         wyckoff_letter='c',
         b_iso=1.9764,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='O2',
         type_symbol='O',
         fract_x=0.1935,
@@ -58,7 +58,7 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None:
         wyckoff_letter='c',
         b_iso=1.4456,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='O3',
         type_symbol='O',
         fract_x=0.0811,
@@ -70,7 +70,7 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None:
 
     # Set experiments
     data_path = download_data(id=14, destination=TEMP_DIR)
-    expt1 = ExperimentFactory.create(name='npd1', data_path=data_path)
+    expt1 = ExperimentFactory.from_data_path(name='npd1', data_path=data_path)
     expt1.instrument.setup_wavelength = 1.91
     expt1.instrument.calib_twotheta_offset = -0.1406
     expt1.peak.broad_gauss_u = 0.139
@@ -78,7 +78,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(id='pbso4', scale=1.46)
+    expt1.linked_phases.create(id='pbso4', scale=1.46)
     expt1.background_type = 'line-segment'
     for id, x, y in [
         ('1', 11.0, 206.1624),
@@ -90,10 +90,10 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None:
         ('7', 120.0, 244.4525),
         ('8', 153.0, 226.0595),
     ]:
-        expt1.background.add(id=id, x=x, y=y)
+        expt1.background.create(id=id, x=x, y=y)
 
     data_path = download_data(id=15, destination=TEMP_DIR)
-    expt2 = ExperimentFactory.create(name='npd2', data_path=data_path)
+    expt2 = ExperimentFactory.from_data_path(name='npd2', data_path=data_path)
     expt2.instrument.setup_wavelength = 1.91
     expt2.instrument.calib_twotheta_offset = -0.1406
     expt2.peak.broad_gauss_u = 0.139
@@ -101,7 +101,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(id='pbso4', scale=1.46)
+    expt2.linked_phases.create(id='pbso4', scale=1.46)
     expt2.background_type = 'line-segment'
     for id, x, y in [
         ('1', 11.0, 206.1624),
@@ -113,18 +113,17 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None:
         ('7', 120.0, 244.4525),
         ('8', 153.0, 226.0595),
     ]:
-        expt2.background.add(id=id, x=x, y=y)
+        expt2.background.create(id=id, x=x, y=y)
 
     # Create project
     project = Project()
-    project.sample_models.add(sample_model=model)
-    project.experiments.add(experiment=expt1)
-    project.experiments.add(experiment=expt2)
+    project.structures.add(model)
+    project.experiments.add(expt1)
+    project.experiments.add(expt2)
 
     # Prepare for fitting
-    project.analysis.current_calculator = 'cryspy'
-    project.analysis.current_minimizer = 'lmfit (leastsq)'
-    project.analysis.fit_mode = 'joint'
+    project.analysis.current_minimizer = 'lmfit'
+    project.analysis.fit_mode.mode = 'joint'
 
     # Select fitting parameters
     model.cell.length_a.free = True
@@ -144,13 +143,13 @@ 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 = SampleModelFactory.create(name='pbso4')
+    # Set structure
+    model = StructureFactory.from_scratch(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(
+    model.atom_sites.create(
         label='Pb',
         type_symbol='Pb',
         fract_x=0.1876,
@@ -159,7 +158,7 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None:
         wyckoff_letter='c',
         b_iso=1.37,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='S',
         type_symbol='S',
         fract_x=0.0654,
@@ -168,7 +167,7 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None:
         wyckoff_letter='c',
         b_iso=0.3777,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='O1',
         type_symbol='O',
         fract_x=0.9082,
@@ -177,7 +176,7 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None:
         wyckoff_letter='c',
         b_iso=1.9764,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='O2',
         type_symbol='O',
         fract_x=0.1935,
@@ -186,7 +185,7 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None:
         wyckoff_letter='c',
         b_iso=1.4456,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='O3',
         type_symbol='O',
         fract_x=0.0811,
@@ -198,7 +197,7 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None:
 
     # Set experiments
     data_path = download_data(id=13, destination=TEMP_DIR)
-    expt1 = ExperimentFactory.create(
+    expt1 = ExperimentFactory.from_data_path(
         name='npd',
         data_path=data_path,
         radiation_probe='neutron',
@@ -210,7 +209,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(id='pbso4', scale=1.5)
+    expt1.linked_phases.create(id='pbso4', scale=1.5)
     for id, x, y in [
         ('1', 11.0, 206.1624),
         ('2', 15.0, 194.75),
@@ -221,10 +220,10 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None:
         ('7', 120.0, 244.4525),
         ('8', 153.0, 226.0595),
     ]:
-        expt1.background.add(id=id, x=x, y=y)
+        expt1.background.create(id=id, x=x, y=y)
 
     data_path = download_data(id=16, destination=TEMP_DIR)
-    expt2 = ExperimentFactory.create(
+    expt2 = ExperimentFactory.from_data_path(
         name='xrd',
         data_path=data_path,
         radiation_probe='xray',
@@ -236,7 +235,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(id='pbso4', scale=0.001)
+    expt2.linked_phases.create(id='pbso4', scale=0.001)
     for id, x, y in [
         ('1', 11.0, 141.8516),
         ('2', 13.0, 102.8838),
@@ -247,17 +246,16 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None:
         ('7', 90.0, 113.7473),
         ('8', 110.0, 132.4643),
     ]:
-        expt2.background.add(id=id, x=x, y=y)
+        expt2.background.create(id=id, x=x, y=y)
 
     # Create project
     project = Project()
-    project.sample_models.add(sample_model=model)
-    project.experiments.add(experiment=expt1)
-    project.experiments.add(experiment=expt2)
+    project.structures.add(model)
+    project.experiments.add(expt1)
+    project.experiments.add(expt2)
 
     # Prepare for fitting
-    project.analysis.current_calculator = 'cryspy'
-    project.analysis.current_minimizer = 'lmfit (leastsq)'
+    project.analysis.current_minimizer = 'lmfit'
 
     # Select fitting parameters
     model.cell.length_a.free = True
@@ -269,7 +267,6 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None:
     # ------------ 1st fitting ------------
 
     # Perform fit
-    project.analysis.fit_mode = 'single'  # Default
     project.analysis.fit()
 
     # Compare fit quality
@@ -282,7 +279,7 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None:
     # ------------ 2nd fitting ------------
 
     # Perform fit
-    project.analysis.fit_mode = 'joint'
+    project.analysis.fit_mode.mode = 'joint'
     project.analysis.fit()
 
     # Compare fit quality
@@ -297,7 +294,6 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None:
     # Perform fit
     project.analysis.joint_fit_experiments['xrd'].weight = 0.5  # Default
     project.analysis.joint_fit_experiments['npd'].weight = 0.5  # Default
-    project.analysis.fit_mode = 'joint'
     project.analysis.fit()
 
     # Compare fit quality
@@ -312,7 +308,6 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None:
     # Perform fit
     project.analysis.joint_fit_experiments['xrd'].weight = 0.3
     project.analysis.joint_fit_experiments['npd'].weight = 0.7
-    project.analysis.fit_mode = 'joint'
     project.analysis.fit()
 
     # Compare fit quality
diff --git a/tests/integration/fitting/test_powder-diffraction_multiphase.py b/tests/integration/fitting/test_powder-diffraction_multiphase.py
deleted file mode 100644
index 2880eb2b..00000000
--- a/tests/integration/fitting/test_powder-diffraction_multiphase.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
-# SPDX-License-Identifier: BSD-3-Clause
-
-import tempfile
-
-from numpy.testing import assert_almost_equal
-
-from easydiffraction import ExperimentFactory
-from easydiffraction import Project
-from easydiffraction import SampleModelFactory
-from easydiffraction import download_data
-
-TEMP_DIR = tempfile.gettempdir()
-
-
-def test_single_fit_neutron_pd_tof_mcstas_lbco_si() -> None:
-    # Set sample models
-    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(
-        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(
-        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(
-        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(
-        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 = 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(
-        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_path = download_data(id=8, destination=TEMP_DIR)
-    expt = ExperimentFactory.create(
-        name='mcstas',
-        data_path=data_path,
-        beam_mode='time-of-flight',
-    )
-    expt.instrument.setup_twotheta_bank = 94.90931761529106
-    expt.instrument.calib_d_to_tof_offset = 0.0
-    expt.instrument.calib_d_to_tof_linear = 58724.76869981215
-    expt.instrument.calib_d_to_tof_quad = -0.00001
-    expt.peak_profile_type = 'pseudo-voigt * ikeda-carpenter'
-    expt.peak.broad_gauss_sigma_0 = 45137
-    expt.peak.broad_gauss_sigma_1 = -52394
-    expt.peak.broad_gauss_sigma_2 = 22998
-    expt.peak.broad_mix_beta_0 = 0.0055
-    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(id='lbco', scale=4.0)
-    expt.linked_phases.add(id='si', scale=0.2)
-    for x in range(45000, 115000, 5000):
-        expt.background.add(id=str(x), x=x, y=0.2)
-
-    # Create project
-    project = Project()
-    project.sample_models.add(sample_model=model_1)
-    project.sample_models.add(sample_model=model_2)
-    project.experiments.add(experiment=expt)
-
-    # Exclude regions from fitting
-    project.experiments['mcstas'].excluded_regions.add(start=108000, end=200000)
-
-    # Prepare for fitting
-    project.analysis.current_calculator = 'cryspy'
-    project.analysis.current_minimizer = 'lmfit (leastsq)'
-
-    # Select fitting parameters
-    model_1.cell.length_a.free = True
-    model_1.atom_sites['La'].b_iso.free = True
-    model_1.atom_sites['Ba'].b_iso.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.atom_sites['Si'].b_iso.free = True
-    expt.linked_phases['lbco'].scale.free = True
-    expt.linked_phases['si'].scale.free = True
-    expt.peak.broad_gauss_sigma_0.free = True
-    expt.peak.broad_gauss_sigma_1.free = True
-    expt.peak.broad_gauss_sigma_2.free = True
-    expt.peak.asym_alpha_1.free = True
-    expt.peak.broad_mix_beta_0.free = True
-    expt.peak.broad_mix_beta_1.free = True
-    for point in expt.background:
-        point.y.free = True
-
-    # Perform fit
-    project.analysis.fit()
-
-    # Compare fit quality
-    assert_almost_equal(
-        project.analysis.fit_results.reduced_chi_square,
-        desired=2.87,
-        decimal=1,
-    )
-
-
-if __name__ == '__main__':
-    test_single_fit_neutron_pd_tof_mcstas_lbco_si()
diff --git a/tests/integration/fitting/test_powder-diffraction_time-of-flight.py b/tests/integration/fitting/test_powder-diffraction_time-of-flight.py
index 0117a90b..ae702b4d 100644
--- a/tests/integration/fitting/test_powder-diffraction_time-of-flight.py
+++ b/tests/integration/fitting/test_powder-diffraction_time-of-flight.py
@@ -7,19 +7,19 @@
 
 from easydiffraction import ExperimentFactory
 from easydiffraction import Project
-from easydiffraction import SampleModelFactory
+from easydiffraction import StructureFactory
 from easydiffraction import download_data
 
 TEMP_DIR = tempfile.gettempdir()
 
 
 def test_single_fit_neutron_pd_tof_si() -> None:
-    # Set sample model
-    model = SampleModelFactory.create(name='si')
+    # Set structure
+    model = StructureFactory.from_scratch(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(
+    model.atom_sites.create(
         label='Si',
         type_symbol='Si',
         fract_x=0.125,
@@ -31,7 +31,7 @@ def test_single_fit_neutron_pd_tof_si() -> None:
 
     # Set experiment
     data_path = download_data(id=7, destination=TEMP_DIR)
-    expt = ExperimentFactory.create(
+    expt = ExperimentFactory.from_data_path(
         name='sepd',
         data_path=data_path,
         beam_mode='time-of-flight',
@@ -48,18 +48,17 @@ 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(id='si', scale=14.92)
+    expt.linked_phases.create(id='si', scale=14.92)
     for x in range(0, 35000, 5000):
-        expt.background.add(id=str(x), x=x, y=200)
+        expt.background.create(id=str(x), x=x, y=200)
 
     # Create project
     project = Project()
-    project.sample_models.add(sample_model=model)
-    project.experiments.add(experiment=expt)
+    project.structures.add(model)
+    project.experiments.add(expt)
 
     # Prepare for fitting
-    project.analysis.current_calculator = 'cryspy'
-    project.analysis.current_minimizer = 'lmfit (leastsq)'
+    project.analysis.current_minimizer = 'lmfit'
 
     # Select fitting parameters
     model.cell.length_a.free = True
@@ -81,12 +80,12 @@ def test_single_fit_neutron_pd_tof_si() -> None:
 
 
 def test_single_fit_neutron_pd_tof_ncaf() -> None:
-    # Set sample model
-    model = SampleModelFactory.create(name='ncaf')
+    # Set structure
+    model = StructureFactory.from_scratch(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(
+    model.atom_sites.create(
         label='Ca',
         type_symbol='Ca',
         fract_x=0.4661,
@@ -95,7 +94,7 @@ def test_single_fit_neutron_pd_tof_ncaf() -> None:
         wyckoff_letter='b',
         b_iso=0.9,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='Al',
         type_symbol='Al',
         fract_x=0.25171,
@@ -104,7 +103,7 @@ def test_single_fit_neutron_pd_tof_ncaf() -> None:
         wyckoff_letter='a',
         b_iso=0.66,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='Na',
         type_symbol='Na',
         fract_x=0.08481,
@@ -113,7 +112,7 @@ def test_single_fit_neutron_pd_tof_ncaf() -> None:
         wyckoff_letter='a',
         b_iso=1.9,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='F1',
         type_symbol='F',
         fract_x=0.1375,
@@ -122,7 +121,7 @@ def test_single_fit_neutron_pd_tof_ncaf() -> None:
         wyckoff_letter='c',
         b_iso=0.9,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='F2',
         type_symbol='F',
         fract_x=0.3626,
@@ -131,7 +130,7 @@ def test_single_fit_neutron_pd_tof_ncaf() -> None:
         wyckoff_letter='c',
         b_iso=1.28,
     )
-    model.atom_sites.add(
+    model.atom_sites.create(
         label='F3',
         type_symbol='F',
         fract_x=0.4612,
@@ -143,13 +142,13 @@ def test_single_fit_neutron_pd_tof_ncaf() -> None:
 
     # Set experiment
     data_path = download_data(id=9, destination=TEMP_DIR)
-    expt = ExperimentFactory.create(
+    expt = ExperimentFactory.from_data_path(
         name='wish',
         data_path=data_path,
         beam_mode='time-of-flight',
     )
-    expt.excluded_regions.add(id='1', start=0, end=9000)
-    expt.excluded_regions.add(id='2', start=100010, end=200000)
+    expt.excluded_regions.create(id='1', start=0, end=9000)
+    expt.excluded_regions.create(id='2', start=100010, end=200000)
     expt.instrument.setup_twotheta_bank = 152.827
     expt.instrument.calib_d_to_tof_offset = -13.7123
     expt.instrument.calib_d_to_tof_linear = 20773.1
@@ -162,7 +161,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(id='ncaf', scale=1.0928)
+    expt.linked_phases.create(id='ncaf', scale=1.0928)
     for x, y in [
         (9162, 465),
         (11136, 593),
@@ -193,16 +192,15 @@ def test_single_fit_neutron_pd_tof_ncaf() -> None:
         (91958, 268),
         (102712, 262),
     ]:
-        expt.background.add(id=str(x), x=x, y=y)
+        expt.background.create(id=str(x), x=x, y=y)
 
     # Create project
     project = Project()
-    project.sample_models.add(sample_model=model)
-    project.experiments.add(experiment=expt)
+    project.structures.add(model)
+    project.experiments.add(expt)
 
     # Prepare for fitting
-    project.analysis.current_calculator = 'cryspy'
-    project.analysis.current_minimizer = 'lmfit (leastsq)'
+    project.analysis.current_minimizer = 'lmfit'
 
     # Select fitting parameters
     expt.linked_phases['ncaf'].scale.free = True
diff --git a/tests/integration/fitting/test_single-crystal-diffraction.py b/tests/integration/fitting/test_single-crystal-diffraction.py
index da7da7eb..af915d92 100644
--- a/tests/integration/fitting/test_single-crystal-diffraction.py
+++ b/tests/integration/fitting/test_single-crystal-diffraction.py
@@ -14,13 +14,13 @@
 def test_single_fit_neut_sc_cwl_tbti() -> None:
     project = ed.Project()
 
-    # Set sample model
+    # Set structure
     model_path = ed.download_data(id=20, destination=TEMP_DIR)
-    project.sample_models.add(cif_path=model_path)
+    project.structures.add_from_cif_path(model_path)
 
     # Set experiment
     data_path = ed.download_data(id=19, destination=TEMP_DIR)
-    project.experiments.add(
+    project.experiments.add_from_data_path(
         name='heidi',
         data_path=data_path,
         sample_form='single crystal',
@@ -36,7 +36,7 @@ def test_single_fit_neut_sc_cwl_tbti() -> None:
     experiment.extinction.radius = 27
 
     # Select fitting parameters (experiment only)
-    # Sample model parameters are selected in the loaded CIF file
+    # Structure parameters are selected in the loaded CIF file
     experiment.linked_crystal.scale.free = True
     experiment.extinction.radius.free = True
 
@@ -52,13 +52,13 @@ def test_single_fit_neut_sc_cwl_tbti() -> None:
 def test_single_fit_neut_sc_tof_taurine() -> None:
     project = ed.Project()
 
-    # Set sample model
+    # Set structure
     model_path = ed.download_data(id=21, destination=TEMP_DIR)
-    project.sample_models.add(cif_path=model_path)
+    project.structures.add_from_cif_path(model_path)
 
     # Set experiment
     data_path = ed.download_data(id=22, destination=TEMP_DIR)
-    project.experiments.add(
+    project.experiments.add_from_data_path(
         name='senju',
         data_path=data_path,
         sample_form='single crystal',
@@ -73,7 +73,7 @@ def test_single_fit_neut_sc_tof_taurine() -> None:
     experiment.extinction.radius = 2.0
 
     # Select fitting parameters (experiment only)
-    # Sample model parameters are selected in the loaded CIF file
+    # Structure parameters are selected in the loaded CIF file
     experiment.linked_crystal.scale.free = True
     experiment.extinction.radius.free = True
 
diff --git a/tests/integration/scipp-analysis/dream/test_analyze_reduced_data.py b/tests/integration/scipp-analysis/dream/test_analyze_reduced_data.py
index eb528ff5..cde09c8a 100644
--- a/tests/integration/scipp-analysis/dream/test_analyze_reduced_data.py
+++ b/tests/integration/scipp-analysis/dream/test_analyze_reduced_data.py
@@ -4,7 +4,7 @@
 
 These tests verify the complete workflow:
 1. Define project
-2. Add sample model manually defined
+2. Add structure manually defined
 3. Modify experiment CIF file
 4. Add experiment from modified CIF file
 5. Modify default experiment configuration
@@ -56,11 +56,11 @@ def prepared_cif_path(
 def project_with_data(
     prepared_cif_path: str,
 ) -> ed.Project:
-    """Create project with sample model, experiment data, and
+    """Create project with structure, experiment data, and
     configuration.
 
     1. Define project
-    2. Add sample model manually defined
+    2. Add structure manually defined
     3. Modify experiment CIF file
     4. Add experiment from modified CIF file
     5. Modify default experiment configuration
@@ -68,16 +68,16 @@ def project_with_data(
     # Step 1: Define Project
     project = ed.Project()
 
-    # Step 2: Define Sample Model manually
-    project.sample_models.add(name='si')
-    sample_model = project.sample_models['si']
+    # Step 2: Define Structure manually
+    project.structures.create(name='si')
+    structure = project.structures['si']
 
-    sample_model.space_group.name_h_m = 'F d -3 m'
-    sample_model.space_group.it_coordinate_system_code = '1'
+    structure.space_group.name_h_m = 'F d -3 m'
+    structure.space_group.it_coordinate_system_code = '1'
 
-    sample_model.cell.length_a = 5.43146
+    structure.cell.length_a = 5.43146
 
-    sample_model.atom_sites.add(
+    structure.atom_sites.create(
         label='Si',
         type_symbol='Si',
         fract_x=0.125,
@@ -88,12 +88,12 @@ def project_with_data(
     )
 
     # Step 3: Add experiment from modified CIF file
-    project.experiments.add(cif_path=prepared_cif_path)
+    project.experiments.add_from_cif_path(prepared_cif_path)
     experiment = project.experiments['reduced_tof']
 
     # Step 4: Configure experiment
     # Link phase
-    experiment.linked_phases.add(id='si', scale=0.8)
+    experiment.linked_phases.create(id='si', scale=0.8)
 
     # Instrument setup
     experiment.instrument.setup_twotheta_bank = 90.0
@@ -109,8 +109,8 @@ def project_with_data(
     experiment.peak.asym_alpha_1 = 0.26
 
     # Excluded regions
-    experiment.excluded_regions.add(id='1', start=0, end=10000)
-    experiment.excluded_regions.add(id='2', start=70000, end=200000)
+    experiment.excluded_regions.create(id='1', start=0, end=10000)
+    experiment.excluded_regions.create(id='2', start=70000, end=200000)
 
     # Background points
     background_points = [
@@ -124,7 +124,7 @@ def project_with_data(
         ('9', 70000, 0.6),
     ]
     for id_, x, y in background_points:
-        experiment.background.add(id=id_, x=x, y=y)
+        experiment.background.create(id=id_, x=x, y=y)
 
     return project
 
@@ -139,12 +139,12 @@ def fitted_project(
     7. Do fitting
     """
     project = project_with_data
-    sample_model = project.sample_models['si']
+    structure = project.structures['si']
     experiment = project.experiments['reduced_tof']
 
     # Step 5: Select parameters to be fitted
-    # Set free parameters for sample model
-    sample_model.atom_sites['Si'].b_iso.free = True
+    # Set free parameters for structure
+    structure.atom_sites['Si'].b_iso.free = True
 
     # Set free parameters for experiment
     experiment.linked_phases['si'].scale.free = True
diff --git a/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py b/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py
index 866d4008..e35d1bd5 100644
--- a/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py
+++ b/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py
@@ -17,7 +17,7 @@ def test_crysfml_engine_flag_and_structure_factors_raises():
     # 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)
+        calc.calculate_structure_factors(structures=None, experiments=None)
 
 
 def test_crysfml_adjust_pattern_length_truncates():
diff --git a/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py b/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py
index 9289c15e..bccd63ec 100644
--- a/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py
+++ b/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py
@@ -21,5 +21,5 @@ class DummySample:
         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'
+    # _convert_structure_to_cryspy_cif returns input as_cif
+    assert calc._convert_structure_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
index 7cf9fc25..7df08b97 100644
--- a/tests/unit/easydiffraction/analysis/calculators/test_factory.py
+++ b/tests/unit/easydiffraction/analysis/calculators/test_factory.py
@@ -1,41 +1,22 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
 
-def test_list_and_show_supported_calculators_do_not_crash(capsys, monkeypatch):
+import pytest
+
+
+def test_supported_tags_and_show_supported(capsys):
     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()
+    tags = CalculatorFactory.supported_tags()
+    assert isinstance(tags, list)
+
+    CalculatorFactory.show_supported()
     out = capsys.readouterr().out
-    # Should print the paragraph title
-    assert 'Supported calculators' in out
+    assert 'Supported types' in out
 
 
-def test_create_calculator_unknown_returns_none(capsys):
+def test_create_unknown_raises():
     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
+    with pytest.raises(ValueError):
+        CalculatorFactory.create('this_is_unknown')
diff --git a/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py b/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py
index e7662d43..268d0d84 100644
--- a/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py
+++ b/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py
@@ -16,7 +16,7 @@ def test_pdffit_engine_flag_and_hkl_message(capsys):
     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)
+    out = calc.calculate_structure_factors(structures=None, experiments=None)
     assert out == []
     # The method prints a note
     printed = capsys.readouterr().out
@@ -53,7 +53,7 @@ def __init__(self):
             self.type = type('T', (), {'radiation_probe': type('P', (), {'value': 'neutron'})()})()
             self.linked_phases = DummyLinkedPhases()
 
-    class DummySampleModel:
+    class DummyStructure:
         name = 'PhaseA'
 
         @property
@@ -93,6 +93,6 @@ def parse(self, text):
 
     calc = PdffitCalculator()
     pattern = calc.calculate_pattern(
-        DummySampleModel(), DummyExperiment(), called_by_minimizer=False
+        DummyStructure(), 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
index 73c7b9ab..4860961d 100644
--- a/tests/unit/easydiffraction/analysis/categories/test_aliases.py
+++ b/tests/unit/easydiffraction/analysis/categories/test_aliases.py
@@ -6,10 +6,12 @@
 
 
 def test_alias_creation_and_collection():
-    a = Alias(label='x', param_uid='p1')
+    a = Alias()
+    a.label='x'
+    a.param_uid='p1'
     assert a.label.value == 'x'
     coll = Aliases()
-    coll.add(label='x', param_uid='p1')
+    coll.create(label='x', param_uid='p1')
     # 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
index 542a9f52..7d4acb0a 100644
--- a/tests/unit/easydiffraction/analysis/categories/test_constraints.py
+++ b/tests/unit/easydiffraction/analysis/categories/test_constraints.py
@@ -6,9 +6,11 @@
 
 
 def test_constraint_creation_and_collection():
-    c = Constraint(lhs_alias='a', rhs_expr='b + c')
+    c = Constraint()
+    c.lhs_alias='a'
+    c.rhs_expr='b + c'
     assert c.lhs_alias.value == 'a'
     coll = Constraints()
-    coll.add(lhs_alias='a', rhs_expr='b + c')
+    coll.create(lhs_alias='a', rhs_expr='b + 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
index 6a134ea0..c4194c35 100644
--- a/tests/unit/easydiffraction/analysis/categories/test_joint_fit_experiments.py
+++ b/tests/unit/easydiffraction/analysis/categories/test_joint_fit_experiments.py
@@ -6,10 +6,12 @@
 
 
 def test_joint_fit_experiment_and_collection():
-    j = JointFitExperiment(id='ex1', weight=0.5)
+    j = JointFitExperiment()
+    j.id='ex1'
+    j.weight=0.5
     assert j.id.value == 'ex1'
     assert j.weight.value == 0.5
     coll = JointFitExperiments()
-    coll.add(id='ex1', weight=0.5)
+    coll.create(id='ex1', weight=0.5)
     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
index 540c61c7..7c8b75c9 100644
--- a/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py
+++ b/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py
@@ -46,9 +46,9 @@ class Expts(dict):
         def values(self):
             return [Expt()]
 
-    class SampleModels(dict):
+    class DummyStructures(dict):
         pass
 
-    y_obs, y_calc, y_err = M.get_reliability_inputs(SampleModels(), Expts())
+    y_obs, y_calc, y_err = M.get_reliability_inputs(DummyStructures(), Expts())
     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/minimizers/test_base.py b/tests/unit/easydiffraction/analysis/minimizers/test_base.py
index dca36ee1..5d04a75a 100644
--- a/tests/unit/easydiffraction/analysis/minimizers/test_base.py
+++ b/tests/unit/easydiffraction/analysis/minimizers/test_base.py
@@ -49,7 +49,7 @@ def _check_success(self, raw_result):
 
         # Provide residuals implementation used by _objective_function
         def _compute_residuals(
-            self, engine_params, parameters, sample_models, experiments, calculator
+            self, engine_params, parameters, structures, experiments, calculator
         ):
             # Minimal residuals; verify engine params passed through
             assert engine_params == {'ok': True}
@@ -62,7 +62,7 @@ def _compute_residuals(
     # Wrap minimizer's objective creator to simulate higher-level usage
     objective = minim._create_objective_function(
         parameters=params,
-        sample_models=None,
+        structures=None,
         experiments=None,
         calculator=None,
     )
@@ -94,14 +94,14 @@ def _check_success(self, raw_result):
             return True
 
         def _compute_residuals(
-            self, engine_params, parameters, sample_models, experiments, calculator
+            self, engine_params, parameters, structures, 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
+        parameters=[], structures=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
index f0a9ea9d..88e22dee 100644
--- a/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py
+++ b/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py
@@ -15,11 +15,22 @@ def test_dfols_prepare_run_and_sync(monkeypatch):
 
     class P:
         def __init__(self, v, lo=-np.inf, hi=np.inf):
-            self.value = v
+            self._value = v
             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
+
+        def _set_value_from_minimizer(self, v):
+            self._value = v
+
     class FakeRes:
         EXIT_SUCCESS = 0
 
diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_factory.py b/tests/unit/easydiffraction/analysis/minimizers/test_factory.py
index 940143a6..236d1656 100644
--- a/tests/unit/easydiffraction/analysis/minimizers/test_factory.py
+++ b/tests/unit/easydiffraction/analysis/minimizers/test_factory.py
@@ -4,34 +4,38 @@
 def test_minimizer_factory_list_and_show(capsys):
     from easydiffraction.analysis.minimizers.factory import MinimizerFactory
 
-    lst = MinimizerFactory.list_available_minimizers()
+    lst = MinimizerFactory.supported_tags()
     assert isinstance(lst, list) and len(lst) >= 1
-    MinimizerFactory.show_available_minimizers()
+    MinimizerFactory.show_supported()
     out = capsys.readouterr().out
-    assert 'Supported minimizers' in out
+    assert 'Supported types' in out
 
 
 def test_minimizer_factory_unknown_raises():
     from easydiffraction.analysis.minimizers.factory import MinimizerFactory
 
     try:
-        MinimizerFactory.create_minimizer('___unknown___')
+        MinimizerFactory.create('___unknown___')
     except ValueError as e:
-        assert 'Unknown minimizer' in str(e)
+        assert 'Unsupported type' in str(e)
     else:
         assert False, 'Expected ValueError'
 
 
-def test_minimizer_factory_create_known_and_register(monkeypatch):
+def test_minimizer_factory_create_known_and_register():
     from easydiffraction.analysis.minimizers.base import MinimizerBase
     from easydiffraction.analysis.minimizers.factory import MinimizerFactory
+    from easydiffraction.core.metadata import TypeInfo
 
-    # Create a known minimizer instance (lmfit (leastsq) exists)
-    m = MinimizerFactory.create_minimizer('lmfit (leastsq)')
+    # Create a known minimizer instance (lmfit exists)
+    m = MinimizerFactory.create('lmfit')
     assert isinstance(m, MinimizerBase)
 
     # Register a custom minimizer and create it
+    @MinimizerFactory.register
     class Custom(MinimizerBase):
+        type_info = TypeInfo(tag='custom-test', description='x')
+
         def _prepare_solver_args(self, parameters):
             return {}
 
@@ -44,8 +48,5 @@ def _sync_result_to_parameters(self, raw_result, parameters):
         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')
+    created = MinimizerFactory.create('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
index c2385ee4..77b694ee 100644
--- a/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py
+++ b/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py
@@ -32,6 +32,9 @@ def value(self):
         def value(self, v):
             self._value = v
 
+        def _set_value_from_minimizer(self, v):
+            self._value = v
+
     # Fake lmfit.Parameters and result structure
     class FakeParam:
         def __init__(self, value, stderr=None):
diff --git a/tests/unit/easydiffraction/analysis/test_analysis.py b/tests/unit/easydiffraction/analysis/test_analysis.py
index 4a16d206..56d493aa 100644
--- a/tests/unit/easydiffraction/analysis/test_analysis.py
+++ b/tests/unit/easydiffraction/analysis/test_analysis.py
@@ -20,68 +20,97 @@ def names(self):
 
     class P:
         experiments = ExpCol(names)
-        sample_models = object()
+        structures = object()
         _varname = 'proj'
 
     return P()
 
 
-def test_show_current_calculator_and_minimizer_prints(capsys):
+def test_show_current_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
+    assert 'lmfit' in out
 
 
-def test_current_calculator_setter_success_and_unknown(monkeypatch, capsys):
-    from easydiffraction.analysis import calculators as calc_pkg
+
+def test_fit_mode_category_and_joint_fit_experiments(monkeypatch, capsys):
+    from easydiffraction.analysis.analysis import Analysis
+
+    a = Analysis(project=_make_project_with_names(['e1', 'e2']))
+
+    # Default fit mode is 'single'
+    assert a.fit_mode.mode.value == 'single'
+
+    # Switch to joint
+    a.fit_mode.mode = 'joint'
+    assert a.fit_mode.mode.value == 'joint'
+
+    # joint_fit_experiments exists but is empty until fit() populates it
+    assert len(a.joint_fit_experiments) == 0
+
+
+def test_fit_mode_type_getter(capsys):
     from easydiffraction.analysis.analysis import Analysis
 
     a = Analysis(project=_make_project_with_names([]))
+    assert a.fit_mode_type == 'default'
+
+
+def test_show_supported_fit_mode_types(capsys):
+    from easydiffraction.analysis.analysis import Analysis
+
+    a = Analysis(project=_make_project_with_names([]))
+    a.show_supported_fit_mode_types()
+    out = capsys.readouterr().out
+    assert 'default' in out
 
-    # Success path
-    monkeypatch.setattr(
-        calc_pkg.factory.CalculatorFactory,
-        'create_calculator',
-        lambda name: object(),
-    )
-    a.current_calculator = 'pdffit'
+
+def test_show_current_fit_mode_type(capsys):
+    from easydiffraction.analysis.analysis import Analysis
+
+    a = Analysis(project=_make_project_with_names([]))
+    a.show_current_fit_mode_type()
     out = capsys.readouterr().out
-    assert 'Current calculator changed to' in out
-    assert a.current_calculator == 'pdffit'
+    assert 'Current fit-mode type' in out
+    assert 'default' in out
 
-    # 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_mode_type_setter_valid(capsys):
+    from easydiffraction.analysis.analysis import Analysis
+
+    a = Analysis(project=_make_project_with_names([]))
+    a.fit_mode_type = 'default'
+    assert a.fit_mode_type == 'default'
 
 
-def test_fit_modes_show_and_switch_to_joint(monkeypatch, capsys):
+def test_fit_mode_type_setter_invalid(capsys):
     from easydiffraction.analysis.analysis import Analysis
 
-    a = Analysis(project=_make_project_with_names(['e1', 'e2']))
+    a = Analysis(project=_make_project_with_names([]))
+    a.fit_mode_type = 'nonexistent'
+    out = capsys.readouterr().out
+    assert 'Unsupported' in out
+    # Type should remain unchanged
+    assert a.fit_mode_type == 'default'
 
-    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'
+def test_analysis_help(capsys):
+    from easydiffraction.analysis.analysis import Analysis
+
+    a = Analysis(project=_make_project_with_names([]))
+    a.help()
+    out = capsys.readouterr().out
+    assert "Help for 'Analysis'" in out
+    assert 'fit_mode' in out
+    assert 'current_minimizer' in out
+    assert 'Properties' in out
+    assert 'Methods' in out
+    assert 'fit()' in out
+    assert 'show_fit_results()' in out
 
 
 def test_show_fit_results_warns_when_no_results(capsys):
@@ -105,13 +134,13 @@ def test_show_fit_results_calls_process_fit_results(monkeypatch):
     # Track if _process_fit_results was called
     process_called = {'called': False, 'args': None}
 
-    def mock_process_fit_results(sample_models, experiments):
+    def mock_process_fit_results(structures, experiments):
         process_called['called'] = True
-        process_called['args'] = (sample_models, experiments)
+        process_called['args'] = (structures, experiments)
 
-    # Create a mock project with sample_models and experiments
+    # Create a mock project with structures and experiments
     class MockProject:
-        sample_models = object()
+        structures = object()
         experiments = object()
         _varname = 'proj'
 
@@ -121,7 +150,7 @@ class experiments_cls:
         experiments = experiments_cls()
 
     project = MockProject()
-    project.sample_models = object()
+    project.structures = object()
     project.experiments.names = []
 
     a = Analysis(project=project)
diff --git a/tests/unit/easydiffraction/analysis/test_analysis_access_params.py b/tests/unit/easydiffraction/analysis/test_analysis_access_params.py
index df8827ea..96bf6ca1 100644
--- a/tests/unit/easydiffraction/analysis/test_analysis_access_params.py
+++ b/tests/unit/easydiffraction/analysis/test_analysis_access_params.py
@@ -3,9 +3,8 @@
 
 def test_how_to_access_parameters_prints_paths_and_uids(capsys, monkeypatch):
     from easydiffraction.analysis.analysis import Analysis
-    from easydiffraction.core.parameters import Parameter
+    from easydiffraction.core.variable import Parameter
     from easydiffraction.core.validation import AttributeSpec
-    from easydiffraction.core.validation import DataTypes
     from easydiffraction.io.cif.handler import CifHandler
     import easydiffraction.analysis.analysis as analysis_mod
 
@@ -13,9 +12,10 @@ def test_how_to_access_parameters_prints_paths_and_uids(capsys, monkeypatch):
     def make_param(db, cat, entry, name, val):
         p = Parameter(
             name=name,
-            value_spec=AttributeSpec(value=val, type_=DataTypes.NUMERIC, default=0.0),
+            value_spec=AttributeSpec(default=0.0),
             cif_handler=CifHandler(names=[f'_{cat}.{name}']),
         )
+        p.value = val
         # Inject identity metadata (avoid parent chain)
         p._identity.datablock_entry_name = lambda: db
         p._identity.category_code = cat
@@ -36,7 +36,7 @@ class Project:
         _varname = 'proj'
 
         def __init__(self):
-            self.sample_models = Coll([p1])
+            self.structures = Coll([p1])
             self.experiments = Coll([p2])
 
     # Capture the table payload by monkeypatching render_table to avoid
@@ -63,7 +63,7 @@ def fake_render_table(**kwargs):
     flat_rows = [' '.join(map(str, row)) for row in data]
 
     # Python access paths
-    assert any("proj.sample_models['db1'].catA.alpha" in r for r in flat_rows)
+    assert any("proj.structures['db1'].catA.alpha" in r for r in flat_rows)
     assert any("proj.experiments['db2'].catB['row1'].beta" in r for r in flat_rows)
 
     # Now check CIF unique identifiers via the new API
diff --git a/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py b/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py
index 8503398c..921127ba 100644
--- a/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py
+++ b/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py
@@ -18,7 +18,7 @@ def free_parameters(self):
             return []
 
     class P:
-        sample_models = Empty()
+        structures = Empty()
         experiments = Empty()
         _varname = 'proj'
 
diff --git a/tests/unit/easydiffraction/analysis/test_fitting.py b/tests/unit/easydiffraction/analysis/test_fitting.py
index 990cabaf..d3a1b1fb 100644
--- a/tests/unit/easydiffraction/analysis/test_fitting.py
+++ b/tests/unit/easydiffraction/analysis/test_fitting.py
@@ -31,7 +31,7 @@ def fit(self, params, obj):
     f = Fitter()
     # Avoid creating a real minimizer
     f.minimizer = DummyMin()
-    f.fit(sample_models=DummyCollection(), experiments=DummyCollection())
+    f.fit(structures=DummyCollection(), experiments=DummyCollection())
     out = capsys.readouterr().out
     assert 'No parameters selected for fitting' in out
 
@@ -83,7 +83,7 @@ def mock_process(*args, **kwargs):
 
     monkeypatch.setattr(f, '_process_fit_results', mock_process)
 
-    f.fit(sample_models=DummyCollection(), experiments=DummyCollection())
+    f.fit(structures=DummyCollection(), experiments=DummyCollection())
 
     assert not process_called['called'], (
         'Fitter.fit() should not call _process_fit_results automatically. '
diff --git a/tests/unit/easydiffraction/core/test_category.py b/tests/unit/easydiffraction/core/test_category.py
index 6143c479..c53fd12c 100644
--- a/tests/unit/easydiffraction/core/test_category.py
+++ b/tests/unit/easydiffraction/core/test_category.py
@@ -3,24 +3,22 @@
 
 from easydiffraction.core.category import CategoryCollection
 from easydiffraction.core.category import CategoryItem
-from easydiffraction.core.parameters import StringDescriptor
+from easydiffraction.core.variable 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):
+    def __init__(self):
         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=''),
+                value_spec=AttributeSpec(default='_'),
                 cif_handler=CifHandler(names=['_simple.a']),
             ),
         )
@@ -30,19 +28,28 @@ def __init__(self, entry_name):
             StringDescriptor(
                 name='b',
                 description='',
-                value_spec=AttributeSpec(value='y', type_=DataTypes.STRING, default=''),
+                value_spec=AttributeSpec(default='_'),
                 cif_handler=CifHandler(names=['_simple.b']),
             ),
         )
+        self._identity.category_entry_name = lambda: str(self._a.value)
 
     @property
     def a(self):
         return self._a
 
+    @a.setter
+    def a(self, value):
+        self._a.value = value
+
     @property
     def b(self):
         return self._b
 
+    @b.setter
+    def b(self, value):
+        self._b.value = value
+
 
 class SimpleCollection(CategoryCollection):
     def __init__(self):
@@ -50,7 +57,8 @@ def __init__(self):
 
 
 def test_category_item_str_and_properties():
-    it = SimpleItem('name1')
+    it = SimpleItem()
+    it.a = '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'
@@ -59,9 +67,35 @@ def test_category_item_str_and_properties():
 
 def test_category_collection_str_and_cif_calls():
     c = SimpleCollection()
-    c.add('n1')
-    c.add('n2')
+    c.create(a='n1')
+    c.create(a='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)
+
+
+def test_category_item_help(capsys):
+    it = SimpleItem()
+    it.a = 'name1'
+    it.help()
+    out = capsys.readouterr().out
+    assert 'Help for' in out
+    assert 'Parameters' in out
+    assert 'string' in out  # Type column
+    assert '✓' in out  # a and b are writable
+    assert 'Methods' in out
+
+
+def test_category_collection_help(capsys):
+    c = SimpleCollection()
+    c.create(a='n1')
+    c.create(a='n2')
+    c.help()
+    out = capsys.readouterr().out
+    assert 'Help for' in out
+    assert 'Items (2)' in out
+    assert 'n1' in out
+    assert 'n2' in out
+
+
diff --git a/tests/unit/easydiffraction/core/test_collection.py b/tests/unit/easydiffraction/core/test_collection.py
index 9d89afea..616b232f 100644
--- a/tests/unit/easydiffraction/core/test_collection.py
+++ b/tests/unit/easydiffraction/core/test_collection.py
@@ -29,3 +29,94 @@ def as_cif(self) -> str:
     assert c['a'] is a2 and len(list(c.keys())) == 2
     del c['b']
     assert list(c.names) == ['a']
+
+
+def test_collection_contains():
+    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)
+    c['x'] = Item('x')
+    assert 'x' in c
+    assert 'y' not in c
+
+
+def test_collection_remove():
+    from easydiffraction.core.collection import CollectionBase
+    from easydiffraction.core.identity import Identity
+
+    import pytest
+
+    class Item:
+        def __init__(self, name):
+            self._identity = Identity(owner=self, category_entry=lambda: name)
+
+    class MyCollection(CollectionBase):
+        @property
+        def parameters(self):
+            return []
+
+        @property
+        def as_cif(self) -> str:
+            return ''
+
+    c = MyCollection(item_type=Item)
+    c['a'] = Item('a')
+    c['b'] = Item('b')
+    c.remove('a')
+    assert 'a' not in c
+    assert len(c) == 1
+    with pytest.raises(KeyError):
+        c.remove('nonexistent')
+
+
+def test_collection_datablock_keyed_items():
+    """Verify __setitem__/__delitem__/__contains__ work for datablock-keyed items."""
+    from easydiffraction.core.collection import CollectionBase
+    from easydiffraction.core.identity import Identity
+
+    class DbItem:
+        def __init__(self, name):
+            self._identity = Identity(owner=self, datablock_entry=lambda: name)
+
+    class MyCollection(CollectionBase):
+        @property
+        def parameters(self):
+            return []
+
+        @property
+        def as_cif(self) -> str:
+            return ''
+
+    c = MyCollection(item_type=DbItem)
+    a = DbItem('alpha')
+    b = DbItem('beta')
+    c['alpha'] = a
+    c['beta'] = b
+    assert 'alpha' in c
+    assert c['alpha'] is a
+
+    # Replace
+    a2 = DbItem('alpha')
+    c['alpha'] = a2
+    assert c['alpha'] is a2
+    assert len(c) == 2
+
+    # Delete
+    del c['beta']
+    assert 'beta' not in c
+    assert len(c) == 1
+
diff --git a/tests/unit/easydiffraction/core/test_datablock.py b/tests/unit/easydiffraction/core/test_datablock.py
index 91d35663..6565bbaa 100644
--- a/tests/unit/easydiffraction/core/test_datablock.py
+++ b/tests/unit/easydiffraction/core/test_datablock.py
@@ -5,9 +5,8 @@ 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.variable import Parameter
     from easydiffraction.core.validation import AttributeSpec
-    from easydiffraction.core.validation import DataTypes
     from easydiffraction.io.cif.handler import CifHandler
 
     class Cat(CategoryItem):
@@ -19,17 +18,20 @@ def __init__(self):
             self._p1 = Parameter(
                 name='p1',
                 description='',
-                value_spec=AttributeSpec(value=1.0, type_=DataTypes.NUMERIC, default=0.0),
+                value_spec=AttributeSpec(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),
+                value_spec=AttributeSpec(default=0.0),
                 units='',
                 cif_handler=CifHandler(names=['_cat.p2']),
             )
+            # Set actual values via setter
+            self._p1.value = 1.0
+            self._p2.value = 2.0
             # Make p2 constrained and not free
             self._p2._constrained = True
             self._p2._free = False
@@ -59,8 +61,8 @@ def cat(self):
     coll = DatablockCollection(item_type=Block)
     a = Block('A')
     b = Block('B')
-    coll._add(a)
-    coll._add(b)
+    coll.add(a)
+    coll.add(b)
     # parameters collection aggregates from both blocks (p1 & p2 each)
     params = coll.parameters
     assert len(params) == 4
@@ -71,3 +73,64 @@ def cat(self):
     # free is subset of fittable where free=True (true for p1)
     free_params = coll.free_parameters
     assert free_params == fittable
+
+
+def test_datablock_item_help(capsys):
+    from easydiffraction.core.category import CategoryItem
+    from easydiffraction.core.datablock import DatablockItem
+    from easydiffraction.core.variable import Parameter
+    from easydiffraction.core.validation import AttributeSpec
+    from easydiffraction.io.cif.handler import CifHandler
+
+    class Cat(CategoryItem):
+        def __init__(self):
+            super().__init__()
+            self._identity.category_code = 'cat'
+            self._identity.category_entry_name = 'e1'
+            self._p1 = Parameter(
+                name='p1',
+                description='',
+                value_spec=AttributeSpec(default=0.0),
+                units='',
+                cif_handler=CifHandler(names=['_cat.p1']),
+            )
+
+        @property
+        def p1(self):
+            return self._p1
+
+    class Block(DatablockItem):
+        def __init__(self):
+            super().__init__()
+            self._identity.datablock_entry_name = lambda: 'blk'
+            self._cat = Cat()
+
+        @property
+        def cat(self):
+            return self._cat
+
+    b = Block()
+    b.help()
+    out = capsys.readouterr().out
+    assert 'Help for' in out
+    assert 'Categories' in out
+    assert 'cat' in out
+
+
+def test_datablock_collection_help(capsys):
+    from easydiffraction.core.datablock import DatablockCollection
+    from easydiffraction.core.datablock import DatablockItem
+
+    class Block(DatablockItem):
+        def __init__(self, name):
+            super().__init__()
+            self._identity.datablock_entry_name = lambda: name
+
+    coll = DatablockCollection(item_type=Block)
+    a = Block('A')
+    coll.add(a)
+    coll.help()
+    out = capsys.readouterr().out
+    assert 'Items (1)' in out
+    assert 'A' in out
+
diff --git a/tests/unit/easydiffraction/core/test_factory.py b/tests/unit/easydiffraction/core/test_factory.py
index 22ffad86..ee85b97c 100644
--- a/tests/unit/easydiffraction/core/test_factory.py
+++ b/tests/unit/easydiffraction/core/test_factory.py
@@ -1,29 +1,5 @@
 # SPDX-FileCopyrightText: 2021-2026 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')
+# core/factory.py was removed — FactoryBase and _validate_args are no
+# longer part of the codebase.  This test file is intentionally empty.
diff --git a/tests/unit/easydiffraction/core/test_guard.py b/tests/unit/easydiffraction/core/test_guard.py
index 1cd9dd15..34407914 100644
--- a/tests/unit/easydiffraction/core/test_guard.py
+++ b/tests/unit/easydiffraction/core/test_guard.py
@@ -51,3 +51,53 @@ def as_cif(self) -> str:
     # Unknown attribute should raise AttributeError under current logging mode
     with pytest.raises(AttributeError):
         p.child.unknown_attr = 1
+
+
+def test_help_lists_public_properties(capsys):
+    from easydiffraction.core.guard import GuardedBase
+
+    class Obj(GuardedBase):
+        @property
+        def parameters(self):
+            return []
+
+        @property
+        def as_cif(self) -> str:
+            return ''
+
+        @property
+        def name(self):
+            """Human-readable name."""
+            return 'test'
+
+        @property
+        def score(self):
+            """Computed score."""
+            return 42
+
+        @score.setter
+        def score(self, v):
+            pass
+
+    obj = Obj()
+    obj.help()
+    out = capsys.readouterr().out
+    assert "Help for 'Obj'" in out
+    assert 'name' in out
+    assert 'score' in out
+    assert 'Properties' in out
+    assert 'Methods' in out
+    assert '✓' in out  # score is writable
+    assert '✗' in out  # name is read-only
+
+
+def test_first_sentence_extracts_first_paragraph():
+    from easydiffraction.core.guard import GuardedBase
+
+    assert GuardedBase._first_sentence(None) == ''
+    assert GuardedBase._first_sentence('') == ''
+    assert GuardedBase._first_sentence('One liner.') == 'One liner.'
+    assert GuardedBase._first_sentence('First.\n\nSecond.') == 'First.'
+    assert GuardedBase._first_sentence('Line one\ncontinued.') == 'Line one continued.'
+
+
diff --git a/tests/unit/easydiffraction/core/test_parameters.py b/tests/unit/easydiffraction/core/test_parameters.py
index 3183fb81..35c65558 100644
--- a/tests/unit/easydiffraction/core/test_parameters.py
+++ b/tests/unit/easydiffraction/core/test_parameters.py
@@ -6,14 +6,14 @@
 
 
 def test_module_import():
-    import easydiffraction.core.parameters as MUT
+    import easydiffraction.core.variable as MUT
 
-    assert MUT.__name__ == 'easydiffraction.core.parameters'
+    assert MUT.__name__ == 'easydiffraction.core.variable'
 
 
 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.variable import StringDescriptor
     from easydiffraction.core.validation import AttributeSpec
     from easydiffraction.core.validation import DataTypes
     from easydiffraction.io.cif.handler import CifHandler
@@ -21,21 +21,20 @@ def test_string_descriptor_type_override_raises_type_error():
     with pytest.raises(TypeError):
         StringDescriptor(
             name='title',
-            value_spec=AttributeSpec(value='abc', type_=DataTypes.NUMERIC, default='x'),
+            value_spec=AttributeSpec(data_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.variable 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),
+        value_spec=AttributeSpec(default=1.23),
         units='deg',
         cif_handler=CifHandler(names=['_x.w']),
     )
@@ -44,17 +43,17 @@ def test_numeric_descriptor_str_includes_units():
 
 
 def test_parameter_string_repr_and_as_cif_and_flags():
-    from easydiffraction.core.parameters import Parameter
+    from easydiffraction.core.variable 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),
+        value_spec=AttributeSpec(default=0.0),
         units='A',
         cif_handler=CifHandler(names=['_param.a']),
     )
+    p.value = 2.5
     # Update extra attributes
     p.uncertainty = 0.1
     p.free = True
@@ -70,14 +69,13 @@ def test_parameter_string_repr_and_as_cif_and_flags():
 
 
 def test_parameter_uncertainty_must_be_non_negative():
-    from easydiffraction.core.parameters import Parameter
+    from easydiffraction.core.variable 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),
+        value_spec=AttributeSpec(default=1.0),
         cif_handler=CifHandler(names=['_param.b']),
     )
     with pytest.raises(TypeError):
@@ -85,14 +83,13 @@ def test_parameter_uncertainty_must_be_non_negative():
 
 
 def test_parameter_fit_bounds_assign_and_read():
-    from easydiffraction.core.parameters import Parameter
+    from easydiffraction.core.variable 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),
+        value_spec=AttributeSpec(default=0.0),
         cif_handler=CifHandler(names=['_param.c']),
     )
     p.fit_min = -1.0
diff --git a/tests/unit/easydiffraction/core/test_singletons.py b/tests/unit/easydiffraction/core/test_singletons.py
index d2f8fa3a..bd2c5aef 100644
--- a/tests/unit/easydiffraction/core/test_singletons.py
+++ b/tests/unit/easydiffraction/core/test_singletons.py
@@ -5,7 +5,7 @@
 
 
 def test_uid_map_handler_rejects_non_descriptor():
-    from easydiffraction.core.singletons import UidMapHandler
+    from easydiffraction.core.singleton import UidMapHandler
 
     h = UidMapHandler.get()
     with pytest.raises(TypeError):
diff --git a/tests/unit/easydiffraction/core/test_validation.py b/tests/unit/easydiffraction/core/test_validation.py
index 64150f33..70a92bb5 100644
--- a/tests/unit/easydiffraction/core/test_validation.py
+++ b/tests/unit/easydiffraction/core/test_validation.py
@@ -9,7 +9,7 @@ def test_module_import():
     assert expected_module_name == actual_module_name
 
 
-def test_type_validator_accepts_and_rejects(monkeypatch):
+def test_data_type_validator_accepts_and_rejects(monkeypatch):
     from easydiffraction.core.validation import AttributeSpec
     from easydiffraction.core.validation import DataTypes
     from easydiffraction.utils.logging import log
@@ -17,7 +17,7 @@ def test_type_validator_accepts_and_rejects(monkeypatch):
     # So that errors do not raise in test process
     log.configure(reaction=log.Reaction.WARN)
 
-    spec = AttributeSpec(type_=DataTypes.STRING, default='abc')
+    spec = AttributeSpec(data_type=DataTypes.STRING, default='abc')
     # valid
     expected = 'xyz'
     actual = spec.validated('xyz', name='p')
@@ -36,7 +36,7 @@ def test_range_validator_bounds(monkeypatch):
 
     log.configure(reaction=log.Reaction.WARN)
     spec = AttributeSpec(
-        type_=DataTypes.NUMERIC, default=1.0, content_validator=RangeValidator(ge=0, le=2)
+        data_type=DataTypes.NUMERIC, default=1.0, validator=RangeValidator(ge=0, le=2)
     )
     # inside range
     expected = 1.5
@@ -55,11 +55,11 @@ def test_membership_and_regex_validators(monkeypatch):
     from easydiffraction.utils.logging import log
 
     log.configure(reaction=log.Reaction.WARN)
-    mspec = AttributeSpec(default='b', content_validator=MembershipValidator(['a', 'b']))
+    mspec = AttributeSpec(default='b', 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$'))
+    rspec = AttributeSpec(default='a1', 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/experiments/categories/background/test_base.py b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_base.py
similarity index 75%
rename from tests/unit/easydiffraction/experiments/categories/background/test_base.py
rename to tests/unit/easydiffraction/datablocks/experiment/categories/background/test_base.py
index 57f1bfeb..31c7fd0b 100644
--- a/tests/unit/easydiffraction/experiments/categories/background/test_base.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_base.py
@@ -7,23 +7,30 @@
 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.variable import Parameter
     from easydiffraction.core.validation import AttributeSpec
     from easydiffraction.core.validation import DataTypes
-    from easydiffraction.experiments.categories.background.base import BackgroundBase
+    from easydiffraction.datablocks.experiment.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()
+        def __init__(self):
             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),
+                value_spec=AttributeSpec(data_type=DataTypes.NUMERIC, default=0.0),
                 cif_handler=CifHandler(names=['_bkg.level']),
             )
+            self._identity.category_entry_name = lambda: str(self._level.value)
+
+        @property
+        def level(self):
+            return self._level
+
+        @level.setter
+        def level(self, value):
+            self._level.value = value
 
         def calculate(self, x_data):
             return np.full_like(np.asarray(x_data), fill_value=self._level.value, dtype=float)
@@ -48,9 +55,10 @@ def show(self) -> None:  # pragma: no cover - trivial
             return None
 
     coll = BackgroundCollection()
-    a = ConstantBackground('a', 1.0)
-    coll.add('a', 1.0)
-    coll.add('b', 2.0)
+    a = ConstantBackground()
+    a.level = 1.0
+    coll.create(level=1.0)
+    coll.create(level=2.0)
 
     # calculate sums two backgrounds externally (out of scope), here just verify item.calculate
     x = np.array([0.0, 1.0, 2.0])
diff --git a/tests/unit/easydiffraction/experiments/categories/background/test_chebyshev.py b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_chebyshev.py
similarity index 85%
rename from tests/unit/easydiffraction/experiments/categories/background/test_chebyshev.py
rename to tests/unit/easydiffraction/datablocks/experiment/categories/background/test_chebyshev.py
index 07a61a94..b59ea102 100644
--- a/tests/unit/easydiffraction/experiments/categories/background/test_chebyshev.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_chebyshev.py
@@ -7,7 +7,7 @@
 def test_chebyshev_background_calculate_and_cif():
     from types import SimpleNamespace
 
-    from easydiffraction.experiments.categories.background.chebyshev import (
+    from easydiffraction.datablocks.experiment.categories.background.chebyshev import (
         ChebyshevPolynomialBackground,
     )
 
@@ -25,7 +25,7 @@ def test_chebyshev_background_calculate_and_cif():
     assert np.allclose(mock_data._bkg, 0.0)
 
     # Add two terms and verify CIF contains expected tags
-    cb.add(order=0, coef=1.0)
-    cb.add(order=1, coef=0.5)
+    cb.create(order=0, coef=1.0)
+    cb.create(order=1, coef=0.5)
     cif = cb.as_cif
     assert '_pd_background.Chebyshev_order' in cif and '_pd_background.Chebyshev_coef' in cif
diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_enums.py b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_enums.py
new file mode 100644
index 00000000..47e0f5f3
--- /dev/null
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_enums.py
@@ -0,0 +1,17 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+
+def test_background_type_info():
+    from easydiffraction.datablocks.experiment.categories.background.line_segment import (
+        LineSegmentBackground,
+    )
+    from easydiffraction.datablocks.experiment.categories.background.chebyshev import (
+        ChebyshevPolynomialBackground,
+    )
+
+    assert LineSegmentBackground.type_info.tag == 'line-segment'
+    assert LineSegmentBackground.type_info.description == 'Linear interpolation between points'
+
+    assert ChebyshevPolynomialBackground.type_info.tag == 'chebyshev'
+    assert ChebyshevPolynomialBackground.type_info.description == 'Chebyshev polynomial background'
diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_factory.py
new file mode 100644
index 00000000..15c40a19
--- /dev/null
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_factory.py
@@ -0,0 +1,20 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+import pytest
+
+
+def test_background_factory_default_and_errors():
+    from easydiffraction.datablocks.experiment.categories.background.factory import BackgroundFactory
+
+    # Default via default_tag()
+    obj = BackgroundFactory.create(BackgroundFactory.default_tag())
+    assert obj.__class__.__name__.endswith('LineSegmentBackground')
+
+    # Explicit type by tag
+    obj2 = BackgroundFactory.create('chebyshev')
+    assert obj2.__class__.__name__.endswith('ChebyshevPolynomialBackground')
+
+    # Unsupported tag should raise ValueError
+    with pytest.raises(ValueError):
+        BackgroundFactory.create('nonexistent')
diff --git a/tests/unit/easydiffraction/experiments/categories/background/test_line_segment.py b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_line_segment.py
similarity index 86%
rename from tests/unit/easydiffraction/experiments/categories/background/test_line_segment.py
rename to tests/unit/easydiffraction/datablocks/experiment/categories/background/test_line_segment.py
index 23067f3c..e4e89605 100644
--- a/tests/unit/easydiffraction/experiments/categories/background/test_line_segment.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_line_segment.py
@@ -7,7 +7,7 @@
 def test_line_segment_background_calculate_and_cif():
     from types import SimpleNamespace
 
-    from easydiffraction.experiments.categories.background.line_segment import (
+    from easydiffraction.datablocks.experiment.categories.background.line_segment import (
         LineSegmentBackground,
     )
 
@@ -25,8 +25,8 @@ def test_line_segment_background_calculate_and_cif():
     assert np.allclose(mock_data._bkg, [0.0, 0.0, 0.0])
 
     # Add two points -> linear interpolation
-    bkg.add(id='1', x=0.0, y=0.0)
-    bkg.add(id='2', x=2.0, y=4.0)
+    bkg.create(id='1', x=0.0, y=0.0)
+    bkg.create(id='2', x=2.0, y=4.0)
     bkg._update()
     assert np.allclose(mock_data._bkg, [0.0, 2.0, 4.0])
 
diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_pd.py b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_pd.py
new file mode 100644
index 00000000..eaa48808
--- /dev/null
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_pd.py
@@ -0,0 +1,145 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+import numpy as np
+
+
+def test_pd_cwl_data_point_defaults():
+    from easydiffraction.datablocks.experiment.categories.data.bragg_pd import PdCwlDataPoint
+
+    pt = PdCwlDataPoint()
+    assert pt.point_id.value == '0'
+    assert pt.d_spacing.value == 0.0
+    assert pt.two_theta.value == 0.0
+    assert pt.intensity_meas.value == 0.0
+    assert pt.intensity_meas_su.value == 1.0
+    assert pt.intensity_calc.value == 0.0
+    assert pt.intensity_bkg.value == 0.0
+    assert pt.calc_status.value == 'incl'
+    assert pt._identity.category_code == 'pd_data'
+
+
+def test_pd_tof_data_point_defaults():
+    from easydiffraction.datablocks.experiment.categories.data.bragg_pd import PdTofDataPoint
+
+    pt = PdTofDataPoint()
+    assert pt.point_id.value == '0'
+    assert pt.d_spacing.value == 0.0
+    assert pt.time_of_flight.value == 0.0
+    assert pt.intensity_meas.value == 0.0
+    assert pt.intensity_meas_su.value == 1.0
+    assert pt.intensity_calc.value == 0.0
+    assert pt.intensity_bkg.value == 0.0
+    assert pt.calc_status.value == 'incl'
+    assert pt._identity.category_code == 'pd_data'
+
+
+def test_pd_cwl_data_collection_create_and_properties():
+    from easydiffraction.datablocks.experiment.categories.data.bragg_pd import PdCwlData
+
+    coll = PdCwlData()
+
+    # Create items with x-coordinate (two_theta) values
+    x_vals = np.array([10.0, 20.0, 30.0])
+    coll._create_items_set_xcoord_and_id(x_vals)
+
+    assert len(coll._items) == 3
+
+    # Check two_theta property (returns calc items only, all included)
+    np.testing.assert_array_almost_equal(coll.two_theta, x_vals)
+
+    # Check x is alias for two_theta
+    np.testing.assert_array_almost_equal(coll.x, coll.two_theta)
+
+    # Check unfiltered_x returns all items
+    np.testing.assert_array_almost_equal(coll.unfiltered_x, x_vals)
+
+    # Set and read measured intensities
+    meas = np.array([100.0, 200.0, 300.0])
+    coll._set_intensity_meas(meas)
+    np.testing.assert_array_almost_equal(coll.intensity_meas, meas)
+
+    # Set and read standard uncertainties
+    su = np.array([10.0, 20.0, 30.0])
+    coll._set_intensity_meas_su(su)
+    np.testing.assert_array_almost_equal(coll.intensity_meas_su, su)
+
+    # Check point IDs are set
+    assert coll._items[0].point_id.value == '1'
+    assert coll._items[1].point_id.value == '2'
+    assert coll._items[2].point_id.value == '3'
+
+
+def test_pd_tof_data_collection_create_and_properties():
+    from easydiffraction.datablocks.experiment.categories.data.bragg_pd import PdTofData
+
+    coll = PdTofData()
+
+    # Create items with x-coordinate (time_of_flight) values
+    x_vals = np.array([1000.0, 2000.0, 3000.0])
+    coll._create_items_set_xcoord_and_id(x_vals)
+
+    assert len(coll._items) == 3
+
+    # Check time_of_flight property
+    np.testing.assert_array_almost_equal(coll.time_of_flight, x_vals)
+
+    # Check x is alias for time_of_flight
+    np.testing.assert_array_almost_equal(coll.x, coll.time_of_flight)
+
+    # Check unfiltered_x returns all items
+    np.testing.assert_array_almost_equal(coll.unfiltered_x, x_vals)
+
+    # Check point IDs are set
+    assert coll._items[0].point_id.value == '1'
+    assert coll._items[2].point_id.value == '3'
+
+
+def test_pd_data_calc_status_exclusion():
+    from easydiffraction.datablocks.experiment.categories.data.bragg_pd import PdCwlData
+
+    coll = PdCwlData()
+
+    x_vals = np.array([10.0, 20.0, 30.0, 40.0])
+    coll._create_items_set_xcoord_and_id(x_vals)
+    coll._set_intensity_meas(np.array([100.0, 200.0, 300.0, 400.0]))
+    coll._set_intensity_meas_su(np.array([10.0, 20.0, 30.0, 40.0]))
+
+    # Exclude the second and third points
+    coll._set_calc_status([True, False, False, True])
+
+    # calc_status should reflect the change
+    assert np.array_equal(coll.calc_status, np.array(['incl', 'excl', 'excl', 'incl']))
+
+    # x should only return included points
+    np.testing.assert_array_almost_equal(coll.x, np.array([10.0, 40.0]))
+
+    # intensity_meas should only return included points
+    np.testing.assert_array_almost_equal(coll.intensity_meas, np.array([100.0, 400.0]))
+
+
+def test_pd_cwl_data_type_info():
+    from easydiffraction.datablocks.experiment.categories.data.bragg_pd import PdCwlData
+    from easydiffraction.datablocks.experiment.categories.data.bragg_pd import PdTofData
+
+    assert PdCwlData.type_info.tag == 'bragg-pd'
+    assert PdCwlData.type_info.description == 'Bragg powder CWL data'
+
+    assert PdTofData.type_info.tag == 'bragg-pd-tof'
+    assert PdTofData.type_info.description == 'Bragg powder TOF data'
+
+
+def test_pd_data_intensity_meas_su_zero_replacement():
+    from easydiffraction.datablocks.experiment.categories.data.bragg_pd import PdCwlData
+
+    coll = PdCwlData()
+    x_vals = np.array([10.0, 20.0, 30.0])
+    coll._create_items_set_xcoord_and_id(x_vals)
+
+    # Set su with near-zero values — those should be replaced by 1.0
+    coll._set_intensity_meas_su(np.array([0.0, 0.00001, 5.0]))
+    su = coll.intensity_meas_su
+    assert su[0] == 1.0  # replaced
+    assert su[1] == 1.0  # replaced
+    assert su[2] == 5.0  # kept
+
diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_sc.py b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_sc.py
new file mode 100644
index 00000000..06dd634d
--- /dev/null
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_sc.py
@@ -0,0 +1,93 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+import numpy as np
+
+
+def test_refln_data_point_defaults():
+    from easydiffraction.datablocks.experiment.categories.data.bragg_sc import Refln
+
+    pt = Refln()
+    assert pt.id.value == '0'
+    assert pt.d_spacing.value == 0.0
+    assert pt.sin_theta_over_lambda.value == 0.0
+    assert pt.index_h.value == 0.0
+    assert pt.index_k.value == 0.0
+    assert pt.index_l.value == 0.0
+    assert pt.intensity_meas.value == 0.0
+    assert pt.intensity_meas_su.value == 0.0
+    assert pt.intensity_calc.value == 0.0
+    assert pt.wavelength.value == 0.0
+    assert pt._identity.category_code == 'refln'
+
+
+def test_refln_data_collection_create_and_properties():
+    from easydiffraction.datablocks.experiment.categories.data.bragg_sc import ReflnData
+
+    coll = ReflnData()
+
+    # Create items with hkl
+    h = np.array([1.0, 2.0, 0.0])
+    k = np.array([0.0, 1.0, 0.0])
+    l = np.array([0.0, 0.0, 2.0])
+    coll._create_items_set_hkl_and_id(h, k, l)
+
+    assert len(coll._items) == 3
+
+    # Check hkl arrays
+    np.testing.assert_array_almost_equal(coll.index_h, h)
+    np.testing.assert_array_almost_equal(coll.index_k, k)
+    np.testing.assert_array_almost_equal(coll.index_l, l)
+
+    # Check IDs are sequential
+    assert coll._items[0].id.value == '1'
+    assert coll._items[1].id.value == '2'
+    assert coll._items[2].id.value == '3'
+
+    # Set and read measured intensities
+    meas = np.array([50.0, 100.0, 150.0])
+    coll._set_intensity_meas(meas)
+    np.testing.assert_array_almost_equal(coll.intensity_meas, meas)
+
+    # Set and read su
+    su = np.array([5.0, 10.0, 15.0])
+    coll._set_intensity_meas_su(su)
+    np.testing.assert_array_almost_equal(coll.intensity_meas_su, su)
+
+    # Set wavelength
+    wl = np.array([0.84, 0.84, 0.84])
+    coll._set_wavelength(wl)
+    np.testing.assert_array_almost_equal(coll.wavelength, wl)
+
+    # Set and read calculated intensities
+    calc = np.array([48.0, 102.0, 148.0])
+    coll._set_intensity_calc(calc)
+    np.testing.assert_array_almost_equal(coll.intensity_calc, calc)
+
+
+def test_refln_data_d_spacing_and_stol():
+    from easydiffraction.datablocks.experiment.categories.data.bragg_sc import ReflnData
+
+    coll = ReflnData()
+    h = np.array([1.0, 2.0])
+    k = np.array([0.0, 0.0])
+    l = np.array([0.0, 0.0])
+    coll._create_items_set_hkl_and_id(h, k, l)
+
+    # Set d-spacing
+    d = np.array([5.43, 2.715])
+    coll._set_d_spacing(d)
+    np.testing.assert_array_almost_equal(coll.d_spacing, d)
+
+    # Set sin(theta)/lambda
+    stol = np.array([0.092, 0.184])
+    coll._set_sin_theta_over_lambda(stol)
+    np.testing.assert_array_almost_equal(coll.sin_theta_over_lambda, stol)
+
+
+def test_refln_data_type_info():
+    from easydiffraction.datablocks.experiment.categories.data.bragg_sc import ReflnData
+
+    assert ReflnData.type_info.tag == 'bragg-sc'
+    assert ReflnData.type_info.description == 'Bragg single-crystal reflection data'
+
diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_factory.py
new file mode 100644
index 00000000..18ecd5d0
--- /dev/null
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_factory.py
@@ -0,0 +1,89 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+import pytest
+
+
+def test_data_factory_default_and_errors():
+    from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory
+
+    # Ensure concrete classes are registered
+    from easydiffraction.datablocks.experiment.categories.data import bragg_pd  # noqa: F401
+    from easydiffraction.datablocks.experiment.categories.data import bragg_sc  # noqa: F401
+    from easydiffraction.datablocks.experiment.categories.data import total_pd  # noqa: F401
+
+    # Explicit type by tag
+    obj = DataFactory.create('bragg-pd')
+    assert obj.__class__.__name__ == 'PdCwlData'
+
+    # Explicit type by tag
+    obj2 = DataFactory.create('bragg-pd-tof')
+    assert obj2.__class__.__name__ == 'PdTofData'
+
+    obj3 = DataFactory.create('bragg-sc')
+    assert obj3.__class__.__name__ == 'ReflnData'
+
+    obj4 = DataFactory.create('total-pd')
+    assert obj4.__class__.__name__ == 'TotalData'
+
+    # Unsupported tag should raise ValueError
+    with pytest.raises(ValueError):
+        DataFactory.create('nonexistent')
+
+
+def test_data_factory_default_tag_resolution():
+    from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory
+    from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+    from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+    from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
+
+    # Ensure concrete classes are registered
+    from easydiffraction.datablocks.experiment.categories.data import bragg_pd  # noqa: F401
+    from easydiffraction.datablocks.experiment.categories.data import bragg_sc  # noqa: F401
+    from easydiffraction.datablocks.experiment.categories.data import total_pd  # noqa: F401
+
+    # Context-dependent default: Bragg powder CWL
+    tag = DataFactory.default_tag(
+        sample_form=SampleFormEnum.POWDER,
+        scattering_type=ScatteringTypeEnum.BRAGG,
+        beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH,
+    )
+    assert tag == 'bragg-pd'
+
+    # Context-dependent default: Bragg powder TOF
+    tag = DataFactory.default_tag(
+        sample_form=SampleFormEnum.POWDER,
+        scattering_type=ScatteringTypeEnum.BRAGG,
+        beam_mode=BeamModeEnum.TIME_OF_FLIGHT,
+    )
+    assert tag == 'bragg-pd-tof'
+
+    # Context-dependent default: total scattering
+    tag = DataFactory.default_tag(
+        sample_form=SampleFormEnum.POWDER,
+        scattering_type=ScatteringTypeEnum.TOTAL,
+    )
+    assert tag == 'total-pd'
+
+    # Context-dependent default: single crystal
+    tag = DataFactory.default_tag(
+        sample_form=SampleFormEnum.SINGLE_CRYSTAL,
+        scattering_type=ScatteringTypeEnum.BRAGG,
+    )
+    assert tag == 'bragg-sc'
+
+
+def test_data_factory_supported_tags():
+    from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory
+
+    # Ensure concrete classes are registered
+    from easydiffraction.datablocks.experiment.categories.data import bragg_pd  # noqa: F401
+    from easydiffraction.datablocks.experiment.categories.data import bragg_sc  # noqa: F401
+    from easydiffraction.datablocks.experiment.categories.data import total_pd  # noqa: F401
+
+    tags = DataFactory.supported_tags()
+    assert 'bragg-pd' in tags
+    assert 'bragg-pd-tof' in tags
+    assert 'bragg-sc' in tags
+    assert 'total-pd' in tags
+
diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_total_pd.py b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_total_pd.py
new file mode 100644
index 00000000..90c85e3e
--- /dev/null
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_total_pd.py
@@ -0,0 +1,92 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+import numpy as np
+
+
+def test_total_data_point_defaults():
+    from easydiffraction.datablocks.experiment.categories.data.total_pd import TotalDataPoint
+
+    pt = TotalDataPoint()
+    assert pt.point_id.value == '0'
+    assert pt.r.value == 0.0
+    assert pt.g_r_meas.value == 0.0
+    assert pt.g_r_meas_su.value == 0.0
+    assert pt.g_r_calc.value == 0.0
+    assert pt.calc_status.value == 'incl'
+    assert pt._identity.category_code == 'total_data'
+
+
+def test_total_data_collection_create_and_properties():
+    from easydiffraction.datablocks.experiment.categories.data.total_pd import TotalData
+
+    coll = TotalData()
+
+    # Create items with r values
+    r_vals = np.array([1.0, 2.0, 3.0, 4.0])
+    coll._create_items_set_xcoord_and_id(r_vals)
+
+    assert len(coll._items) == 4
+
+    # Check x property (returns calc items, all included)
+    np.testing.assert_array_almost_equal(coll.x, r_vals)
+
+    # Check unfiltered_x returns all items
+    np.testing.assert_array_almost_equal(coll.unfiltered_x, r_vals)
+
+    # Set and read measured G(r)
+    g_meas = np.array([0.1, 0.5, 0.3, 0.2])
+    coll._set_g_r_meas(g_meas)
+    np.testing.assert_array_almost_equal(coll.intensity_meas, g_meas)
+
+    # Set and read su
+    g_su = np.array([0.01, 0.05, 0.03, 0.02])
+    coll._set_g_r_meas_su(g_su)
+    np.testing.assert_array_almost_equal(coll.intensity_meas_su, g_su)
+
+    # Point IDs
+    assert coll._items[0].point_id.value == '1'
+    assert coll._items[3].point_id.value == '4'
+
+
+def test_total_data_calc_status_and_exclusion():
+    from easydiffraction.datablocks.experiment.categories.data.total_pd import TotalData
+
+    coll = TotalData()
+    r_vals = np.array([1.0, 2.0, 3.0, 4.0])
+    coll._create_items_set_xcoord_and_id(r_vals)
+    coll._set_g_r_meas(np.array([0.1, 0.5, 0.3, 0.2]))
+
+    # Exclude the second and third points
+    coll._set_calc_status([True, False, False, True])
+
+    assert np.array_equal(coll.calc_status, np.array(['incl', 'excl', 'excl', 'incl']))
+
+    # x should only return included points
+    np.testing.assert_array_almost_equal(coll.x, np.array([1.0, 4.0]))
+
+    # intensity_meas should only return included points
+    np.testing.assert_array_almost_equal(coll.intensity_meas, np.array([0.1, 0.2]))
+
+
+def test_total_data_intensity_bkg_always_zero():
+    from easydiffraction.datablocks.experiment.categories.data.total_pd import TotalData
+
+    coll = TotalData()
+    r_vals = np.array([1.0, 2.0, 3.0])
+    coll._create_items_set_xcoord_and_id(r_vals)
+
+    # Set calc G(r) so intensity_calc is non-empty
+    coll._set_g_r_calc(np.array([0.5, 0.6, 0.7]))
+
+    # Background should always be zeros
+    bkg = coll.intensity_bkg
+    np.testing.assert_array_almost_equal(bkg, np.zeros(3))
+
+
+def test_total_data_type_info():
+    from easydiffraction.datablocks.experiment.categories.data.total_pd import TotalData
+
+    assert TotalData.type_info.tag == 'total-pd'
+    assert TotalData.type_info.description == 'Total scattering (PDF) data'
+
diff --git a/tests/unit/easydiffraction/experiments/categories/instrument/test_base.py b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_base.py
similarity index 79%
rename from tests/unit/easydiffraction/experiments/categories/instrument/test_base.py
rename to tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_base.py
index 047d314b..70d36b8a 100644
--- a/tests/unit/easydiffraction/experiments/categories/instrument/test_base.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_base.py
@@ -2,7 +2,7 @@
 # SPDX-License-Identifier: BSD-3-Clause
 
 def test_instrument_base_sets_category_code():
-    from easydiffraction.experiments.categories.instrument.base import InstrumentBase
+    from easydiffraction.datablocks.experiment.categories.instrument.base import InstrumentBase
 
     class DummyInstr(InstrumentBase):
         def __init__(self):
diff --git a/tests/unit/easydiffraction/experiments/categories/instrument/test_cwl.py b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_cwl.py
similarity index 81%
rename from tests/unit/easydiffraction/experiments/categories/instrument/test_cwl.py
rename to tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_cwl.py
index 2dc7bd6c..205abd50 100644
--- a/tests/unit/easydiffraction/experiments/categories/instrument/test_cwl.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_cwl.py
@@ -1,7 +1,7 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
 
-from easydiffraction.experiments.categories.instrument.cwl import CwlPdInstrument
+from easydiffraction.datablocks.experiment.categories.instrument.cwl import CwlPdInstrument
 
 
 def test_cwl_instrument_parameters_settable():
diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_factory.py
new file mode 100644
index 00000000..f122f9be
--- /dev/null
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_factory.py
@@ -0,0 +1,35 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+import pytest
+
+
+def test_instrument_factory_default_and_errors():
+    try:
+        from easydiffraction.datablocks.experiment.categories.instrument.factory import InstrumentFactory
+        from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+        from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+    except ImportError as e:  # pragma: no cover - environment-specific circular import
+        pytest.skip(f'InstrumentFactory import triggers circular import in this context: {e}')
+        return
+
+    # By tag
+    inst = InstrumentFactory.create('cwl-pd')
+    assert inst.__class__.__name__ == 'CwlPdInstrument'
+
+    # By tag
+    inst2 = InstrumentFactory.create('cwl-pd')
+    assert inst2.__class__.__name__ == 'CwlPdInstrument'
+    inst3 = InstrumentFactory.create('tof-pd')
+    assert inst3.__class__.__name__ == 'TofPdInstrument'
+
+    # Context-dependent default
+    tag = InstrumentFactory.default_tag(
+        beam_mode=BeamModeEnum.TIME_OF_FLIGHT,
+        sample_form=SampleFormEnum.POWDER,
+    )
+    assert tag == 'tof-pd'
+
+    # Invalid tag
+    with pytest.raises(ValueError):
+        InstrumentFactory.create('nonexistent')
diff --git a/tests/unit/easydiffraction/experiments/categories/instrument/test_tof.py b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_tof.py
similarity index 94%
rename from tests/unit/easydiffraction/experiments/categories/instrument/test_tof.py
rename to tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_tof.py
index 339bcded..91c4f0d0 100644
--- a/tests/unit/easydiffraction/experiments/categories/instrument/test_tof.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_tof.py
@@ -5,7 +5,7 @@
 
 
 def test_tof_instrument_defaults_and_setters_and_parameters_and_cif():
-    from easydiffraction.experiments.categories.instrument.tof import TofPdInstrument
+    from easydiffraction.datablocks.experiment.categories.instrument.tof import TofPdInstrument
 
     inst = TofPdInstrument()
 
diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_base.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_base.py
similarity index 81%
rename from tests/unit/easydiffraction/experiments/categories/peak/test_base.py
rename to tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_base.py
index 737c7e17..ad3708dc 100644
--- a/tests/unit/easydiffraction/experiments/categories/peak/test_base.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_base.py
@@ -1,7 +1,7 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
 
-from easydiffraction.experiments.categories.peak.base import PeakBase
+from easydiffraction.datablocks.experiment.categories.peak.base import PeakBase
 
 
 def test_peak_base_identity_code():
diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_cwl.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl.py
similarity index 79%
rename from tests/unit/easydiffraction/experiments/categories/peak/test_cwl.py
rename to tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl.py
index b3b16d4e..f4505287 100644
--- a/tests/unit/easydiffraction/experiments/categories/peak/test_cwl.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl.py
@@ -2,9 +2,9 @@
 # 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
+    from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlPseudoVoigt
+    from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlSplitPseudoVoigt
+    from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlThompsonCoxHastings
 
     pv = CwlPseudoVoigt()
     spv = CwlSplitPseudoVoigt()
diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_cwl_mixins.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl_mixins.py
similarity index 74%
rename from tests/unit/easydiffraction/experiments/categories/peak/test_cwl_mixins.py
rename to tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl_mixins.py
index ffde4deb..3bdc4466 100644
--- a/tests/unit/easydiffraction/experiments/categories/peak/test_cwl_mixins.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl_mixins.py
@@ -1,14 +1,14 @@
 # SPDX-FileCopyrightText: 2021-2026 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
+from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlPseudoVoigt
+from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlSplitPseudoVoigt
+from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlThompsonCoxHastings
 
 
 def test_cwl_pseudo_voigt_params_exist_and_settable():
     peak = CwlPseudoVoigt()
-    # Created by _add_constant_wavelength_broadening
+    # CwlBroadening parameters
     assert peak.broad_gauss_u.name == 'broad_gauss_u'
     peak.broad_gauss_u = 0.123
     assert peak.broad_gauss_u.value == 0.123
diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_factory.py
new file mode 100644
index 00000000..6ff155ac
--- /dev/null
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_factory.py
@@ -0,0 +1,54 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+import pytest
+
+
+def test_peak_factory_default_and_combinations_and_errors():
+    from easydiffraction.datablocks.experiment.categories.peak.factory import PeakFactory
+    from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+    from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
+
+    # Explicit valid combos by tag
+    p = PeakFactory.create('pseudo-voigt')
+    assert p._identity.category_code == 'peak'
+
+    # Explicit valid combos by tag
+    p1 = PeakFactory.create('pseudo-voigt')
+    assert p1.__class__.__name__ == 'CwlPseudoVoigt'
+
+    p2 = PeakFactory.create('pseudo-voigt * ikeda-carpenter')
+    assert p2.__class__.__name__ == 'TofPseudoVoigtIkedaCarpenter'
+
+    p3 = PeakFactory.create('gaussian-damped-sinc')
+    assert p3.__class__.__name__ == 'TotalGaussianDampedSinc'
+
+    # Context-dependent defaults
+    tag_bragg_cwl = PeakFactory.default_tag(
+        scattering_type=ScatteringTypeEnum.BRAGG,
+        beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH,
+    )
+    assert tag_bragg_cwl == 'pseudo-voigt'
+
+    tag_bragg_tof = PeakFactory.default_tag(
+        scattering_type=ScatteringTypeEnum.BRAGG,
+        beam_mode=BeamModeEnum.TIME_OF_FLIGHT,
+    )
+    assert tag_bragg_tof == 'pseudo-voigt * ikeda-carpenter'
+
+    tag_total = PeakFactory.default_tag(
+        scattering_type=ScatteringTypeEnum.TOTAL,
+    )
+    assert tag_total == 'gaussian-damped-sinc'
+
+    # supported_for filtering
+    cwl_profiles = PeakFactory.supported_for(
+        scattering_type=ScatteringTypeEnum.BRAGG,
+        beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH,
+    )
+    assert len(cwl_profiles) == 3
+    assert all(k.type_info.tag for k in cwl_profiles)
+
+    # Invalid tag
+    with pytest.raises(ValueError):
+        PeakFactory.create('nonexistent-profile')
diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_tof.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof.py
similarity index 72%
rename from tests/unit/easydiffraction/experiments/categories/peak/test_tof.py
rename to tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof.py
index 0ace3beb..fde6d062 100644
--- a/tests/unit/easydiffraction/experiments/categories/peak/test_tof.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof.py
@@ -1,9 +1,9 @@
 # SPDX-FileCopyrightText: 2021-2026 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
+from easydiffraction.datablocks.experiment.categories.peak.tof import TofPseudoVoigt
+from easydiffraction.datablocks.experiment.categories.peak.tof import TofPseudoVoigtBackToBack
+from easydiffraction.datablocks.experiment.categories.peak.tof import TofPseudoVoigtIkedaCarpenter
 
 
 def test_tof_pseudo_voigt_has_broadening_params():
diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_tof_mixins.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof_mixins.py
similarity index 71%
rename from tests/unit/easydiffraction/experiments/categories/peak/test_tof_mixins.py
rename to tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof_mixins.py
index c54fdc93..ae39c99c 100644
--- a/tests/unit/easydiffraction/experiments/categories/peak/test_tof_mixins.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof_mixins.py
@@ -5,15 +5,13 @@
 
 
 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
+    from easydiffraction.datablocks.experiment.categories.peak.base import PeakBase
+    from easydiffraction.datablocks.experiment.categories.peak.tof_mixins import IkedaCarpenterAsymmetryMixin
+    from easydiffraction.datablocks.experiment.categories.peak.tof_mixins import TofBroadeningMixin
 
-    class TofPeak(PeakBase, TofBroadeningMixin, IkedaCarpenterAsymmetryMixin):
+    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}
diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_total.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total.py
similarity index 91%
rename from tests/unit/easydiffraction/experiments/categories/peak/test_total.py
rename to tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total.py
index 7529d875..c49d1cf7 100644
--- a/tests/unit/easydiffraction/experiments/categories/peak/test_total.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total.py
@@ -5,7 +5,7 @@
 
 
 def test_total_gaussian_damped_sinc_parameters_and_setters():
-    from easydiffraction.experiments.categories.peak.total import TotalGaussianDampedSinc
+    from easydiffraction.datablocks.experiment.categories.peak.total import TotalGaussianDampedSinc
 
     p = TotalGaussianDampedSinc()
     assert p._identity.category_code == 'peak'
diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_total_mixins.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total_mixins.py
similarity index 79%
rename from tests/unit/easydiffraction/experiments/categories/peak/test_total_mixins.py
rename to tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total_mixins.py
index 475ab781..0979dcb9 100644
--- a/tests/unit/easydiffraction/experiments/categories/peak/test_total_mixins.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total_mixins.py
@@ -1,7 +1,7 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
 
-from easydiffraction.experiments.categories.peak.total import TotalGaussianDampedSinc
+from easydiffraction.datablocks.experiment.categories.peak.total import TotalGaussianDampedSinc
 
 
 def test_total_gaussian_damped_sinc_params():
diff --git a/tests/unit/easydiffraction/experiments/categories/test_excluded_regions.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_excluded_regions.py
similarity index 92%
rename from tests/unit/easydiffraction/experiments/categories/test_excluded_regions.py
rename to tests/unit/easydiffraction/datablocks/experiment/categories/test_excluded_regions.py
index bf363e7f..8026740b 100644
--- a/tests/unit/easydiffraction/experiments/categories/test_excluded_regions.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_excluded_regions.py
@@ -7,7 +7,7 @@
 def test_excluded_regions_add_updates_datastore_and_cif():
     from types import SimpleNamespace
 
-    from easydiffraction.experiments.categories.excluded_regions import ExcludedRegions
+    from easydiffraction.datablocks.experiment.categories.excluded_regions import ExcludedRegions
 
     # Minimal fake datastore
     full_x = np.array([0.0, 1.0, 2.0, 3.0])
@@ -38,7 +38,7 @@ def set_calc_status(status):
     # stitch in a parent with data
     object.__setattr__(coll, '_parent', SimpleNamespace(data=ds))
 
-    coll.add(start=1.0, end=2.0)
+    coll.create(start=1.0, end=2.0)
     # Call _update() to apply exclusions
     coll._update()
 
diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/test_experiment_type.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_experiment_type.py
new file mode 100644
index 00000000..169510ab
--- /dev/null
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_experiment_type.py
@@ -0,0 +1,36 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+def test_module_import():
+    import easydiffraction.datablocks.experiment.categories.experiment_type as MUT
+
+    expected_module_name = 'easydiffraction.datablocks.experiment.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.datablocks.experiment.categories.experiment_type import ExperimentType
+    from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+    from easydiffraction.datablocks.experiment.item.enums import RadiationProbeEnum
+    from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+    from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
+    from easydiffraction.utils.logging import log
+
+    log.configure(reaction=log.Reaction.WARN)
+
+    et = ExperimentType()
+    et._set_sample_form(SampleFormEnum.POWDER.value)
+    et._set_beam_mode(BeamModeEnum.CONSTANT_WAVELENGTH.value)
+    et._set_radiation_probe(RadiationProbeEnum.NEUTRON.value)
+    et._set_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
+
+    # public setters are blocked (read-only properties via GuardedBase)
+    et.sample_form = 'single crystal'
+    assert et.sample_form.value == SampleFormEnum.POWDER.value
diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py
new file mode 100644
index 00000000..5287b488
--- /dev/null
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py
@@ -0,0 +1,78 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+
+def test_module_import():
+    import easydiffraction.datablocks.experiment.categories.extinction.shelx as MUT
+
+    expected_module_name = 'easydiffraction.datablocks.experiment.categories.extinction.shelx'
+    actual_module_name = MUT.__name__
+    assert expected_module_name == actual_module_name
+
+
+def test_extinction_defaults():
+    from easydiffraction.datablocks.experiment.categories.extinction.shelx import ShelxExtinction
+
+    ext = ShelxExtinction()
+    assert ext.mosaicity.value == 1.0
+    assert ext.radius.value == 1.0
+    assert ext._identity.category_code == 'extinction'
+
+
+def test_extinction_property_setters():
+    from easydiffraction.datablocks.experiment.categories.extinction.shelx import ShelxExtinction
+
+    ext = ShelxExtinction()
+
+    ext.mosaicity = 0.5
+    assert ext.mosaicity.value == 0.5
+
+    ext.radius = 10.0
+    assert ext.radius.value == 10.0
+
+
+def test_extinction_cif_handler_names():
+    from easydiffraction.datablocks.experiment.categories.extinction.shelx import ShelxExtinction
+
+    ext = ShelxExtinction()
+
+    mosaicity_cif_names = ext._mosaicity._cif_handler.names
+    assert '_extinction.mosaicity' in mosaicity_cif_names
+
+    radius_cif_names = ext._radius._cif_handler.names
+    assert '_extinction.radius' in radius_cif_names
+
+
+def test_extinction_type_info():
+    from easydiffraction.datablocks.experiment.categories.extinction.shelx import ShelxExtinction
+
+    assert ShelxExtinction.type_info.tag == 'shelx'
+    assert ShelxExtinction.type_info.description != ''
+
+
+def test_extinction_factory_registration():
+    from easydiffraction.datablocks.experiment.categories.extinction.factory import (
+        ExtinctionFactory,
+    )
+
+    assert 'shelx' in ExtinctionFactory.supported_tags()
+
+
+def test_extinction_factory_create():
+    from easydiffraction.datablocks.experiment.categories.extinction.factory import (
+        ExtinctionFactory,
+    )
+    from easydiffraction.datablocks.experiment.categories.extinction.shelx import ShelxExtinction
+
+    ext = ExtinctionFactory.create('shelx')
+    assert isinstance(ext, ShelxExtinction)
+
+
+def test_extinction_factory_default_tag():
+    from easydiffraction.datablocks.experiment.categories.extinction.factory import (
+        ExtinctionFactory,
+    )
+
+    assert ExtinctionFactory.default_tag() == 'shelx'
+
+
diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_crystal.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_crystal.py
new file mode 100644
index 00000000..06035598
--- /dev/null
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_crystal.py
@@ -0,0 +1,90 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+
+def test_module_import():
+    import easydiffraction.datablocks.experiment.categories.linked_crystal.default as MUT
+
+    expected_module_name = (
+        'easydiffraction.datablocks.experiment.categories.linked_crystal.default'
+    )
+    actual_module_name = MUT.__name__
+    assert expected_module_name == actual_module_name
+
+
+def test_linked_crystal_defaults():
+    from easydiffraction.datablocks.experiment.categories.linked_crystal.default import (
+        LinkedCrystal,
+    )
+
+    lc = LinkedCrystal()
+    assert lc.id.value == 'Si'
+    assert lc.scale.value == 1.0
+    assert lc._identity.category_code == 'linked_crystal'
+
+
+def test_linked_crystal_property_setters():
+    from easydiffraction.datablocks.experiment.categories.linked_crystal.default import (
+        LinkedCrystal,
+    )
+
+    lc = LinkedCrystal()
+
+    lc.id = 'Ge'
+    assert lc.id.value == 'Ge'
+
+    lc.scale = 2.5
+    assert lc.scale.value == 2.5
+
+
+def test_linked_crystal_cif_handler_names():
+    from easydiffraction.datablocks.experiment.categories.linked_crystal.default import (
+        LinkedCrystal,
+    )
+
+    lc = LinkedCrystal()
+
+    id_cif_names = lc._id._cif_handler.names
+    assert '_sc_crystal_block.id' in id_cif_names
+
+    scale_cif_names = lc._scale._cif_handler.names
+    assert '_sc_crystal_block.scale' in scale_cif_names
+
+
+def test_linked_crystal_type_info():
+    from easydiffraction.datablocks.experiment.categories.linked_crystal.default import (
+        LinkedCrystal,
+    )
+
+    assert LinkedCrystal.type_info.tag == 'default'
+    assert LinkedCrystal.type_info.description != ''
+
+
+def test_linked_crystal_factory_registration():
+    from easydiffraction.datablocks.experiment.categories.linked_crystal.factory import (
+        LinkedCrystalFactory,
+    )
+
+    assert 'default' in LinkedCrystalFactory.supported_tags()
+
+
+def test_linked_crystal_factory_create():
+    from easydiffraction.datablocks.experiment.categories.linked_crystal.default import (
+        LinkedCrystal,
+    )
+    from easydiffraction.datablocks.experiment.categories.linked_crystal.factory import (
+        LinkedCrystalFactory,
+    )
+
+    lc = LinkedCrystalFactory.create('default')
+    assert isinstance(lc, LinkedCrystal)
+
+
+def test_linked_crystal_factory_default_tag():
+    from easydiffraction.datablocks.experiment.categories.linked_crystal.factory import (
+        LinkedCrystalFactory,
+    )
+
+    assert LinkedCrystalFactory.default_tag() == 'default'
+
+
diff --git a/tests/unit/easydiffraction/experiments/categories/test_linked_phases.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_phases.py
similarity index 60%
rename from tests/unit/easydiffraction/experiments/categories/test_linked_phases.py
rename to tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_phases.py
index 2b7de53c..263586f8 100644
--- a/tests/unit/easydiffraction/experiments/categories/test_linked_phases.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_phases.py
@@ -2,14 +2,16 @@
 # 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
+    from easydiffraction.datablocks.experiment.categories.linked_phases import LinkedPhase
+    from easydiffraction.datablocks.experiment.categories.linked_phases import LinkedPhases
 
-    lp = LinkedPhase(id='Si', scale=2.0)
+    lp = LinkedPhase()
+    lp.id = 'Si'
+    lp.scale = 2.0
     assert lp.id.value == 'Si' and lp.scale.value == 2.0
 
     coll = LinkedPhases()
-    coll.add(id='Si', scale=2.0)
+    coll.create(id='Si', scale=2.0)
 
     # CIF loop header presence
     cif = coll.as_cif
diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_base.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_base.py
new file mode 100644
index 00000000..6a6766ed
--- /dev/null
+++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_base.py
@@ -0,0 +1,37 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+def test_module_import():
+    import easydiffraction.datablocks.experiment.item.base as MUT
+
+    expected_module_name = 'easydiffraction.datablocks.experiment.item.base'
+    actual_module_name = MUT.__name__
+    assert expected_module_name == actual_module_name
+
+
+def test_pd_experiment_peak_profile_type_switch(capsys):
+    from easydiffraction.datablocks.experiment.categories.experiment_type import ExperimentType
+    from easydiffraction.datablocks.experiment.item.base import PdExperimentBase
+    from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+    from easydiffraction.datablocks.experiment.item.enums import RadiationProbeEnum
+    from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+    from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
+
+    class ConcretePd(PdExperimentBase):
+        def _load_ascii_data_to_experiment(self, data_path: str) -> None:
+            pass
+
+    et = ExperimentType()
+    et._set_sample_form(SampleFormEnum.POWDER.value)
+    et._set_beam_mode(BeamModeEnum.CONSTANT_WAVELENGTH.value)
+    et._set_radiation_probe(RadiationProbeEnum.NEUTRON.value)
+    et._set_scattering_type(ScatteringTypeEnum.BRAGG.value)
+
+    ex = ConcretePd(name='ex1', type=et)
+    # valid switch using tag string
+    ex.peak_profile_type = 'pseudo-voigt'
+    assert ex.peak_profile_type == '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/datablocks/experiment/item/test_bragg_pd.py
similarity index 65%
rename from tests/unit/easydiffraction/experiments/experiment/test_bragg_pd.py
rename to tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_pd.py
index a0dfa3f2..8b164ed0 100644
--- a/tests/unit/easydiffraction/experiments/experiment/test_bragg_pd.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_pd.py
@@ -4,36 +4,38 @@
 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
+from easydiffraction.datablocks.experiment.categories.background.factory import BackgroundFactory
+from easydiffraction.datablocks.experiment.categories.experiment_type import ExperimentType
+from easydiffraction.datablocks.experiment.item.bragg_pd import BraggPdExperiment
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import RadiationProbeEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.experiment.item.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,
-    )
+    et = ExperimentType()
+    et._set_sample_form(SampleFormEnum.POWDER.value)
+    et._set_beam_mode(BeamModeEnum.CONSTANT_WAVELENGTH.value)
+    et._set_radiation_probe(RadiationProbeEnum.NEUTRON.value)
+    et._set_scattering_type(ScatteringTypeEnum.BRAGG.value)
+    return et
+
+
 
 
 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()
+    assert expt.background_type == BackgroundFactory.default_tag()
 
     # change to a supported type
-    expt.background_type = BackgroundTypeEnum.CHEBYSHEV
-    assert expt.background_type == BackgroundTypeEnum.CHEBYSHEV
+    expt.background_type = 'chebyshev'
+    assert expt.background_type == '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
+    assert expt.background_type == 'chebyshev'
 
 
 def test_load_ascii_data_rounds_and_defaults_sy(tmp_path: pytest.TempPathFactory):
diff --git a/tests/unit/easydiffraction/experiments/experiment/test_bragg_sc.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_sc.py
similarity index 51%
rename from tests/unit/easydiffraction/experiments/experiment/test_bragg_sc.py
rename to tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_sc.py
index af9a65f7..c01a5ee2 100644
--- a/tests/unit/easydiffraction/experiments/experiment/test_bragg_sc.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_sc.py
@@ -3,22 +3,23 @@
 
 import pytest
 
-from easydiffraction.experiments.categories.experiment_type import ExperimentType
-from easydiffraction.experiments.experiment.bragg_sc import CwlScExperiment
-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.datablocks.experiment.categories.experiment_type import ExperimentType
+from easydiffraction.datablocks.experiment.item.bragg_sc import CwlScExperiment
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import RadiationProbeEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.experiment.item.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,
-    )
+    et = ExperimentType()
+    et._set_sample_form(SampleFormEnum.SINGLE_CRYSTAL.value)
+    et._set_beam_mode(BeamModeEnum.CONSTANT_WAVELENGTH.value)
+    et._set_radiation_probe(RadiationProbeEnum.NEUTRON.value)
+    et._set_scattering_type(ScatteringTypeEnum.BRAGG.value)
+    return et
+
 
 
 class _ConcreteCwlSc(CwlScExperiment):
diff --git a/tests/unit/easydiffraction/experiments/experiment/test_enums.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_enums.py
similarity index 73%
rename from tests/unit/easydiffraction/experiments/experiment/test_enums.py
rename to tests/unit/easydiffraction/datablocks/experiment/item/test_enums.py
index 2df2dbb5..dff44c0f 100644
--- a/tests/unit/easydiffraction/experiments/experiment/test_enums.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_enums.py
@@ -2,15 +2,15 @@
 # SPDX-License-Identifier: BSD-3-Clause
 
 def test_module_import():
-    import easydiffraction.experiments.experiment.enums as MUT
+    import easydiffraction.datablocks.experiment.item.enums as MUT
 
-    expected_module_name = 'easydiffraction.experiments.experiment.enums'
+    expected_module_name = 'easydiffraction.datablocks.experiment.item.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
+    import easydiffraction.datablocks.experiment.item.enums as MUT
 
     assert MUT.SampleFormEnum.default() in list(MUT.SampleFormEnum)
     assert MUT.ScatteringTypeEnum.default() in list(MUT.ScatteringTypeEnum)
diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py
new file mode 100644
index 00000000..8dac91b6
--- /dev/null
+++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py
@@ -0,0 +1,28 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+def test_module_import():
+    import easydiffraction.datablocks.experiment.item.factory as MUT
+
+    expected_module_name = 'easydiffraction.datablocks.experiment.item.factory'
+    actual_module_name = MUT.__name__
+    assert expected_module_name == actual_module_name
+
+
+def test_experiment_factory_from_scratch():
+    import easydiffraction.datablocks.experiment.item.factory as EF
+    from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+    from easydiffraction.datablocks.experiment.item.enums import RadiationProbeEnum
+    from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+    from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
+
+    ex = EF.ExperimentFactory.from_scratch(
+        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
+
diff --git a/tests/unit/easydiffraction/experiments/experiment/test_total_pd.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_total_pd.py
similarity index 61%
rename from tests/unit/easydiffraction/experiments/experiment/test_total_pd.py
rename to tests/unit/easydiffraction/datablocks/experiment/item/test_total_pd.py
index 8f10c692..78d40afc 100644
--- a/tests/unit/easydiffraction/experiments/experiment/test_total_pd.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_total_pd.py
@@ -4,21 +4,21 @@
 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
+from easydiffraction.datablocks.experiment.categories.experiment_type import ExperimentType
+from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+from easydiffraction.datablocks.experiment.item.enums import RadiationProbeEnum
+from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
+from easydiffraction.datablocks.experiment.item.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,
-    )
+    et = ExperimentType()
+    et._set_sample_form(SampleFormEnum.POWDER.value)
+    et._set_beam_mode(BeamModeEnum.CONSTANT_WAVELENGTH.value)
+    et._set_radiation_probe(RadiationProbeEnum.NEUTRON.value)
+    et._set_scattering_type(ScatteringTypeEnum.TOTAL.value)
+    return et
 
 
 def test_load_ascii_data_pdf(tmp_path: pytest.TempPathFactory):
diff --git a/tests/unit/easydiffraction/experiments/test_experiments.py b/tests/unit/easydiffraction/datablocks/experiment/test_collection.py
similarity index 74%
rename from tests/unit/easydiffraction/experiments/test_experiments.py
rename to tests/unit/easydiffraction/datablocks/experiment/test_collection.py
index 89dd874e..5165f141 100644
--- a/tests/unit/easydiffraction/experiments/test_experiments.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/test_collection.py
@@ -2,16 +2,16 @@
 # SPDX-License-Identifier: BSD-3-Clause
 
 def test_module_import():
-    import easydiffraction.experiments.experiments as MUT
+    import easydiffraction.datablocks.experiment.collection as MUT
 
-    expected_module_name = 'easydiffraction.experiments.experiments'
+    expected_module_name = 'easydiffraction.datablocks.experiment.collection'
     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
+    from easydiffraction.datablocks.experiment.item.base import ExperimentBase
+    from easydiffraction.datablocks.experiment.collection import Experiments
 
     class DummyType:
         def __init__(self):
@@ -26,8 +26,8 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None:
             pass
 
     exps = Experiments()
-    exps.add(experiment=DummyExp('a'))
-    exps.add(experiment=DummyExp('b'))
+    exps.add(DummyExp('a'))
+    exps.add(DummyExp('b'))
     exps.show_names()
     out = capsys.readouterr().out
     assert 'Defined experiments' in out
diff --git a/tests/unit/easydiffraction/sample_models/categories/test_space_group.py b/tests/unit/easydiffraction/datablocks/structure/categories/test_space_group.py
similarity index 88%
rename from tests/unit/easydiffraction/sample_models/categories/test_space_group.py
rename to tests/unit/easydiffraction/datablocks/structure/categories/test_space_group.py
index d1a0238c..89786b9e 100644
--- a/tests/unit/easydiffraction/sample_models/categories/test_space_group.py
+++ b/tests/unit/easydiffraction/datablocks/structure/categories/test_space_group.py
@@ -1,7 +1,7 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
 
-from easydiffraction.sample_models.categories.space_group import SpaceGroup
+from easydiffraction.datablocks.structure.categories.space_group import SpaceGroup
 
 
 def test_space_group_name_updates_it_code():
diff --git a/tests/unit/easydiffraction/sample_models/sample_model/test_base.py b/tests/unit/easydiffraction/datablocks/structure/item/test_base.py
similarity index 50%
rename from tests/unit/easydiffraction/sample_models/sample_model/test_base.py
rename to tests/unit/easydiffraction/datablocks/structure/item/test_base.py
index 61c4b3db..33bb878b 100644
--- a/tests/unit/easydiffraction/sample_models/sample_model/test_base.py
+++ b/tests/unit/easydiffraction/datablocks/structure/item/test_base.py
@@ -1,12 +1,12 @@
 # SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
 # SPDX-License-Identifier: BSD-3-Clause
 
-from easydiffraction.sample_models.sample_model.base import SampleModelBase
+from easydiffraction.datablocks.structure.item.base import Structure
 
 
-def test_sample_model_base_str_and_properties():
-    m = SampleModelBase(name='m1')
+def test_structure_base_str_and_properties():
+    m = Structure(name='m1')
     m.name = 'm2'
     assert m.name == 'm2'
     s = str(m)
-    assert 'SampleModelBase' in s or '<' in s
+    assert 'Structure' in s or '<' in s
diff --git a/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py b/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py
new file mode 100644
index 00000000..d1b50776
--- /dev/null
+++ b/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from easydiffraction.datablocks.structure.item.factory import StructureFactory
+
+
+def test_from_scratch():
+    m = StructureFactory.from_scratch(name='abc')
+    assert m.name == 'abc'
diff --git a/tests/unit/easydiffraction/sample_models/test_sample_models.py b/tests/unit/easydiffraction/datablocks/structure/test_collection.py
similarity index 70%
rename from tests/unit/easydiffraction/sample_models/test_sample_models.py
rename to tests/unit/easydiffraction/datablocks/structure/test_collection.py
index eed0ea84..9955a1e3 100644
--- a/tests/unit/easydiffraction/sample_models/test_sample_models.py
+++ b/tests/unit/easydiffraction/datablocks/structure/test_collection.py
@@ -3,4 +3,4 @@
 
 import pytest
 
-from easydiffraction.sample_models.sample_models import SampleModels
+from easydiffraction.datablocks.structure.collection import Structures
diff --git a/tests/unit/easydiffraction/display/plotters/test_base.py b/tests/unit/easydiffraction/display/plotters/test_base.py
index 1b81f7c8..d1c2d561 100644
--- a/tests/unit/easydiffraction/display/plotters/test_base.py
+++ b/tests/unit/easydiffraction/display/plotters/test_base.py
@@ -32,8 +32,8 @@ def test_default_engine_switches_with_notebook(monkeypatch):
 
 def test_default_axes_labels_keys_present():
     import easydiffraction.display.plotters.base as pb
-    from easydiffraction.experiments.experiment.enums import SampleFormEnum
-    from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum
+    from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+    from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
 
     # Powder Bragg
     assert (SampleFormEnum.POWDER, ScatteringTypeEnum.BRAGG, pb.XAxisType.TWO_THETA) in pb.DEFAULT_AXES_LABELS
diff --git a/tests/unit/easydiffraction/display/test_plotting.py b/tests/unit/easydiffraction/display/test_plotting.py
index a27c7e47..15caf344 100644
--- a/tests/unit/easydiffraction/display/test_plotting.py
+++ b/tests/unit/easydiffraction/display/test_plotting.py
@@ -53,9 +53,9 @@ def test_plotter_factory_supported_and_unsupported():
 
 
 def test_plotter_error_paths_and_filtering(capsys):
-    from easydiffraction.experiments.experiment.enums import BeamModeEnum
-    from easydiffraction.experiments.experiment.enums import SampleFormEnum
-    from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum
+    from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+    from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+    from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
     from easydiffraction.display.plotting import Plotter
 
     class Ptn:
@@ -113,9 +113,9 @@ def test_plotter_routes_to_ascii_plotter(monkeypatch):
     import numpy as np
 
     import easydiffraction.display.plotters.ascii as ascii_mod
-    from easydiffraction.experiments.experiment.enums import BeamModeEnum
-    from easydiffraction.experiments.experiment.enums import SampleFormEnum
-    from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum
+    from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum
+    from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
+    from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum
     from easydiffraction.display.plotting import Plotter
 
     called = {}
diff --git a/tests/unit/easydiffraction/experiments/categories/background/test_enums.py b/tests/unit/easydiffraction/experiments/categories/background/test_enums.py
deleted file mode 100644
index 4a64fb29..00000000
--- a/tests/unit/easydiffraction/experiments/categories/background/test_enums.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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
deleted file mode 100644
index 57308f95..00000000
--- a/tests/unit/easydiffraction/experiments/categories/background/test_factory.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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/instrument/test_factory.py b/tests/unit/easydiffraction/experiments/categories/instrument/test_factory.py
deleted file mode 100644
index 0c6066b0..00000000
--- a/tests/unit/easydiffraction/experiments/categories/instrument/test_factory.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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 {'CwlPdInstrument', 'CwlScInstrument', 'TofPdInstrument', 'TofScInstrument'}
-
-    # Valid combinations
-    inst2 = InstrumentFactory.create(ScatteringTypeEnum.BRAGG, BeamModeEnum.CONSTANT_WAVELENGTH)
-    assert inst2.__class__.__name__ == 'CwlPdInstrument'
-    inst3 = InstrumentFactory.create(ScatteringTypeEnum.BRAGG, BeamModeEnum.TIME_OF_FLIGHT)
-    assert inst3.__class__.__name__ == 'TofPdInstrument'
-
-    # 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/peak/test_factory.py b/tests/unit/easydiffraction/experiments/categories/peak/test_factory.py
deleted file mode 100644
index bc474949..00000000
--- a/tests/unit/easydiffraction/experiments/categories/peak/test_factory.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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/test_experiment_type.py b/tests/unit/easydiffraction/experiments/categories/test_experiment_type.py
deleted file mode 100644
index 429eb419..00000000
--- a/tests/unit/easydiffraction/experiments/categories/test_experiment_type.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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/experiment/test_base.py b/tests/unit/easydiffraction/experiments/experiment/test_base.py
deleted file mode 100644
index bd781afb..00000000
--- a/tests/unit/easydiffraction/experiments/experiment/test_base.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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_factory.py b/tests/unit/easydiffraction/experiments/experiment/test_factory.py
deleted file mode 100644
index d7838b04..00000000
--- a/tests/unit/easydiffraction/experiments/experiment/test_factory.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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/io/cif/test_serialize.py b/tests/unit/easydiffraction/io/cif/test_serialize.py
index d85126e8..48f044a1 100644
--- a/tests/unit/easydiffraction/io/cif/test_serialize.py
+++ b/tests/unit/easydiffraction/io/cif/test_serialize.py
@@ -73,7 +73,7 @@ def as_cif(self):
     class Project:
         def __init__(self):
             self.info = Obj('I')
-            self.sample_models = None
+            self.structures = None
             self.experiments = Obj('E')
             self.analysis = None
             self.summary = None
diff --git a/tests/unit/easydiffraction/io/cif/test_serialize_more.py b/tests/unit/easydiffraction/io/cif/test_serialize_more.py
index c36a01ab..54f345ee 100644
--- a/tests/unit/easydiffraction/io/cif/test_serialize_more.py
+++ b/tests/unit/easydiffraction/io/cif/test_serialize_more.py
@@ -117,6 +117,8 @@ def as_cif(self):
 
 def test_analysis_to_cif_renders_all_sections():
     import easydiffraction.io.cif.serialize as MUT
+    from easydiffraction.analysis.categories.fit_mode import FitMode
+    from easydiffraction.analysis.categories.joint_fit_experiments import JointFitExperiments
 
     class Obj:
         def __init__(self, t):
@@ -127,16 +129,14 @@ def as_cif(self):
             return self._t
 
     class A:
-        current_calculator = 'cryspy engine'
-        current_minimizer = 'lmfit (leastsq)'
-        fit_mode = 'single'
+        current_minimizer = 'lmfit'
+        fit_mode = FitMode()
+        joint_fit_experiments = JointFitExperiments()
         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 lines[0].startswith('_analysis.fitting_engine') and 'lmfit' in lines[0]
+    assert lines[1].startswith('_analysis.fit_mode') and 'single' in lines[1]
     assert 'ALIASES' in out and 'CONSTRAINTS' in out
diff --git a/tests/unit/easydiffraction/project/test_project.py b/tests/unit/easydiffraction/project/test_project.py
index 046a44f0..1a949fc5 100644
--- a/tests/unit/easydiffraction/project/test_project.py
+++ b/tests/unit/easydiffraction/project/test_project.py
@@ -7,3 +7,16 @@ def test_module_import():
     expected_module_name = 'easydiffraction.project.project'
     actual_module_name = MUT.__name__
     assert expected_module_name == actual_module_name
+
+
+def test_project_help(capsys):
+    from easydiffraction.project.project import Project
+
+    p = Project()
+    p.help()
+    out = capsys.readouterr().out
+    assert "Help for 'Project'" in out
+    assert 'experiments' in out
+    assert 'analysis' in out
+    assert 'summary' in out
+
diff --git a/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py b/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py
index d3e0b2d6..5cb103b8 100644
--- a/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py
+++ b/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py
@@ -2,15 +2,14 @@
 # SPDX-License-Identifier: BSD-3-Clause
 
 def test_project_load_prints_and_sets_path(tmp_path, capsys):
+    import pytest
+
     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
+    with pytest.raises(NotImplementedError, match='not implemented yet'):
+        p.load(str(dir_path))
 
 
 def test_summary_show_project_info_wraps_description(capsys):
diff --git a/tests/unit/easydiffraction/project/test_project_save.py b/tests/unit/easydiffraction/project/test_project_save.py
index 2879f85c..eb662dfc 100644
--- a/tests/unit/easydiffraction/project/test_project_save.py
+++ b/tests/unit/easydiffraction/project/test_project_save.py
@@ -35,5 +35,5 @@ def test_project_save_as_writes_core_files(tmp_path, monkeypatch):
     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 / 'structures').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
deleted file mode 100644
index db4c22ed..00000000
--- a/tests/unit/easydiffraction/sample_models/categories/test_atom_sites.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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()
-    sites.add(label='O1', type_symbol='O')
-    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
deleted file mode 100644
index 8de1da42..00000000
--- a/tests/unit/easydiffraction/sample_models/categories/test_cell.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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/sample_model/test_factory.py b/tests/unit/easydiffraction/sample_models/sample_model/test_factory.py
deleted file mode 100644
index aa9fd9a0..00000000
--- a/tests/unit/easydiffraction/sample_models/sample_model/test_factory.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# SPDX-FileCopyrightText: 2021-2026 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/summary/test_summary.py b/tests/unit/easydiffraction/summary/test_summary.py
index e6388cbf..29da4e28 100644
--- a/tests/unit/easydiffraction/summary/test_summary.py
+++ b/tests/unit/easydiffraction/summary/test_summary.py
@@ -23,11 +23,10 @@ class Info:
     class Project:
         def __init__(self):
             self.info = Info()
-            self.sample_models = {}  # empty mapping to exercise loops safely
+            self.structures = {}  # empty mapping to exercise loops safely
             self.experiments = {}  # empty mapping to exercise loops safely
 
             class A:
-                current_calculator = 'cryspy'
                 current_minimizer = 'lmfit'
 
                 class R:
diff --git a/tests/unit/easydiffraction/summary/test_summary_details.py b/tests/unit/easydiffraction/summary/test_summary_details.py
index 8965c5ed..0e9fcf04 100644
--- a/tests/unit/easydiffraction/summary/test_summary_details.py
+++ b/tests/unit/easydiffraction/summary/test_summary_details.py
@@ -4,7 +4,7 @@
 def test_summary_crystallographic_and_experimental_sections(capsys):
     from easydiffraction.summary.summary import Summary
 
-    # Build a minimal sample model stub that exposes required attributes
+    # Build a minimal structure stub that exposes required attributes
     class Val:
         def __init__(self, v):
             self.value = v
@@ -96,11 +96,10 @@ class Info:
     class Project:
         def __init__(self):
             self.info = Info()
-            self.sample_models = {'phaseA': Model()}
+            self.structures = {'phaseA': Model()}
             self.experiments = {'exp1': Expt()}
 
             class A:
-                current_calculator = 'cryspy'
                 current_minimizer = 'lmfit'
 
                 class R:
diff --git a/tests/unit/easydiffraction/test___init__.py b/tests/unit/easydiffraction/test___init__.py
index 714ffc4d..5eb8c38f 100644
--- a/tests/unit/easydiffraction/test___init__.py
+++ b/tests/unit/easydiffraction/test___init__.py
@@ -14,7 +14,7 @@ def test_lazy_attributes_resolve_and_are_accessible():
     # 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')
+    assert hasattr(ed, 'StructureFactory')
 
     # Access utility functions from utils via lazy getattr
     assert callable(ed.show_version)
diff --git a/tmp/__validator.py b/tmp/__validator.py
new file mode 100644
index 00000000..359ecd1a
--- /dev/null
+++ b/tmp/__validator.py
@@ -0,0 +1,87 @@
+# %%
+import easydiffraction as ed
+import numpy as np
+
+# %%
+project = ed.Project()
+
+#
+
+project.experiments.add_from_data_path(name='aaa', data_path=23)
+
+exit()
+
+# %%
+model_path = ed.download_data(id=1, destination='data')
+project.structures.add_from_cif_path(cif_path=model_path)
+
+#project.structures.add_from_scratch(name='qwe')
+#project.structures['qwe'] = 6
+#print(project.structures['qwe'].name.value)
+#struct = project.structures['qwe']
+#struct.cell = "cell"
+#print(struct.cell)
+
+#exit()
+
+
+
+# %%
+expt_path = ed.download_data(id=2, destination='data')
+project.experiments.add_from_cif_path(cif_path=expt_path)
+#project.experiments.add_from_cif_path(cif_path=77)
+
+#expt = ed.ExperimentFactory.from_scratch(name='expt', scattering_type='total2')
+#print(expt)
+exit()
+
+print('\nStructure:')
+print(project.structures['lbco'])
+
+print('\nExperiment:')
+print(project.experiments['hrpt'])
+
+
+exit()
+
+
+
+# %%
+#sample = project.sample_models.get(id=1)
+#sample = project.sample_models.get(name='Fe2O3')
+sample = project.sample_models['lbco']
+
+# %%
+print()
+print("=== Testing cell.length_a ===")
+sample.cell.length_a = 3
+print(sample.cell.length_a, type(sample.cell.length_a.value))
+sample.cell.length_a = np.int64(4)
+print(sample.cell.length_a, type(sample.cell.length_a.value))
+sample.cell.length_a = np.float64(5.5)
+print(sample.cell.length_a, type(sample.cell.length_a.value))
+###sample.cell.length_a = "6.0"
+###sample.cell.length_a = -7.0
+###sample.cell.length_a = None
+print(sample.cell.length_a, type(sample.cell.length_a.value))
+
+# %%
+print()
+print("=== Testing space_group ===")
+sample.space_group.name_h_m = 'P n m a'
+print(sample.space_group.name_h_m)
+print(sample.space_group.it_coordinate_system_code)
+###sample.space_group.name_h_m = 'P x y z'
+print(sample.space_group.name_h_m)
+###sample.space_group.name_h_m = 4500
+print(sample.space_group.name_h_m)
+sample.space_group.it_coordinate_system_code = 'cab'
+print(sample.space_group.it_coordinate_system_code)
+
+# %%
+print()
+print("=== Testing atom_sites ===")
+sample.atom_sites.add(label2='O5', type_symbol='O')
+
+# %%
+sample.show_as_cif()
\ No newline at end of file
diff --git a/tmp/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py b/tmp/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py
index cf21bcc8..18db2b22 100644
--- a/tmp/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py
+++ b/tmp/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py
@@ -375,22 +375,22 @@
 #
 # #### Set Calculator
 #
-# Show supported calculation engines.
+# Show supported calculation engines for this experiment.
 
 # %%
-project.analysis.show_supported_calculators()
+project.experiments['hrpt'].show_supported_calculator_types()
 
 # %% [markdown]
-# Show current calculation engine.
+# Show current calculation engine for this experiment.
 
 # %%
-project.analysis.show_current_calculator()
+project.experiments['hrpt'].show_current_calculator_type()
 
 # %% [markdown]
 # Select the desired calculation engine.
 
 # %%
-project.analysis.current_calculator = 'cryspy'
+project.experiments['hrpt'].calculator_type = 'cryspy'
 
 # %% [markdown]
 # #### Show Calculated Data
@@ -435,23 +435,9 @@
 
 # %% [markdown]
 # #### Set Fit Mode
-#
-# Show supported fit modes.
-
-# %%
-project.analysis.show_available_fit_modes()
-
-# %% [markdown]
-# Show current fit mode.
-
-# %%
-project.analysis.show_current_fit_mode()
-
-# %% [markdown]
-# Select desired fit mode.
 
 # %%
-project.analysis.fit_mode = 'single'
+project.analysis.fit_mode.mode = 'single'
 
 # %% [markdown]
 # #### Set Minimizer
diff --git a/tmp/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py b/tmp/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py
index b4ee7e15..e7e6c1d6 100644
--- a/tmp/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py
+++ b/tmp/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py
@@ -220,12 +220,6 @@
 # This section outlines the analysis process, including how to configure
 # calculation and fitting engines.
 #
-# #### Set Calculator
-
-# %%
-project.analysis.current_calculator = 'cryspy'
-
-# %% [markdown]
 # #### Set Minimizer
 
 # %%
diff --git a/tmp/short7.py b/tmp/short7.py
index dfec2cd8..285704e9 100644
--- a/tmp/short7.py
+++ b/tmp/short7.py
@@ -76,7 +76,6 @@ def single_fit_neutron_pd_cwl_lbco() -> None:
     expt.show_as_cif()
 
     # Prepare for fitting
-    project.analysis.current_calculator = 'cryspy'
     project.analysis.current_minimizer = 'lmfit (leastsq)'
 
     # ------------ 1st fitting ------------
diff --git a/tutorials/data/ed-3.xye b/tutorials/data/ed-3.xye
new file mode 100644
index 00000000..0b9b63e3
--- /dev/null
+++ b/tutorials/data/ed-3.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/tutorials/ed-1.py b/tutorials/ed-1.py
index 38bacfda..51e4e8e6 100644
--- a/tutorials/ed-1.py
+++ b/tutorials/ed-1.py
@@ -2,8 +2,8 @@
 # # Structure Refinement: LBCO, HRPT
 #
 # This minimalistic example is designed to show how Rietveld refinement
-# of a crystal structure can be performed when both the sample model and
-# experiment are defined using CIF files.
+# can be performed when both the crystal structure and experiment
+# parameters are defined using CIF files.
 #
 # For this example, constant-wavelength neutron powder diffraction data
 # for La0.5Ba0.5CoO3 from HRPT at PSI is used.
@@ -33,14 +33,14 @@
 project = ed.Project()
 
 # %% [markdown]
-# ## Step 2: Define Sample Model
+# ## Step 2: Define Crystal Structure
 
 # %%
 # Download CIF file from repository
-model_path = ed.download_data(id=1, destination='data')
+structure_path = ed.download_data(id=1, destination='data')
 
 # %%
-project.sample_models.add(cif_path=model_path)
+project.structures.add_from_cif_path(structure_path)
 
 # %% [markdown]
 # ## Step 3: Define Experiment
@@ -50,7 +50,7 @@
 expt_path = ed.download_data(id=2, destination='data')
 
 # %%
-project.experiments.add(cif_path=expt_path)
+project.experiments.add_from_cif_path(expt_path)
 
 # %% [markdown]
 # ## Step 4: Perform Analysis
diff --git a/tutorials/ed-10.py b/tutorials/ed-10.py
index f3253429..1cad89ab 100644
--- a/tutorials/ed-10.py
+++ b/tutorials/ed-10.py
@@ -21,16 +21,16 @@
 project = ed.Project()
 
 # %% [markdown]
-# ## Add Sample Model
+# ## Add Structure
 
 # %%
-project.sample_models.add(name='ni')
+project.structures.create(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.structures['ni'].space_group.name_h_m = 'F m -3 m'
+project.structures['ni'].space_group.it_coordinate_system_code = '1'
+project.structures['ni'].cell.length_a = 3.52387
+project.structures['ni'].atom_sites.create(
     label='Ni',
     type_symbol='Ni',
     fract_x=0.0,
@@ -47,7 +47,7 @@
 data_path = ed.download_data(id=6, destination='data')
 
 # %%
-project.experiments.add(
+project.experiments.add_from_data_path(
     name='pdf',
     data_path=data_path,
     sample_form='powder',
@@ -57,7 +57,7 @@
 )
 
 # %%
-project.experiments['pdf'].linked_phases.add(id='ni', scale=1.0)
+project.experiments['pdf'].linked_phases.create(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
@@ -69,8 +69,8 @@
 # ## Select Fitting Parameters
 
 # %%
-project.sample_models['ni'].cell.length_a.free = True
-project.sample_models['ni'].atom_sites['Ni'].b_iso.free = True
+project.structures['ni'].cell.length_a.free = True
+project.structures['ni'].atom_sites['Ni'].b_iso.free = True
 
 # %%
 project.experiments['pdf'].linked_phases['ni'].scale.free = True
@@ -81,7 +81,6 @@
 # ## Run Fitting
 
 # %%
-project.analysis.current_calculator = 'pdffit'
 project.analysis.fit()
 project.analysis.show_fit_results()
 
diff --git a/tutorials/ed-11.py b/tutorials/ed-11.py
index 4a5b3176..a16dbec7 100644
--- a/tutorials/ed-11.py
+++ b/tutorials/ed-11.py
@@ -30,17 +30,17 @@
 project.plotter.x_max = 40
 
 # %% [markdown]
-# ## Add Sample Model
+# ## Add Structure
 
 # %%
-project.sample_models.add(name='si')
+project.structures.create(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(
+structure = project.structures['si']
+structure.space_group.name_h_m.value = 'F d -3 m'
+structure.space_group.it_coordinate_system_code = '1'
+structure.cell.length_a = 5.43146
+structure.atom_sites.create(
     label='Si',
     type_symbol='Si',
     fract_x=0,
@@ -57,7 +57,7 @@
 data_path = ed.download_data(id=5, destination='data')
 
 # %%
-project.experiments.add(
+project.experiments.add_from_data_path(
     name='nomad',
     data_path=data_path,
     sample_form='powder',
@@ -68,7 +68,7 @@
 
 # %%
 experiment = project.experiments['nomad']
-experiment.linked_phases.add(id='si', scale=1.0)
+experiment.linked_phases.create(id='si', scale=1.0)
 experiment.peak.damp_q = 0.02
 experiment.peak.broad_q = 0.03
 experiment.peak.cutoff_q = 35.0
@@ -80,8 +80,8 @@
 # ## Select Fitting Parameters
 
 # %%
-project.sample_models['si'].cell.length_a.free = True
-project.sample_models['si'].atom_sites['Si'].b_iso.free = True
+project.structures['si'].cell.length_a.free = True
+project.structures['si'].atom_sites['Si'].b_iso.free = True
 experiment.linked_phases['si'].scale.free = True
 
 # %%
@@ -94,7 +94,6 @@
 # ## Run Fitting
 
 # %%
-project.analysis.current_calculator = 'pdffit'
 project.analysis.fit()
 project.analysis.show_fit_results()
 
diff --git a/tutorials/ed-12.py b/tutorials/ed-12.py
index c40cc5ad..b6701709 100644
--- a/tutorials/ed-12.py
+++ b/tutorials/ed-12.py
@@ -34,16 +34,16 @@
 project.plotter.x_max = 30.0
 
 # %% [markdown]
-# ## Add Sample Model
+# ## Add Structure
 
 # %%
-project.sample_models.add(name='nacl')
+project.structures.create(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.structures['nacl'].space_group.name_h_m = 'F m -3 m'
+project.structures['nacl'].space_group.it_coordinate_system_code = '1'
+project.structures['nacl'].cell.length_a = 5.62
+project.structures['nacl'].atom_sites.create(
     label='Na',
     type_symbol='Na',
     fract_x=0,
@@ -52,7 +52,7 @@
     wyckoff_letter='a',
     b_iso=1.0,
 )
-project.sample_models['nacl'].atom_sites.add(
+project.structures['nacl'].atom_sites.create(
     label='Cl',
     type_symbol='Cl',
     fract_x=0.5,
@@ -69,7 +69,7 @@
 data_path = ed.download_data(id=4, destination='data')
 
 # %%
-project.experiments.add(
+project.experiments.add_from_data_path(
     name='xray_pdf',
     data_path=data_path,
     sample_form='powder',
@@ -96,15 +96,15 @@
 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.create(id='nacl', scale=0.5)
 
 # %% [markdown]
 # ## Select Fitting Parameters
 
 # %%
-project.sample_models['nacl'].cell.length_a.free = True
-project.sample_models['nacl'].atom_sites['Na'].b_iso.free = True
-project.sample_models['nacl'].atom_sites['Cl'].b_iso.free = True
+project.structures['nacl'].cell.length_a.free = True
+project.structures['nacl'].atom_sites['Na'].b_iso.free = True
+project.structures['nacl'].atom_sites['Cl'].b_iso.free = True
 
 # %%
 project.experiments['xray_pdf'].linked_phases['nacl'].scale.free = True
@@ -115,7 +115,6 @@
 # ## Run Fitting
 
 # %%
-project.analysis.current_calculator = 'pdffit'
 project.analysis.fit()
 project.analysis.show_fit_results()
 
diff --git a/tutorials/ed-13.py b/tutorials/ed-13.py
index 1411ba42..e29aad49 100644
--- a/tutorials/ed-13.py
+++ b/tutorials/ed-13.py
@@ -52,11 +52,11 @@
 #
 # In EasyDiffraction, a project serves as a container for all
 # information related to the analysis of a specific experiment or set of
-# experiments. It enables you to organize your data, experiments, sample
-# models, and fitting parameters in a structured manner. You can think
-# of it as a folder containing all the essential details about your
-# analysis. The project also allows us to visualize both the measured
-# and calculated diffraction patterns, among other things.
+# experiments. It enables you to organize your data, experiments,
+# crystal structures, and fitting parameters in an organized manner. You
+# can think of it as a folder containing all the essential details about
+# your analysis. The project also allows us to visualize both the
+# measured and calculated diffraction patterns, among other things.
 
 # %% [markdown] tags=["doc-link"]
 # 📖 See
@@ -120,7 +120,7 @@
 # for more details about different types of experiments.
 
 # %%
-project_1.experiments.add(
+project_1.experiments.add_from_data_path(
     name='sim_si',
     data_path=si_xye_path,
     sample_form='powder',
@@ -185,8 +185,8 @@
 # for more details about excluding regions from the measured data.
 
 # %%
-project_1.experiments['sim_si'].excluded_regions.add(id='1', start=0, end=55000)
-project_1.experiments['sim_si'].excluded_regions.add(id='2', start=105500, end=200000)
+project_1.experiments['sim_si'].excluded_regions.create(id='1', start=0, end=55000)
+project_1.experiments['sim_si'].excluded_regions.create(id='2', start=105500, end=200000)
 
 # %% [markdown]
 # To visualize the effect of excluding the high TOF region, we can plot
@@ -355,25 +355,25 @@
 
 # %%
 project_1.experiments['sim_si'].background_type = 'line-segment'
-project_1.experiments['sim_si'].background.add(id='1', x=50000, y=0.01)
-project_1.experiments['sim_si'].background.add(id='2', x=60000, y=0.01)
-project_1.experiments['sim_si'].background.add(id='3', x=70000, y=0.01)
-project_1.experiments['sim_si'].background.add(id='4', x=80000, y=0.01)
-project_1.experiments['sim_si'].background.add(id='5', x=90000, y=0.01)
-project_1.experiments['sim_si'].background.add(id='6', x=100000, y=0.01)
-project_1.experiments['sim_si'].background.add(id='7', x=110000, y=0.01)
+project_1.experiments['sim_si'].background.create(id='1', x=50000, y=0.01)
+project_1.experiments['sim_si'].background.create(id='2', x=60000, y=0.01)
+project_1.experiments['sim_si'].background.create(id='3', x=70000, y=0.01)
+project_1.experiments['sim_si'].background.create(id='4', x=80000, y=0.01)
+project_1.experiments['sim_si'].background.create(id='5', x=90000, y=0.01)
+project_1.experiments['sim_si'].background.create(id='6', x=100000, y=0.01)
+project_1.experiments['sim_si'].background.create(id='7', x=110000, y=0.01)
 
 # %% [markdown]
-# ### 🧩 Create a Sample Model – Si
+# ### 🧩 Create a Structure – Si
 #
-# After setting up the experiment, we need to create a sample model that
+# After setting up the experiment, we need to create a structure that
 # describes the crystal structure of the sample being analyzed.
 #
-# In this case, we will create a sample model for silicon (Si) with a
-# cubic crystal structure. The sample model contains information about
+# In this case, we will create a structure for silicon (Si) with a
+# cubic crystal structure. The structure contains information about
 # the space group, lattice parameters, atomic positions of the atoms in
 # the unit cell, atom types, occupancies and atomic displacement
-# parameters. The sample model is essential for the fitting process, as
+# parameters. The structure is essential for the fitting process, as
 # it is used to calculate the expected diffraction pattern.
 #
 # EasyDiffraction refines the crystal structure of the sample, but does
@@ -435,54 +435,54 @@
 
 # %% [markdown]
 # As with adding the experiment in the previous step, we will create a
-# default sample model and then modify its parameters to match the Si
+# default structure and then modify its parameters to match the Si
 # structure.
 
 # %% [markdown] tags=["doc-link"]
 # 📖 See
-# [documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/model/)
-# for more details about sample models and their purpose in the data
+# [documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/structure/)
+# for more details about structures and their purpose in the data
 # analysis workflow.
 
 # %% [markdown]
-# #### Add Sample Model
+# #### Add Structure
 
 # %%
-project_1.sample_models.add(name='si')
+project_1.structures.create(name='si')
 
 # %% [markdown]
 # #### Set Space Group
 
 # %% [markdown] tags=["doc-link"]
 # 📖 See
-# [documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/model/#space-group-category)
+# [documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/structure/#space-group-category)
 # for more details about the space group.
 
 # %%
-project_1.sample_models['si'].space_group.name_h_m = 'F d -3 m'
-project_1.sample_models['si'].space_group.it_coordinate_system_code = '2'
+project_1.structures['si'].space_group.name_h_m = 'F d -3 m'
+project_1.structures['si'].space_group.it_coordinate_system_code = '2'
 
 # %% [markdown]
 # #### Set Lattice Parameters
 
 # %% [markdown] tags=["doc-link"]
 # 📖 See
-# [documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/model/#cell-category)
+# [documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/structure/#cell-category)
 # for more details about the unit cell parameters.
 
 # %%
-project_1.sample_models['si'].cell.length_a = 5.43
+project_1.structures['si'].cell.length_a = 5.43
 
 # %% [markdown]
 # #### Set Atom Sites
 
 # %% [markdown] tags=["doc-link"]
 # 📖 See
-# [documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/model/#atom-sites-category)
+# [documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/structure/#atom-sites-category)
 # for more details about the atom sites category.
 
 # %%
-project_1.sample_models['si'].atom_sites.add(
+project_1.structures['si'].atom_sites.create(
     label='Si',
     type_symbol='Si',
     fract_x=0,
@@ -493,29 +493,29 @@
 )
 
 # %% [markdown]
-# ### 🔗 Assign Sample Model to Experiment
+# ### 🔗 Assign Structure to Experiment
 #
-# Now we need to assign, or link, this sample model to the experiment
+# Now we need to assign, or link, this structure to the experiment
 # created above. This linked crystallographic phase will be used to
 # calculate the expected diffraction pattern based on the crystal
-# structure defined in the sample model.
+# structure defined in the structure.
 
 # %% [markdown] tags=["doc-link"]
 # 📖 See
 # [documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#linked-phases-category)
-# for more details about linking a sample model to an experiment.
+# for more details about linking a structure to an experiment.
 
 # %%
-project_1.experiments['sim_si'].linked_phases.add(id='si', scale=1.0)
+project_1.experiments['sim_si'].linked_phases.create(id='si', scale=1.0)
 
 # %% [markdown]
 # ### 🚀 Analyze and Fit the Data
 #
-# After setting up the experiment and sample model, we can now analyze
+# After setting up the experiment and structure, we can now analyze
 # the measured diffraction pattern and perform the fit. Building on the
 # analogies from the EasyScience library and the previous notebooks, we
 # can say that all the parameters we introduced earlier — those defining
-# the sample model (crystal structure parameters) and the experiment
+# the structure (crystal structure parameters) and the experiment
 # (instrument, background, and peak profile parameters) — together form
 # the complete set of parameters that can be refined during the fitting
 # process.
@@ -528,12 +528,12 @@
 # %% [markdown] **Reminder:**
 #
 # The fitting process involves comparing the measured diffraction
-# pattern with the calculated diffraction pattern based on the sample
-# model and instrument parameters. The goal is to adjust the parameters
-# of the sample model and the experiment to minimize the difference
-# between the measured and calculated diffraction patterns. This is done
-# by refining the parameters of the sample model and the instrument
-# settings to achieve a better fit.
+# pattern with the calculated diffraction pattern based on the crystal
+# structure and instrument parameters. The goal is to adjust the
+# parameters of the structure and the experiment to minimize the
+# difference between the measured and calculated diffraction patterns.
+# This is done by refining the parameters of the structure and the
+# instrument settings to achieve a better fit.
 
 # %% [markdown] tags=["doc-link"]
 # 📖 See
@@ -593,7 +593,7 @@
 #
 # Before performing the fit, we can visually compare the measured
 # diffraction pattern with the calculated diffraction pattern based on
-# the initial parameters of the sample model and the instrument. This
+# the initial parameters of the structure and the instrument. This
 # provides an indication of how well the initial parameters match the
 # measured data. The `plot_meas_vs_calc` method of the project allows
 # this comparison.
@@ -689,7 +689,7 @@
 #
 # Before moving on, we can save the project to disk for later use. This
 # will preserve the entire project structure, including experiments,
-# sample models, and fitting results. The project is saved into a
+# structures, and fitting results. The project is saved into a
 # directory specified by the `dir_path` attribute of the project object.
 
 # %%
@@ -753,7 +753,7 @@
 # reduced data file is missing.
 lbco_xye_path = ed.download_data(id=18, destination=data_dir)
 
-project_2.experiments.add(
+project_2.experiments.add_from_data_path(
     name='sim_lbco',
     data_path=lbco_xye_path,
     sample_form='powder',
@@ -783,8 +783,8 @@
 # %% tags=["solution", "hide-input"]
 project_2.plot_meas(expt_name='sim_lbco')
 
-project_2.experiments['sim_lbco'].excluded_regions.add(id='1', start=0, end=55000)
-project_2.experiments['sim_lbco'].excluded_regions.add(id='2', start=105500, end=200000)
+project_2.experiments['sim_lbco'].excluded_regions.create(id='1', start=0, end=55000)
+project_2.experiments['sim_lbco'].excluded_regions.create(id='2', start=105500, end=200000)
 
 project_2.plot_meas(expt_name='sim_lbco')
 
@@ -861,20 +861,20 @@
 
 # %% tags=["solution", "hide-input"]
 project_2.experiments['sim_lbco'].background_type = 'line-segment'
-project_2.experiments['sim_lbco'].background.add(id='1', x=50000, y=0.2)
-project_2.experiments['sim_lbco'].background.add(id='2', x=60000, y=0.2)
-project_2.experiments['sim_lbco'].background.add(id='3', x=70000, y=0.2)
-project_2.experiments['sim_lbco'].background.add(id='4', x=80000, y=0.2)
-project_2.experiments['sim_lbco'].background.add(id='5', x=90000, y=0.2)
-project_2.experiments['sim_lbco'].background.add(id='6', x=100000, y=0.2)
-project_2.experiments['sim_lbco'].background.add(id='7', x=110000, y=0.2)
+project_2.experiments['sim_lbco'].background.create(id='1', x=50000, y=0.2)
+project_2.experiments['sim_lbco'].background.create(id='2', x=60000, y=0.2)
+project_2.experiments['sim_lbco'].background.create(id='3', x=70000, y=0.2)
+project_2.experiments['sim_lbco'].background.create(id='4', x=80000, y=0.2)
+project_2.experiments['sim_lbco'].background.create(id='5', x=90000, y=0.2)
+project_2.experiments['sim_lbco'].background.create(id='6', x=100000, y=0.2)
+project_2.experiments['sim_lbco'].background.create(id='7', x=110000, y=0.2)
 
 # %% [markdown]
-# ### 🧩 Exercise 3: Define a Sample Model – LBCO
+# ### 🧩 Exercise 3: Define a Structure – LBCO
 #
-# The LBSO structure is not as simple as the Si model, as it contains
+# The LBSO structure is not as simple as the Si one, as it contains
 # multiple atoms in the unit cell. It is not in COD, so we give you the
-# structural parameters in CIF format to create the sample model.
+# structural parameters in CIF format to create the structure.
 #
 # Note that those parameters are not necessarily the most accurate ones,
 # but they are a good starting point for the fit. The aim of the study
@@ -914,7 +914,7 @@
 # Note that the `occupancy` of the La and Ba atoms is 0.5
 # and those atoms are located in the same position (0, 0, 0) in the unit
 # cell. This means that an extra attribute `occupancy` needs to be set
-# for those atoms later in the sample model.
+# for those atoms later in the structure.
 #
 # We model the La/Ba site using the virtual crystal approximation. In
 # this approach, the scattering is taken as a weighted average of La and
@@ -936,9 +936,9 @@
 #    of the random case and the extra peaks of the ordered case.
 
 # %% [markdown]
-# #### Exercise 3.1: Create Sample Model
+# #### Exercise 3.1: Create Structure
 #
-# Add a sample model for LBCO to the project. The sample model
+# Add a structure for LBCO to the project. The structure
 # parameters will be set in the next exercises.
 
 # %% [markdown]
@@ -946,19 +946,19 @@
 
 # %% [markdown] tags=["dmsc-school-hint"]
 # You can use the same approach as in the previous part of the notebook,
-# but this time you need to use the model name corresponding to the LBCO
+# but this time you need to use the name corresponding to the LBCO
 # structure, e.g. 'lbco'.
 
 # %% [markdown]
 # **Solution:**
 
 # %% tags=["solution", "hide-input"]
-project_2.sample_models.add(name='lbco')
+project_2.structures.create(name='lbco')
 
 # %% [markdown]
 # #### Exercise 3.2: Set Space Group
 #
-# Set the space group for the LBCO sample model.
+# Set the space group for the LBCO structure.
 
 # %% [markdown]
 # **Hint:**
@@ -971,13 +971,13 @@
 # **Solution:**
 
 # %% tags=["solution", "hide-input"]
-project_2.sample_models['lbco'].space_group.name_h_m = 'P m -3 m'
-project_2.sample_models['lbco'].space_group.it_coordinate_system_code = '1'
+project_2.structures['lbco'].space_group.name_h_m = 'P m -3 m'
+project_2.structures['lbco'].space_group.it_coordinate_system_code = '1'
 
 # %% [markdown]
 # #### Exercise 3.3: Set Lattice Parameters
 #
-# Set the lattice parameters for the LBCO sample model.
+# Set the lattice parameters for the LBCO structure.
 
 # %% [markdown]
 # **Hint:**
@@ -989,25 +989,25 @@
 # **Solution:**
 
 # %% tags=["solution", "hide-input"]
-project_2.sample_models['lbco'].cell.length_a = 3.88
+project_2.structures['lbco'].cell.length_a = 3.88
 
 # %% [markdown]
 # #### Exercise 3.4: Set Atom Sites
 #
-# Set the atom sites for the LBCO sample model.
+# Set the atom sites for the LBCO structure.
 
 # %% [markdown]
 # **Hint:**
 
 # %% [markdown] tags=["dmsc-school-hint"]
 # Use the atom sites from the CIF data. You can use the `add` method of
-# the `atom_sites` attribute of the sample model to add the atom sites.
+# the `atom_sites` attribute of the structure to add the atom sites.
 
 # %% [markdown]
 # **Solution:**
 
 # %% tags=["solution", "hide-input"]
-project_2.sample_models['lbco'].atom_sites.add(
+project_2.structures['lbco'].atom_sites.create(
     label='La',
     type_symbol='La',
     fract_x=0,
@@ -1017,7 +1017,7 @@
     b_iso=0.95,
     occupancy=0.5,
 )
-project_2.sample_models['lbco'].atom_sites.add(
+project_2.structures['lbco'].atom_sites.create(
     label='Ba',
     type_symbol='Ba',
     fract_x=0,
@@ -1027,7 +1027,7 @@
     b_iso=0.95,
     occupancy=0.5,
 )
-project_2.sample_models['lbco'].atom_sites.add(
+project_2.structures['lbco'].atom_sites.create(
     label='Co',
     type_symbol='Co',
     fract_x=0.5,
@@ -1036,7 +1036,7 @@
     wyckoff_letter='b',
     b_iso=0.80,
 )
-project_2.sample_models['lbco'].atom_sites.add(
+project_2.structures['lbco'].atom_sites.create(
     label='O',
     type_symbol='O',
     fract_x=0,
@@ -1047,22 +1047,22 @@
 )
 
 # %% [markdown]
-# ### 🔗 Exercise 4: Assign Sample Model to Experiment
+# ### 🔗 Exercise 4: Assign Structure to Experiment
 #
-# Now assign the LBCO sample model to the experiment created above.
+# Now assign the LBCO structure to the experiment created above.
 
 # %% [markdown]
 # **Hint:**
 
 # %% [markdown] tags=["dmsc-school-hint"]
-# Use the `linked_phases` attribute of the experiment to link the sample
-# model.
+# Use the `linked_phases` attribute of the experiment to link the
+# crystal structure.
 
 # %% [markdown]
 # **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.create(id='lbco', scale=1.0)
 
 # %% [markdown]
 # ### 🚀 Exercise 5: Analyze and Fit the Data
@@ -1176,7 +1176,7 @@
 # **Solution:**
 
 # %% tags=["solution", "hide-input"]
-project_2.sample_models['lbco'].cell.length_a.free = True
+project_2.structures['lbco'].cell.length_a.free = True
 
 project_2.analysis.fit()
 project_2.analysis.show_fit_results()
@@ -1348,14 +1348,14 @@
 # confirm this hypothesis.
 
 # %% tags=["solution", "hide-input"]
-project_1.plot_meas_vs_calc(expt_name='sim_si',  x='d_spacing', x_min=1, x_max=1.7)
+project_1.plot_meas_vs_calc(expt_name='sim_si', x='d_spacing', x_min=1, x_max=1.7)
 project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing', x_min=1, x_max=1.7)
 
 # %% [markdown]
-# #### Exercise 5.10: Create a Second Sample Model – Si as Impurity
+# #### Exercise 5.10: Create a Second Structure – Si as Impurity
 #
-# Create a second sample model for the Si phase, which is the impurity
-# phase identified in the previous step. Link this sample model to the
+# Create a second structure for the Si phase, which is the impurity
+# phase identified in the previous step. Link this structure to the
 # LBCO experiment.
 
 # %% [markdown]
@@ -1363,7 +1363,7 @@
 
 # %% [markdown] tags=["dmsc-school-hint"]
 # You can use the same approach as in the previous part of the notebook,
-# but this time you need to create a sample model for Si and link it to
+# but this time you need to create a structure for Si and link it to
 # the LBCO experiment.
 
 # %% [markdown]
@@ -1371,15 +1371,15 @@
 
 # %% tags=["solution", "hide-input"]
 # Set Space Group
-project_2.sample_models.add(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'
+project_2.structures.create(name='si')
+project_2.structures['si'].space_group.name_h_m = 'F d -3 m'
+project_2.structures['si'].space_group.it_coordinate_system_code = '2'
 
 # Set Lattice Parameters
-project_2.sample_models['si'].cell.length_a = 5.43
+project_2.structures['si'].cell.length_a = 5.43
 
 # Set Atom Sites
-project_2.sample_models['si'].atom_sites.add(
+project_2.structures['si'].atom_sites.create(
     label='Si',
     type_symbol='Si',
     fract_x=0,
@@ -1389,8 +1389,8 @@
     b_iso=0.89,
 )
 
-# Assign Sample Model to Experiment
-project_2.experiments['sim_lbco'].linked_phases.add(id='si', scale=1.0)
+# Assign Structure to Experiment
+project_2.experiments['sim_lbco'].linked_phases.create(id='si', scale=1.0)
 
 # %% [markdown]
 # #### Exercise 5.11: Refine the Scale of the Si Phase
@@ -1443,7 +1443,7 @@
 #
 # To review the analysis results, you can generate and print a summary
 # report using the `show_report()` method, as demonstrated in the cell
-# below. The report includes parameters related to the sample model and
+# below. The report includes parameters related to the structure and
 # the experiment, such as the refined unit cell parameter `a` of LBCO.
 #
 # Information about the crystal or magnetic structure, along with
diff --git a/tutorials/ed-14.py b/tutorials/ed-14.py
index 65d8e98c..eaa3da2a 100644
--- a/tutorials/ed-14.py
+++ b/tutorials/ed-14.py
@@ -18,29 +18,29 @@
 project = ed.Project()
 
 # %% [markdown]
-# ## Step 2: Define Sample Model
+# ## Step 2: Define Structure
 
 # %%
 # Download CIF file from repository
-model_path = ed.download_data(id=20, destination='data')
+structure_path = ed.download_data(id=20, destination='data')
 
 # %%
-project.sample_models.add(cif_path=model_path)
+project.structures.add_from_cif_path(structure_path)
 
 # %%
-project.sample_models.show_names()
+project.structures.show_names()
 
 # %%
-sample_model = project.sample_models['tbti']
+structure = project.structures['tbti']
 
 # %%
-sample_model.atom_sites['Tb'].b_iso.value = 0.0
-sample_model.atom_sites['Ti'].b_iso.value = 0.0
-sample_model.atom_sites['O1'].b_iso.value = 0.0
-sample_model.atom_sites['O2'].b_iso.value = 0.0
+structure.atom_sites['Tb'].b_iso = 0.0
+structure.atom_sites['Ti'].b_iso = 0.0
+structure.atom_sites['O1'].b_iso = 0.0
+structure.atom_sites['O2'].b_iso = 0.0
 
 # %%
-sample_model.show_as_cif()
+structure.show_as_cif()
 
 # %% [markdown]
 # ## Step 3: Define Experiment
@@ -49,7 +49,7 @@
 data_path = ed.download_data(id=19, destination='data')
 
 # %%
-project.experiments.add(
+project.experiments.add_from_data_path(
     name='heidi',
     data_path=data_path,
     sample_form='single crystal',
diff --git a/tutorials/ed-15.py b/tutorials/ed-15.py
index c3e178ff..4ae4933a 100644
--- a/tutorials/ed-15.py
+++ b/tutorials/ed-15.py
@@ -18,23 +18,23 @@
 project = ed.Project()
 
 # %% [markdown]
-# ## Step 2: Define Sample Model
+# ## Step 2: Define Structure
 
 # %%
 # Download CIF file from repository
-model_path = ed.download_data(id=21, destination='data')
+structure_path = ed.download_data(id=21, destination='data')
 
 # %%
-project.sample_models.add(cif_path=model_path)
+project.structures.add_from_cif_path(structure_path)
 
 # %%
-project.sample_models.show_names()
+project.structures.show_names()
 
 # %%
-sample_model = project.sample_models['taurine']
+structure = project.structures['taurine']
 
 # %%
-# sample_model.show_as_cif()
+# structure.show_as_cif()
 
 # %% [markdown]
 # ## Step 3: Define Experiment
@@ -43,7 +43,7 @@
 data_path = ed.download_data(id=22, destination='data')
 
 # %%
-project.experiments.add(
+project.experiments.add_from_data_path(
     name='senju',
     data_path=data_path,
     sample_form='single crystal',
diff --git a/tutorials/ed-16.py b/tutorials/ed-16.py
new file mode 100644
index 00000000..e57f8449
--- /dev/null
+++ b/tutorials/ed-16.py
@@ -0,0 +1,259 @@
+# %% [markdown]
+# # Joint Refinement: Si, Bragg + PDF
+#
+# This example demonstrates a joint refinement of the Si crystal
+# structure combining Bragg diffraction and pair distribution function
+# (PDF) analysis. The Bragg experiment uses time-of-flight neutron
+# powder diffraction data from SEPD at Argonne, while the PDF
+# experiment uses data from NOMAD at SNS. A single shared Si structure
+# is refined simultaneously against both datasets.
+
+# %% [markdown]
+# ## Import Library
+
+# %%
+from easydiffraction import ExperimentFactory
+from easydiffraction import Project
+from easydiffraction import StructureFactory
+from easydiffraction import download_data
+
+# %% [markdown]
+# ## Define Structure
+#
+# A single Si structure is shared between the Bragg and PDF
+# experiments. Structural parameters refined against both datasets
+# simultaneously.
+#
+# #### Create Structure
+
+# %%
+structure = StructureFactory.from_scratch(name='si')
+
+# %% [markdown]
+# #### Set Space Group
+
+# %%
+structure.space_group.name_h_m = 'F d -3 m'
+structure.space_group.it_coordinate_system_code = '1'
+
+# %% [markdown]
+# #### Set Unit Cell
+
+# %%
+structure.cell.length_a = 5.42
+
+# %% [markdown]
+# #### Set Atom Sites
+
+# %%
+structure.atom_sites.create(
+    label='Si',
+    type_symbol='Si',
+    fract_x=0,
+    fract_y=0,
+    fract_z=0,
+    wyckoff_letter='a',
+    b_iso=0.2,
+)
+
+# %% [markdown]
+# ## Define Experiments
+#
+# Two experiments are defined: one for Bragg diffraction and one for
+# PDF analysis. Both are linked to the same Si structure.
+#
+# ### Experiment 1: Bragg (SEPD, TOF)
+#
+# #### Download Data
+
+# %%
+bragg_data_path = download_data(id=7, destination='data')
+
+# %% [markdown]
+# #### Create Experiment
+
+# %%
+bragg_expt = ExperimentFactory.from_data_path(
+    name='sepd', data_path=bragg_data_path, beam_mode='time-of-flight'
+)
+
+# %% [markdown]
+# #### Set Instrument
+
+# %%
+bragg_expt.instrument.setup_twotheta_bank = 144.845
+bragg_expt.instrument.calib_d_to_tof_offset = -9.2
+bragg_expt.instrument.calib_d_to_tof_linear = 7476.91
+bragg_expt.instrument.calib_d_to_tof_quad = -1.54
+
+# %% [markdown]
+# #### Set Peak Profile
+
+# %%
+bragg_expt.peak_profile_type = 'pseudo-voigt * ikeda-carpenter'
+bragg_expt.peak.broad_gauss_sigma_0 = 5.0
+bragg_expt.peak.broad_gauss_sigma_1 = 45.0
+bragg_expt.peak.broad_gauss_sigma_2 = 1.0
+bragg_expt.peak.broad_mix_beta_0 = 0.04221
+bragg_expt.peak.broad_mix_beta_1 = 0.00946
+bragg_expt.peak.asym_alpha_0 = 0.0
+bragg_expt.peak.asym_alpha_1 = 0.5971
+
+# %% [markdown]
+# #### Set Background
+
+# %%
+bragg_expt.background_type = 'line-segment'
+for x in range(0, 35000, 5000):
+    bragg_expt.background.create(id=str(x), x=x, y=200)
+
+# %% [markdown]
+# #### Set Linked Phases
+
+# %%
+bragg_expt.linked_phases.create(id='si', scale=13.0)
+
+# %% [markdown]
+# ### Experiment 2: PDF (NOMAD, TOF)
+#
+# #### Download Data
+
+# %%
+pdf_data_path = download_data(id=5, destination='data')
+
+# %% [markdown]
+# #### Create Experiment
+
+# %%
+pdf_expt = ExperimentFactory.from_data_path(
+    name='nomad',
+    data_path=pdf_data_path,
+    beam_mode='time-of-flight',
+    scattering_type='total',
+)
+
+# %% [markdown]
+# #### Set Peak Profile (PDF Parameters)
+
+# %%
+pdf_expt.peak.damp_q = 0.02
+pdf_expt.peak.broad_q = 0.02
+pdf_expt.peak.cutoff_q = 35.0
+pdf_expt.peak.sharp_delta_1 = 0.001
+pdf_expt.peak.sharp_delta_2 = 4.0
+pdf_expt.peak.damp_particle_diameter = 0
+
+# %% [markdown]
+# #### Set Linked Phases
+
+# %%
+pdf_expt.linked_phases.create(id='si', scale=1.0)
+
+# %% [markdown]
+# ## Define Project
+#
+# The project object manages the shared structure, both experiments,
+# and the analysis.
+#
+# #### Create Project
+
+# %%
+project = Project()
+
+# %% [markdown]
+# #### Add Structure
+
+# %%
+project.structures.add(structure)
+
+# %% [markdown]
+# #### Add Experiments
+
+# %%
+project.experiments.add(bragg_expt)
+project.experiments.add(pdf_expt)
+
+# %% [markdown]
+# ## Perform Analysis
+#
+# This section shows the joint analysis process. The calculator is
+# auto-resolved per experiment: CrysPy for Bragg, PDFfit for PDF.
+#
+# #### Set Fit Mode and Weights
+
+# %%
+project.analysis.fit_mode.mode = 'joint'
+project.analysis.joint_fit_experiments.create(id='sepd', weight=0.7)
+project.analysis.joint_fit_experiments.create(id='nomad', weight=0.3)
+
+# %% [markdown]
+# #### Set Minimizer
+
+# %%
+project.analysis.current_minimizer = 'lmfit'
+
+# %% [markdown]
+# #### Plot Measured vs Calculated (Before Fit)
+
+# %%
+project.plot_meas_vs_calc(expt_name='sepd', show_residual=False)
+
+# %%
+project.plot_meas_vs_calc(expt_name='nomad', show_residual=False)
+
+# %% [markdown]
+# #### Set Fitting Parameters
+#
+# Shared structural parameters are refined against both datasets
+# simultaneously.
+
+# %%
+structure.cell.length_a.free = True
+structure.atom_sites['Si'].b_iso.free = True
+
+# %% [markdown]
+# Bragg experiment parameters.
+
+# %%
+bragg_expt.linked_phases['si'].scale.free = True
+bragg_expt.instrument.calib_d_to_tof_offset.free = True
+bragg_expt.peak.broad_gauss_sigma_0.free = True
+bragg_expt.peak.broad_gauss_sigma_1.free = True
+bragg_expt.peak.broad_gauss_sigma_2.free = True
+for point in bragg_expt.background:
+    point.y.free = True
+
+# %% [markdown]
+# PDF experiment parameters.
+
+# %%
+pdf_expt.linked_phases['si'].scale.free = True
+pdf_expt.peak.damp_q.free = True
+pdf_expt.peak.broad_q.free = True
+pdf_expt.peak.sharp_delta_1.free = True
+pdf_expt.peak.sharp_delta_2.free = True
+
+# %% [markdown]
+# #### Show Free Parameters
+
+# %%
+project.analysis.show_free_params()
+
+# %% [markdown]
+# #### Run Fitting
+
+# %%
+project.analysis.fit()
+project.analysis.show_fit_results()
+
+# %% [markdown]
+# #### Plot Measured vs Calculated (After Fit)
+
+# %%
+project.plot_meas_vs_calc(expt_name='sepd', show_residual=False)
+
+# %%
+project.plot_meas_vs_calc(expt_name='nomad', show_residual=False)
+
+
+# %%
diff --git a/tutorials/ed-2.py b/tutorials/ed-2.py
index 4390b65b..3c8d033e 100644
--- a/tutorials/ed-2.py
+++ b/tutorials/ed-2.py
@@ -2,9 +2,9 @@
 # # Structure Refinement: LBCO, HRPT
 #
 # This minimalistic example is designed to show how Rietveld refinement
-# of a crystal structure can be performed when both the sample model and
-# experiment are defined directly in code. Only the experimentally
-# measured data is loaded from an external file.
+# can be performed when both the crystal structure and experiment are
+# defined directly in code. Only the experimentally measured data is
+# loaded from an external file.
 #
 # For this example, constant-wavelength neutron powder diffraction data
 # for La0.5Ba0.5CoO3 from HRPT at PSI is used.
@@ -33,23 +33,23 @@
 project = ed.Project()
 
 # %% [markdown]
-# ## Step 2: Define Sample Model
+# ## Step 2: Define Structure
 
 # %%
-project.sample_models.add(name='lbco')
+project.structures.create(name='lbco')
 
 # %%
-sample_model = project.sample_models['lbco']
+structure = project.structures['lbco']
 
 # %%
-sample_model.space_group.name_h_m = 'P m -3 m'
-sample_model.space_group.it_coordinate_system_code = '1'
+structure.space_group.name_h_m = 'P m -3 m'
+structure.space_group.it_coordinate_system_code = '1'
 
 # %%
-sample_model.cell.length_a = 3.88
+structure.cell.length_a = 3.88
 
 # %%
-sample_model.atom_sites.add(
+structure.atom_sites.create(
     label='La',
     type_symbol='La',
     fract_x=0,
@@ -59,7 +59,7 @@
     b_iso=0.5,
     occupancy=0.5,
 )
-sample_model.atom_sites.add(
+structure.atom_sites.create(
     label='Ba',
     type_symbol='Ba',
     fract_x=0,
@@ -69,7 +69,7 @@
     b_iso=0.5,
     occupancy=0.5,
 )
-sample_model.atom_sites.add(
+structure.atom_sites.create(
     label='Co',
     type_symbol='Co',
     fract_x=0.5,
@@ -78,7 +78,7 @@
     wyckoff_letter='b',
     b_iso=0.5,
 )
-sample_model.atom_sites.add(
+structure.atom_sites.create(
     label='O',
     type_symbol='O',
     fract_x=0,
@@ -95,7 +95,7 @@
 data_path = ed.download_data(id=3, destination='data')
 
 # %%
-project.experiments.add(
+project.experiments.add_from_data_path(
     name='hrpt',
     data_path=data_path,
     sample_form='powder',
@@ -117,29 +117,29 @@
 experiment.peak.broad_lorentz_y = 0.1
 
 # %%
-experiment.background.add(id='1', x=10, y=170)
-experiment.background.add(id='2', x=30, y=170)
-experiment.background.add(id='3', x=50, y=170)
-experiment.background.add(id='4', x=110, y=170)
-experiment.background.add(id='5', x=165, y=170)
+experiment.background.create(id='1', x=10, y=170)
+experiment.background.create(id='2', x=30, y=170)
+experiment.background.create(id='3', x=50, y=170)
+experiment.background.create(id='4', x=110, y=170)
+experiment.background.create(id='5', x=165, y=170)
 
 # %%
-experiment.excluded_regions.add(id='1', start=0, end=5)
-experiment.excluded_regions.add(id='2', start=165, end=180)
+experiment.excluded_regions.create(id='1', start=0, end=5)
+experiment.excluded_regions.create(id='2', start=165, end=180)
 
 # %%
-experiment.linked_phases.add(id='lbco', scale=10.0)
+experiment.linked_phases.create(id='lbco', scale=10.0)
 
 # %% [markdown]
 # ## Step 4: Perform Analysis
 
 # %%
-sample_model.cell.length_a.free = True
+structure.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
+structure.atom_sites['La'].b_iso.free = True
+structure.atom_sites['Ba'].b_iso.free = True
+structure.atom_sites['Co'].b_iso.free = True
+structure.atom_sites['O'].b_iso.free = True
 
 # %%
 experiment.instrument.calib_twotheta_offset.free = True
diff --git a/tutorials/ed-3.py b/tutorials/ed-3.py
index c0aefa1b..051faae0 100644
--- a/tutorials/ed-3.py
+++ b/tutorials/ed-3.py
@@ -8,12 +8,13 @@
 #
 # It is intended for users with minimal programming experience who want
 # to learn how to perform standard crystal structure fitting using
-# diffraction data. This script covers creating a project, adding sample
-# models and experiments, performing analysis, and refining parameters.
+# diffraction data. This script covers creating a project, adding
+# crystal structures and experiments, performing analysis, and refining
+# parameters.
 #
 # Only a single import of `easydiffraction` is required, and all
 # operations are performed through high-level components of the
-# `project` object, such as `project.sample_models`,
+# `project` object, such as `project.structures`,
 # `project.experiments`, and `project.analysis`. The `project` object is
 # the main container for all information.
 
@@ -84,26 +85,27 @@
 # project.plotter.engine = 'plotly'
 
 # %% [markdown]
-# ## Step 2: Define Sample Model
+# ## Step 2: Define Structure
 #
-# This section shows how to add sample models and modify their
+# This section shows how to add structures and modify their
 # parameters.
 
 # %% [markdown]
-# #### Add Sample Model
+# #### Add Structure
 
 # %%
-project.sample_models.add(name='lbco')
+project.structures.create(name='lbco')
 
 # %% [markdown]
-# #### Show Defined Sample Models
+# #### Show Defined Structures
 #
-# Show the names of the models added. These names are used to access the
-# model using the syntax: `project.sample_models['model_name']`. All
-# model parameters can be accessed via the `project` object.
+# Show the names of the crystal structures added. These names are used
+# to access the structure using the syntax:
+# `project.structures[name]`. All structure parameters can be accessed
+# via the `project` object.
 
 # %%
-project.sample_models.show_names()
+project.structures.show_names()
 
 # %% [markdown]
 # #### Set Space Group
@@ -111,8 +113,8 @@
 # Modify the default space group parameters.
 
 # %%
-project.sample_models['lbco'].space_group.name_h_m = 'P m -3 m'
-project.sample_models['lbco'].space_group.it_coordinate_system_code = '1'
+project.structures['lbco'].space_group.name_h_m = 'P m -3 m'
+project.structures['lbco'].space_group.it_coordinate_system_code = '1'
 
 # %% [markdown]
 # #### Set Unit Cell
@@ -120,15 +122,15 @@
 # Modify the default unit cell parameters.
 
 # %%
-project.sample_models['lbco'].cell.length_a = 3.88
+project.structures['lbco'].cell.length_a = 3.88
 
 # %% [markdown]
 # #### Set Atom Sites
 #
-# Add atom sites to the sample model.
+# Add atom sites to the structure.
 
 # %%
-project.sample_models['lbco'].atom_sites.add(
+project.structures['lbco'].atom_sites.create(
     label='La',
     type_symbol='La',
     fract_x=0,
@@ -138,7 +140,7 @@
     b_iso=0.5,
     occupancy=0.5,
 )
-project.sample_models['lbco'].atom_sites.add(
+project.structures['lbco'].atom_sites.create(
     label='Ba',
     type_symbol='Ba',
     fract_x=0,
@@ -148,7 +150,7 @@
     b_iso=0.5,
     occupancy=0.5,
 )
-project.sample_models['lbco'].atom_sites.add(
+project.structures['lbco'].atom_sites.create(
     label='Co',
     type_symbol='Co',
     fract_x=0.5,
@@ -157,7 +159,7 @@
     wyckoff_letter='b',
     b_iso=0.5,
 )
-project.sample_models['lbco'].atom_sites.add(
+project.structures['lbco'].atom_sites.create(
     label='O',
     type_symbol='O',
     fract_x=0,
@@ -168,21 +170,21 @@
 )
 
 # %% [markdown]
-# #### Show Sample Model as CIF
+# #### Show Structure as CIF
 
 # %%
-project.sample_models['lbco'].show_as_cif()
+project.structures['lbco'].show_as_cif()
 
 # %% [markdown]
-# #### Show Sample Model Structure
+# #### Show Structure Structure
 
 # %%
-project.sample_models['lbco'].show_structure()
+project.structures['lbco'].show()
 
 # %% [markdown]
 # #### Save Project State
 #
-# Save the project state after adding the sample model. This ensures
+# Save the project state after adding the structure. This ensures
 # that all changes are stored and can be accessed later. The project
 # state is saved in the directory specified during project creation.
 
@@ -193,7 +195,7 @@
 # ## Step 3: Define Experiment
 #
 # This section shows how to add experiments, configure their parameters,
-# and link the sample models defined in the previous step.
+# and link the structures defined in the previous step.
 
 # %% [markdown]
 # #### Download Measured Data
@@ -207,7 +209,7 @@
 # #### Add Diffraction Experiment
 
 # %%
-project.experiments.add(
+project.experiments.add_from_data_path(
     name='hrpt',
     data_path=data_path,
     sample_form='powder',
@@ -291,11 +293,11 @@
 # Add background points.
 
 # %%
-project.experiments['hrpt'].background.add(id='10', x=10, y=170)
-project.experiments['hrpt'].background.add(id='30', x=30, y=170)
-project.experiments['hrpt'].background.add(id='50', x=50, y=170)
-project.experiments['hrpt'].background.add(id='110', x=110, y=170)
-project.experiments['hrpt'].background.add(id='165', x=165, y=170)
+project.experiments['hrpt'].background.create(id='10', x=10, y=170)
+project.experiments['hrpt'].background.create(id='30', x=30, y=170)
+project.experiments['hrpt'].background.create(id='50', x=50, y=170)
+project.experiments['hrpt'].background.create(id='110', x=110, y=170)
+project.experiments['hrpt'].background.create(id='165', x=165, y=170)
 
 # %% [markdown]
 # Show current background points.
@@ -306,10 +308,10 @@
 # %% [markdown]
 # #### Set Linked Phases
 #
-# Link the sample model defined in the previous step to the experiment.
+# Link the structure defined in the previous step to the experiment.
 
 # %%
-project.experiments['hrpt'].linked_phases.add(id='lbco', scale=10.0)
+project.experiments['hrpt'].linked_phases.create(id='lbco', scale=10.0)
 
 # %% [markdown]
 # #### Show Experiment as CIF
@@ -331,22 +333,22 @@
 #
 # #### Set Calculator
 #
-# Show supported calculation engines.
+# Show supported calculation engines for this experiment.
 
 # %%
-project.analysis.show_supported_calculators()
+project.experiments['hrpt'].show_supported_calculator_types()
 
 # %% [markdown]
-# Show current calculation engine.
+# Show current calculation engine for this experiment.
 
 # %%
-project.analysis.show_current_calculator()
+project.experiments['hrpt'].show_current_calculator_type()
 
 # %% [markdown]
 # Select the desired calculation engine.
 
 # %%
-project.analysis.current_calculator = 'cryspy'
+project.experiments['hrpt'].calculator_type = 'cryspy'
 
 # %% [markdown]
 # #### Show Calculated Data
@@ -395,19 +397,19 @@
 # Show supported fit modes.
 
 # %%
-project.analysis.show_available_fit_modes()
+project.analysis.show_supported_fit_mode_types()
 
 # %% [markdown]
 # Show current fit mode.
 
 # %%
-project.analysis.show_current_fit_mode()
+project.analysis.show_current_fit_mode_type()
 
 # %% [markdown]
 # Select desired fit mode.
 
 # %%
-project.analysis.fit_mode = 'single'
+project.analysis.fit_mode.mode = 'single'
 
 # %% [markdown]
 # #### Set Minimizer
@@ -427,15 +429,15 @@
 # Select desired fitting engine.
 
 # %%
-project.analysis.current_minimizer = 'lmfit (leastsq)'
+project.analysis.current_minimizer = 'lmfit'
 
 # %% [markdown]
 # ### Perform Fit 1/5
 #
-# Set sample model parameters to be refined.
+# Set structure parameters to be refined.
 
 # %%
-project.sample_models['lbco'].cell.length_a.free = True
+project.structures['lbco'].cell.length_a.free = True
 
 # %% [markdown]
 # Set experiment parameters to be refined.
@@ -522,10 +524,10 @@
 # Set more parameters to be refined.
 
 # %%
-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
-project.sample_models['lbco'].atom_sites['O'].b_iso.free = True
+project.structures['lbco'].atom_sites['La'].b_iso.free = True
+project.structures['lbco'].atom_sites['Ba'].b_iso.free = True
+project.structures['lbco'].atom_sites['Co'].b_iso.free = True
+project.structures['lbco'].atom_sites['O'].b_iso.free = True
 
 # %% [markdown]
 # Show free parameters after selection.
@@ -563,20 +565,20 @@
 # Set aliases for parameters.
 
 # %%
-project.analysis.aliases.add(
+project.analysis.aliases.create(
     label='biso_La',
-    param_uid=project.sample_models['lbco'].atom_sites['La'].b_iso.uid,
+    param_uid=project.structures['lbco'].atom_sites['La'].b_iso.uid,
 )
-project.analysis.aliases.add(
+project.analysis.aliases.create(
     label='biso_Ba',
-    param_uid=project.sample_models['lbco'].atom_sites['Ba'].b_iso.uid,
+    param_uid=project.structures['lbco'].atom_sites['Ba'].b_iso.uid,
 )
 
 # %% [markdown]
 # Set constraints.
 
 # %%
-project.analysis.constraints.add(lhs_alias='biso_Ba', rhs_expr='biso_La')
+project.analysis.constraints.create(lhs_alias='biso_Ba', rhs_expr='biso_La')
 
 # %% [markdown]
 # Show defined constraints.
@@ -632,20 +634,20 @@
 # Set more aliases for parameters.
 
 # %%
-project.analysis.aliases.add(
+project.analysis.aliases.create(
     label='occ_La',
-    param_uid=project.sample_models['lbco'].atom_sites['La'].occupancy.uid,
+    param_uid=project.structures['lbco'].atom_sites['La'].occupancy.uid,
 )
-project.analysis.aliases.add(
+project.analysis.aliases.create(
     label='occ_Ba',
-    param_uid=project.sample_models['lbco'].atom_sites['Ba'].occupancy.uid,
+    param_uid=project.structures['lbco'].atom_sites['Ba'].occupancy.uid,
 )
 
 # %% [markdown]
 # Set more constraints.
 
 # %%
-project.analysis.constraints.add(
+project.analysis.constraints.create(
     lhs_alias='occ_Ba',
     rhs_expr='1 - occ_La',
 )
@@ -663,10 +665,10 @@
 project.analysis.apply_constraints()
 
 # %% [markdown]
-# Set sample model parameters to be refined.
+# Set structure parameters to be refined.
 
 # %%
-project.sample_models['lbco'].atom_sites['La'].occupancy.free = True
+project.structures['lbco'].atom_sites['La'].occupancy.free = True
 
 # %% [markdown]
 # Show free parameters after selection.
diff --git a/tutorials/ed-4.py b/tutorials/ed-4.py
index ce857a2a..3275deab 100644
--- a/tutorials/ed-4.py
+++ b/tutorials/ed-4.py
@@ -2,7 +2,7 @@
 # # Structure Refinement: PbSO4, NPD + XRD
 #
 # This example demonstrates a more advanced use of the EasyDiffraction
-# library by explicitly creating and configuring sample models and
+# library by explicitly creating and configuring structures and
 # experiments before adding them to a project. It could be more suitable
 # for users who are interested in creating custom workflows. This
 # tutorial provides minimal explanation and is intended for users
@@ -17,39 +17,39 @@
 # %%
 from easydiffraction import ExperimentFactory
 from easydiffraction import Project
-from easydiffraction import SampleModelFactory
+from easydiffraction import StructureFactory
 from easydiffraction import download_data
 
 # %% [markdown]
-# ## Define Sample Model
+# ## Define Structure
 #
-# This section shows how to add sample models and modify their
+# This section shows how to add structures and modify their
 # parameters.
 #
-# #### Create Sample Model
+# #### Create Structure
 
 # %%
-model = SampleModelFactory.create(name='pbso4')
+structure = StructureFactory.from_scratch(name='pbso4')
 
 # %% [markdown]
 # #### Set Space Group
 
 # %%
-model.space_group.name_h_m = 'P n m a'
+structure.space_group.name_h_m = 'P n m a'
 
 # %% [markdown]
 # #### Set Unit Cell
 
 # %%
-model.cell.length_a = 8.47
-model.cell.length_b = 5.39
-model.cell.length_c = 6.95
+structure.cell.length_a = 8.47
+structure.cell.length_b = 5.39
+structure.cell.length_c = 6.95
 
 # %% [markdown]
 # #### Set Atom Sites
 
 # %%
-model.atom_sites.add(
+structure.atom_sites.create(
     label='Pb',
     type_symbol='Pb',
     fract_x=0.1876,
@@ -58,7 +58,7 @@
     wyckoff_letter='c',
     b_iso=1.37,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='S',
     type_symbol='S',
     fract_x=0.0654,
@@ -67,7 +67,7 @@
     wyckoff_letter='c',
     b_iso=0.3777,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='O1',
     type_symbol='O',
     fract_x=0.9082,
@@ -76,7 +76,7 @@
     wyckoff_letter='c',
     b_iso=1.9764,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='O2',
     type_symbol='O',
     fract_x=0.1935,
@@ -85,7 +85,7 @@
     wyckoff_letter='c',
     b_iso=1.4456,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='O3',
     type_symbol='O',
     fract_x=0.0811,
@@ -100,7 +100,7 @@
 # ## Define Experiments
 #
 # This section shows how to add experiments, configure their parameters,
-# and link the sample models defined in the previous step.
+# and link the structures defined in the previous step.
 #
 # ### Experiment 1: npd
 #
@@ -113,7 +113,7 @@
 # #### Create Experiment
 
 # %%
-expt1 = ExperimentFactory.create(
+expt1 = ExperimentFactory.from_data_path(
     name='npd',
     data_path=data_path1,
     radiation_probe='neutron',
@@ -159,13 +159,13 @@
     ('7', 120.0, 244.4525),
     ('8', 153.0, 226.0595),
 ]:
-    expt1.background.add(id=id, x=x, y=y)
+    expt1.background.create(id=id, x=x, y=y)
 
 # %% [markdown]
 # #### Set Linked Phases
 
 # %%
-expt1.linked_phases.add(id='pbso4', scale=1.5)
+expt1.linked_phases.create(id='pbso4', scale=1.5)
 
 # %% [markdown]
 # ### Experiment 2: xrd
@@ -179,7 +179,7 @@
 # #### Create Experiment
 
 # %%
-expt2 = ExperimentFactory.create(
+expt2 = ExperimentFactory.from_data_path(
     name='xrd',
     data_path=data_path2,
     radiation_probe='xray',
@@ -209,7 +209,7 @@
 # Select background type.
 
 # %%
-expt2.background_type = 'chebyshev polynomial'
+expt2.background_type = 'chebyshev'
 
 # %% [markdown]
 # Add background points.
@@ -223,18 +223,18 @@
     ('5', 4, 54.552),
     ('6', 5, -20.661),
 ]:
-    expt2.background.add(id=id, order=x, coef=y)
+    expt2.background.create(id=id, order=x, coef=y)
 
 # %% [markdown]
 # #### Set Linked Phases
 
 # %%
-expt2.linked_phases.add(id='pbso4', scale=0.001)
+expt2.linked_phases.create(id='pbso4', scale=0.001)
 
 # %% [markdown]
 # ## Define Project
 #
-# The project object is used to manage sample models, experiments, and
+# The project object is used to manage structures, experiments, and
 # analysis.
 #
 # #### Create Project
@@ -243,17 +243,17 @@
 project = Project()
 
 # %% [markdown]
-# #### Add Sample Model
+# #### Add Structure
 
 # %%
-project.sample_models.add(sample_model=model)
+project.structures.add(structure)
 
 # %% [markdown]
 # #### Add Experiments
 
 # %%
-project.experiments.add(experiment=expt1)
-project.experiments.add(experiment=expt2)
+project.experiments.add(expt1)
+project.experiments.add(expt2)
 
 # %% [markdown]
 # ## Perform Analysis
@@ -261,32 +261,26 @@
 # This section outlines the analysis process, including how to configure
 # calculation and fitting engines.
 #
-# #### Set Calculator
-
-# %%
-project.analysis.current_calculator = 'cryspy'
-
-# %% [markdown]
 # #### Set Fit Mode
 
 # %%
-project.analysis.fit_mode = 'joint'
+project.analysis.fit_mode.mode = 'joint'
 
 # %% [markdown]
 # #### Set Minimizer
 
 # %%
-project.analysis.current_minimizer = 'lmfit (leastsq)'
+project.analysis.current_minimizer = 'lmfit'
 
 # %% [markdown]
 # #### Set Fitting Parameters
 #
-# Set sample model parameters to be optimized.
+# Set structure parameters to be optimized.
 
 # %%
-model.cell.length_a.free = True
-model.cell.length_b.free = True
-model.cell.length_c.free = True
+structure.cell.length_a.free = True
+structure.cell.length_b.free = True
+structure.cell.length_c.free = True
 
 # %% [markdown]
 # Set experiment parameters to be optimized.
diff --git a/tutorials/ed-5.py b/tutorials/ed-5.py
index 0e46baa8..f7a30da2 100644
--- a/tutorials/ed-5.py
+++ b/tutorials/ed-5.py
@@ -11,40 +11,40 @@
 # %%
 from easydiffraction import ExperimentFactory
 from easydiffraction import Project
-from easydiffraction import SampleModelFactory
+from easydiffraction import StructureFactory
 from easydiffraction import download_data
 
 # %% [markdown]
-# ## Define Sample Model
+# ## Define Structure
 #
-# This section shows how to add sample models and modify their
+# This section shows how to add structures and modify their
 # parameters.
 #
-# #### Create Sample Model
+# #### Create Structure
 
 # %%
-model = SampleModelFactory.create(name='cosio')
+structure = StructureFactory.from_scratch(name='cosio')
 
 # %% [markdown]
 # #### Set Space Group
 
 # %%
-model.space_group.name_h_m = 'P n m a'
-model.space_group.it_coordinate_system_code = 'abc'
+structure.space_group.name_h_m = 'P n m a'
+structure.space_group.it_coordinate_system_code = 'abc'
 
 # %% [markdown]
 # #### Set Unit Cell
 
 # %%
-model.cell.length_a = 10.3
-model.cell.length_b = 6.0
-model.cell.length_c = 4.8
+structure.cell.length_a = 10.3
+structure.cell.length_b = 6.0
+structure.cell.length_c = 4.8
 
 # %% [markdown]
 # #### Set Atom Sites
 
 # %%
-model.atom_sites.add(
+structure.atom_sites.create(
     label='Co1',
     type_symbol='Co',
     fract_x=0,
@@ -53,7 +53,7 @@
     wyckoff_letter='a',
     b_iso=0.5,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='Co2',
     type_symbol='Co',
     fract_x=0.279,
@@ -62,7 +62,7 @@
     wyckoff_letter='c',
     b_iso=0.5,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='Si',
     type_symbol='Si',
     fract_x=0.094,
@@ -71,7 +71,7 @@
     wyckoff_letter='c',
     b_iso=0.5,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='O1',
     type_symbol='O',
     fract_x=0.091,
@@ -80,7 +80,7 @@
     wyckoff_letter='c',
     b_iso=0.5,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='O2',
     type_symbol='O',
     fract_x=0.448,
@@ -89,7 +89,7 @@
     wyckoff_letter='c',
     b_iso=0.5,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='O3',
     type_symbol='O',
     fract_x=0.164,
@@ -103,7 +103,7 @@
 # ## Define Experiment
 #
 # This section shows how to add experiments, configure their parameters,
-# and link the sample models defined in the previous step.
+# and link the structures defined in the previous step.
 #
 # #### Download Measured Data
 
@@ -114,7 +114,7 @@
 # #### Create Experiment
 
 # %%
-expt = ExperimentFactory.create(name='d20', data_path=data_path)
+expt = ExperimentFactory.from_data_path(name='d20', data_path=data_path)
 
 # %% [markdown]
 # #### Set Instrument
@@ -135,31 +135,31 @@
 # #### Set Background
 
 # %%
-expt.background.add(id='1', x=8, y=500)
-expt.background.add(id='2', x=9, y=500)
-expt.background.add(id='3', x=10, y=500)
-expt.background.add(id='4', x=11, y=500)
-expt.background.add(id='5', x=12, y=500)
-expt.background.add(id='6', x=15, y=500)
-expt.background.add(id='7', x=25, y=500)
-expt.background.add(id='8', x=30, y=500)
-expt.background.add(id='9', x=50, y=500)
-expt.background.add(id='10', x=70, y=500)
-expt.background.add(id='11', x=90, y=500)
-expt.background.add(id='12', x=110, y=500)
-expt.background.add(id='13', x=130, y=500)
-expt.background.add(id='14', x=150, y=500)
+expt.background.create(id='1', x=8, y=500)
+expt.background.create(id='2', x=9, y=500)
+expt.background.create(id='3', x=10, y=500)
+expt.background.create(id='4', x=11, y=500)
+expt.background.create(id='5', x=12, y=500)
+expt.background.create(id='6', x=15, y=500)
+expt.background.create(id='7', x=25, y=500)
+expt.background.create(id='8', x=30, y=500)
+expt.background.create(id='9', x=50, y=500)
+expt.background.create(id='10', x=70, y=500)
+expt.background.create(id='11', x=90, y=500)
+expt.background.create(id='12', x=110, y=500)
+expt.background.create(id='13', x=130, y=500)
+expt.background.create(id='14', x=150, y=500)
 
 # %% [markdown]
 # #### Set Linked Phases
 
 # %%
-expt.linked_phases.add(id='cosio', scale=1.0)
+expt.linked_phases.create(id='cosio', scale=1.0)
 
 # %% [markdown]
 # ## Define Project
 #
-# The project object is used to manage the sample model, experiment, and
+# The project object is used to manage the structure, experiment, and
 # analysis.
 #
 # #### Create Project
@@ -176,16 +176,16 @@
 # project.plotter.engine = 'plotly'
 
 # %% [markdown]
-# #### Add Sample Model
+# #### Add Structure
 
 # %%
-project.sample_models.add(sample_model=model)
+project.structures.add(structure)
 
 # %% [markdown]
 # #### Add Experiment
 
 # %%
-project.experiments.add(experiment=expt)
+project.experiments.add(expt)
 
 # %% [markdown]
 # ## Perform Analysis
@@ -193,16 +193,10 @@
 # This section shows the analysis process, including how to set up
 # calculation and fitting engines.
 #
-# #### Set Calculator
-
-# %%
-project.analysis.current_calculator = 'cryspy'
-
-# %% [markdown]
 # #### Set Minimizer
 
 # %%
-project.analysis.current_minimizer = 'lmfit (leastsq)'
+project.analysis.current_minimizer = 'lmfit'
 
 # %% [markdown]
 # #### Plot Measured vs Calculated
@@ -217,28 +211,28 @@
 # #### Set Free Parameters
 
 # %%
-model.cell.length_a.free = True
-model.cell.length_b.free = True
-model.cell.length_c.free = True
-
-model.atom_sites['Co2'].fract_x.free = True
-model.atom_sites['Co2'].fract_z.free = True
-model.atom_sites['Si'].fract_x.free = True
-model.atom_sites['Si'].fract_z.free = True
-model.atom_sites['O1'].fract_x.free = True
-model.atom_sites['O1'].fract_z.free = True
-model.atom_sites['O2'].fract_x.free = True
-model.atom_sites['O2'].fract_z.free = True
-model.atom_sites['O3'].fract_x.free = True
-model.atom_sites['O3'].fract_y.free = True
-model.atom_sites['O3'].fract_z.free = True
-
-model.atom_sites['Co1'].b_iso.free = True
-model.atom_sites['Co2'].b_iso.free = True
-model.atom_sites['Si'].b_iso.free = True
-model.atom_sites['O1'].b_iso.free = True
-model.atom_sites['O2'].b_iso.free = True
-model.atom_sites['O3'].b_iso.free = True
+structure.cell.length_a.free = True
+structure.cell.length_b.free = True
+structure.cell.length_c.free = True
+
+structure.atom_sites['Co2'].fract_x.free = True
+structure.atom_sites['Co2'].fract_z.free = True
+structure.atom_sites['Si'].fract_x.free = True
+structure.atom_sites['Si'].fract_z.free = True
+structure.atom_sites['O1'].fract_x.free = True
+structure.atom_sites['O1'].fract_z.free = True
+structure.atom_sites['O2'].fract_x.free = True
+structure.atom_sites['O2'].fract_z.free = True
+structure.atom_sites['O3'].fract_x.free = True
+structure.atom_sites['O3'].fract_y.free = True
+structure.atom_sites['O3'].fract_z.free = True
+
+structure.atom_sites['Co1'].b_iso.free = True
+structure.atom_sites['Co2'].b_iso.free = True
+structure.atom_sites['Si'].b_iso.free = True
+structure.atom_sites['O1'].b_iso.free = True
+structure.atom_sites['O2'].b_iso.free = True
+structure.atom_sites['O3'].b_iso.free = True
 
 # %%
 expt.linked_phases['cosio'].scale.free = True
@@ -259,20 +253,20 @@
 # Set aliases for parameters.
 
 # %%
-project.analysis.aliases.add(
+project.analysis.aliases.create(
     label='biso_Co1',
-    param_uid=project.sample_models['cosio'].atom_sites['Co1'].b_iso.uid,
+    param_uid=project.structures['cosio'].atom_sites['Co1'].b_iso.uid,
 )
-project.analysis.aliases.add(
+project.analysis.aliases.create(
     label='biso_Co2',
-    param_uid=project.sample_models['cosio'].atom_sites['Co2'].b_iso.uid,
+    param_uid=project.structures['cosio'].atom_sites['Co2'].b_iso.uid,
 )
 
 # %% [markdown]
 # Set constraints.
 
 # %%
-project.analysis.constraints.add(
+project.analysis.constraints.create(
     lhs_alias='biso_Co2',
     rhs_expr='biso_Co1',
 )
diff --git a/tutorials/ed-6.py b/tutorials/ed-6.py
index 106a088c..e0339c91 100644
--- a/tutorials/ed-6.py
+++ b/tutorials/ed-6.py
@@ -11,40 +11,40 @@
 # %%
 from easydiffraction import ExperimentFactory
 from easydiffraction import Project
-from easydiffraction import SampleModelFactory
+from easydiffraction import StructureFactory
 from easydiffraction import download_data
 
 # %% [markdown]
-# ## Define Sample Model
+# ## Define Structure
 #
-# This section shows how to add sample models and modify their
+# This section shows how to add structures and modify their
 # parameters.
 #
-# #### Create Sample Model
+# #### Create Structure
 
 # %%
-model = SampleModelFactory.create(name='hs')
+structure = StructureFactory.from_scratch(name='hs')
 
 # %% [markdown]
 # #### Set Space Group
 
 # %%
-model.space_group.name_h_m = 'R -3 m'
-model.space_group.it_coordinate_system_code = 'h'
+structure.space_group.name_h_m = 'R -3 m'
+structure.space_group.it_coordinate_system_code = 'h'
 
 # %% [markdown]
 # #### Set Unit Cell
 
 
 # %%
-model.cell.length_a = 6.9
-model.cell.length_c = 14.1
+structure.cell.length_a = 6.9
+structure.cell.length_c = 14.1
 
 # %% [markdown]
 # #### Set Atom Sites
 
 # %%
-model.atom_sites.add(
+structure.atom_sites.create(
     label='Zn',
     type_symbol='Zn',
     fract_x=0,
@@ -53,7 +53,7 @@
     wyckoff_letter='b',
     b_iso=0.5,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='Cu',
     type_symbol='Cu',
     fract_x=0.5,
@@ -62,7 +62,7 @@
     wyckoff_letter='e',
     b_iso=0.5,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='O',
     type_symbol='O',
     fract_x=0.21,
@@ -71,7 +71,7 @@
     wyckoff_letter='h',
     b_iso=0.5,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='Cl',
     type_symbol='Cl',
     fract_x=0,
@@ -80,7 +80,7 @@
     wyckoff_letter='c',
     b_iso=0.5,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='H',
     type_symbol='2H',
     fract_x=0.13,
@@ -94,7 +94,7 @@
 # ## Define Experiment
 #
 # This section shows how to add experiments, configure their parameters,
-# and link the sample models defined in the previous step.
+# and link the structures defined in the previous step.
 #
 # #### Download Measured Data
 
@@ -105,7 +105,7 @@
 # #### Create Experiment
 
 # %%
-expt = ExperimentFactory.create(name='hrpt', data_path=data_path)
+expt = ExperimentFactory.from_data_path(name='hrpt', data_path=data_path)
 
 # %% [markdown]
 # #### Set Instrument
@@ -128,26 +128,26 @@
 # #### Set Background
 
 # %%
-expt.background.add(id='1', x=4.4196, y=500)
-expt.background.add(id='2', x=6.6207, y=500)
-expt.background.add(id='3', x=10.4918, y=500)
-expt.background.add(id='4', x=15.4634, y=500)
-expt.background.add(id='5', x=45.6041, y=500)
-expt.background.add(id='6', x=74.6844, y=500)
-expt.background.add(id='7', x=103.4187, y=500)
-expt.background.add(id='8', x=121.6311, y=500)
-expt.background.add(id='9', x=159.4116, y=500)
+expt.background.create(id='1', x=4.4196, y=500)
+expt.background.create(id='2', x=6.6207, y=500)
+expt.background.create(id='3', x=10.4918, y=500)
+expt.background.create(id='4', x=15.4634, y=500)
+expt.background.create(id='5', x=45.6041, y=500)
+expt.background.create(id='6', x=74.6844, y=500)
+expt.background.create(id='7', x=103.4187, y=500)
+expt.background.create(id='8', x=121.6311, y=500)
+expt.background.create(id='9', x=159.4116, y=500)
 
 # %% [markdown]
 # #### Set Linked Phases
 
 # %%
-expt.linked_phases.add(id='hs', scale=0.5)
+expt.linked_phases.create(id='hs', scale=0.5)
 
 # %% [markdown]
 # ## Define Project
 #
-# The project object is used to manage the sample model, experiment, and
+# The project object is used to manage the structure, experiment, and
 # analysis.
 #
 # #### Create Project
@@ -164,16 +164,16 @@
 # project.plotter.engine = 'plotly'
 
 # %% [markdown]
-# #### Add Sample Model
+# #### Add Structure
 
 # %%
-project.sample_models.add(sample_model=model)
+project.structures.add(structure)
 
 # %% [markdown]
 # #### Add Experiment
 
 # %%
-project.experiments.add(experiment=expt)
+project.experiments.add(expt)
 
 # %% [markdown]
 # ## Perform Analysis
@@ -181,16 +181,10 @@
 # This section shows the analysis process, including how to set up
 # calculation and fitting engines.
 #
-# #### Set Calculator
-
-# %%
-project.analysis.current_calculator = 'cryspy'
-
-# %% [markdown]
 # #### Set Minimizer
 
 # %%
-project.analysis.current_minimizer = 'lmfit (leastsq)'
+project.analysis.current_minimizer = 'lmfit'
 
 # %% [markdown]
 # #### Plot Measured vs Calculated
@@ -207,8 +201,8 @@
 # Set parameters to be refined.
 
 # %%
-model.cell.length_a.free = True
-model.cell.length_c.free = True
+structure.cell.length_a.free = True
+structure.cell.length_c.free = True
 
 expt.linked_phases['hs'].scale.free = True
 expt.instrument.calib_twotheta_offset.free = True
@@ -281,11 +275,11 @@
 # Set more parameters to be refined.
 
 # %%
-model.atom_sites['O'].fract_x.free = True
-model.atom_sites['O'].fract_z.free = True
-model.atom_sites['Cl'].fract_z.free = True
-model.atom_sites['H'].fract_x.free = True
-model.atom_sites['H'].fract_z.free = True
+structure.atom_sites['O'].fract_x.free = True
+structure.atom_sites['O'].fract_z.free = True
+structure.atom_sites['Cl'].fract_z.free = True
+structure.atom_sites['H'].fract_x.free = True
+structure.atom_sites['H'].fract_z.free = True
 
 # %% [markdown]
 # Show free parameters after selection.
@@ -317,11 +311,11 @@
 # Set more parameters to be refined.
 
 # %%
-model.atom_sites['Zn'].b_iso.free = True
-model.atom_sites['Cu'].b_iso.free = True
-model.atom_sites['O'].b_iso.free = True
-model.atom_sites['Cl'].b_iso.free = True
-model.atom_sites['H'].b_iso.free = True
+structure.atom_sites['Zn'].b_iso.free = True
+structure.atom_sites['Cu'].b_iso.free = True
+structure.atom_sites['O'].b_iso.free = True
+structure.atom_sites['Cl'].b_iso.free = True
+structure.atom_sites['H'].b_iso.free = True
 
 # %% [markdown]
 # Show free parameters after selection.
diff --git a/tutorials/ed-7.py b/tutorials/ed-7.py
index 1e12be88..cd719154 100644
--- a/tutorials/ed-7.py
+++ b/tutorials/ed-7.py
@@ -11,38 +11,38 @@
 # %%
 from easydiffraction import ExperimentFactory
 from easydiffraction import Project
-from easydiffraction import SampleModelFactory
+from easydiffraction import StructureFactory
 from easydiffraction import download_data
 
 # %% [markdown]
-# ## Define Sample Model
+# ## Define Structure
 #
-# This section shows how to add sample models and modify their
+# This section shows how to add structures and modify their
 # parameters.
 #
-# #### Create Sample Model
+# #### Create Structure
 
 # %%
-model = SampleModelFactory.create(name='si')
+structure = StructureFactory.from_scratch(name='si')
 
 # %% [markdown]
 # #### Set Space Group
 
 # %%
-model.space_group.name_h_m = 'F d -3 m'
-model.space_group.it_coordinate_system_code = '2'
+structure.space_group.name_h_m = 'F d -3 m'
+structure.space_group.it_coordinate_system_code = '2'
 
 # %% [markdown]
 # #### Set Unit Cell
 
 # %%
-model.cell.length_a = 5.431
+structure.cell.length_a = 5.431
 
 # %% [markdown]
 # #### Set Atom Sites
 
 # %%
-model.atom_sites.add(
+structure.atom_sites.create(
     label='Si',
     type_symbol='Si',
     fract_x=0.125,
@@ -55,7 +55,7 @@
 # ## Define Experiment
 #
 # This section shows how to add experiments, configure their
-# parameters, and link the sample models defined in the previous step.
+# parameters, and link the structures defined in the previous step.
 #
 # #### Download Measured Data
 
@@ -66,7 +66,9 @@
 # #### Create Experiment
 
 # %%
-expt = ExperimentFactory.create(name='sepd', data_path=data_path, beam_mode='time-of-flight')
+expt = ExperimentFactory.from_data_path(
+    name='sepd', data_path=data_path, beam_mode='time-of-flight'
+)
 
 # %% [markdown]
 # #### Set Instrument
@@ -101,18 +103,18 @@
 # %%
 expt.background_type = 'line-segment'
 for x in range(0, 35000, 5000):
-    expt.background.add(id=str(x), x=x, y=200)
+    expt.background.create(id=str(x), x=x, y=200)
 
 # %% [markdown]
 # #### Set Linked Phases
 
 # %%
-expt.linked_phases.add(id='si', scale=10.0)
+expt.linked_phases.create(id='si', scale=10.0)
 
 # %% [markdown]
 # ## Define Project
 #
-# The project object is used to manage the sample model, experiment, and
+# The project object is used to manage the structure, experiment, and
 # analysis.
 #
 # #### Create Project
@@ -121,16 +123,16 @@
 project = Project()
 
 # %% [markdown]
-# #### Add Sample Model
+# #### Add Structure
 
 # %%
-project.sample_models.add(sample_model=model)
+project.structures.add(structure)
 
 # %% [markdown]
 # #### Add Experiment
 
 # %%
-project.experiments.add(experiment=expt)
+project.experiments.add(expt)
 
 # %% [markdown]
 # ## Perform Analysis
@@ -138,16 +140,10 @@
 # This section shows the analysis process, including how to set up
 # calculation and fitting engines.
 #
-# #### Set Calculator
-
-# %%
-project.analysis.current_calculator = 'cryspy'
-
-# %% [markdown]
 # #### Set Minimizer
 
 # %%
-project.analysis.current_minimizer = 'lmfit (leastsq)'
+project.analysis.current_minimizer = 'lmfit'
 
 # %% [markdown]
 # #### Plot Measured vs Calculated
@@ -162,7 +158,7 @@
 # Set parameters to be refined.
 
 # %%
-model.cell.length_a.free = True
+structure.cell.length_a.free = True
 
 expt.linked_phases['si'].scale.free = True
 expt.instrument.calib_d_to_tof_offset.free = True
@@ -265,7 +261,7 @@
 # Set more parameters to be refined.
 
 # %%
-model.atom_sites['Si'].b_iso.free = True
+structure.atom_sites['Si'].b_iso.free = True
 
 # %% [markdown]
 # Show free parameters after selection.
diff --git a/tutorials/ed-8.py b/tutorials/ed-8.py
index 2aa85227..b8cdf0bd 100644
--- a/tutorials/ed-8.py
+++ b/tutorials/ed-8.py
@@ -14,38 +14,38 @@
 # %%
 from easydiffraction import ExperimentFactory
 from easydiffraction import Project
-from easydiffraction import SampleModelFactory
+from easydiffraction import StructureFactory
 from easydiffraction import download_data
 
 # %% [markdown]
-# ## Define Sample Model
+# ## Define Structure
 #
-# This section covers how to add sample models and modify their
+# This section covers how to add structures and modify their
 # parameters.
 #
-# #### Create Sample Model
+# #### Create Structure
 
 # %%
-model = SampleModelFactory.create(name='ncaf')
+structure = StructureFactory.from_scratch(name='ncaf')
 
 # %% [markdown]
 # #### Set Space Group
 
 # %%
-model.space_group.name_h_m = 'I 21 3'
-model.space_group.it_coordinate_system_code = '1'
+structure.space_group.name_h_m = 'I 21 3'
+structure.space_group.it_coordinate_system_code = '1'
 
 # %% [markdown]
 # #### Set Unit Cell
 
 # %%
-model.cell.length_a = 10.250256
+structure.cell.length_a = 10.250256
 
 # %% [markdown]
 # #### Set Atom Sites
 
 # %%
-model.atom_sites.add(
+structure.atom_sites.create(
     label='Ca',
     type_symbol='Ca',
     fract_x=0.4663,
@@ -54,7 +54,7 @@
     wyckoff_letter='b',
     b_iso=0.92,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='Al',
     type_symbol='Al',
     fract_x=0.2521,
@@ -63,7 +63,7 @@
     wyckoff_letter='a',
     b_iso=0.73,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='Na',
     type_symbol='Na',
     fract_x=0.0851,
@@ -72,7 +72,7 @@
     wyckoff_letter='a',
     b_iso=2.08,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='F1',
     type_symbol='F',
     fract_x=0.1377,
@@ -81,7 +81,7 @@
     wyckoff_letter='c',
     b_iso=0.90,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='F2',
     type_symbol='F',
     fract_x=0.3625,
@@ -90,7 +90,7 @@
     wyckoff_letter='c',
     b_iso=1.37,
 )
-model.atom_sites.add(
+structure.atom_sites.create(
     label='F3',
     type_symbol='F',
     fract_x=0.4612,
@@ -104,7 +104,7 @@
 # ## Define Experiment
 #
 # This section shows how to add experiments, configure their parameters,
-# and link the sample models defined in the previous step.
+# and link the structures defined in the previous step.
 #
 # #### Download Measured Data
 
@@ -118,14 +118,14 @@
 # #### Create Experiment
 
 # %%
-expt56 = ExperimentFactory.create(
+expt56 = ExperimentFactory.from_data_path(
     name='wish_5_6',
     data_path=data_path56,
     beam_mode='time-of-flight',
 )
 
 # %%
-expt47 = ExperimentFactory.create(
+expt47 = ExperimentFactory.from_data_path(
     name='wish_4_7',
     data_path=data_path47,
     beam_mode='time-of-flight',
@@ -205,7 +205,7 @@
     ],
     start=1,
 ):
-    expt56.background.add(id=str(idx), x=x, y=y)
+    expt56.background.create(id=str(idx), x=x, y=y)
 
 # %%
 expt47.background_type = 'line-segment'
@@ -241,32 +241,32 @@
     ],
     start=1,
 ):
-    expt47.background.add(id=str(idx), x=x, y=y)
+    expt47.background.create(id=str(idx), x=x, y=y)
 
 # %% [markdown]
 # #### Set Linked Phases
 
 # %%
-expt56.linked_phases.add(id='ncaf', scale=1.0)
+expt56.linked_phases.create(id='ncaf', scale=1.0)
 
 # %%
-expt47.linked_phases.add(id='ncaf', scale=2.0)
+expt47.linked_phases.create(id='ncaf', scale=2.0)
 
 # %% [markdown]
 # #### Set Excluded Regions
 
 # %%
-expt56.excluded_regions.add(id='1', start=0, end=10010)
-expt56.excluded_regions.add(id='2', start=100010, end=200000)
+expt56.excluded_regions.create(id='1', start=0, end=10010)
+expt56.excluded_regions.create(id='2', start=100010, end=200000)
 
 # %%
-expt47.excluded_regions.add(id='1', start=0, end=10006)
-expt47.excluded_regions.add(id='2', start=100004, end=200000)
+expt47.excluded_regions.create(id='1', start=0, end=10006)
+expt47.excluded_regions.create(id='2', start=100004, end=200000)
 
 # %% [markdown]
 # ## Define Project
 #
-# The project object is used to manage the sample model, experiments,
+# The project object is used to manage the structure, experiments,
 # and analysis
 #
 # #### Create Project
@@ -283,17 +283,17 @@
 # project.plotter.engine = 'plotly'
 
 # %% [markdown]
-# #### Add Sample Model
+# #### Add Structure
 
 # %%
-project.sample_models.add(sample_model=model)
+project.structures.add(structure)
 
 # %% [markdown]
 # #### Add Experiment
 
 # %%
-project.experiments.add(experiment=expt56)
-project.experiments.add(experiment=expt47)
+project.experiments.add(expt56)
+project.experiments.add(expt47)
 
 # %% [markdown]
 # ## Perform Analysis
@@ -301,33 +301,27 @@
 # This section shows the analysis process, including how to set up
 # calculation and fitting engines.
 #
-# #### Set Calculator
-
-# %%
-project.analysis.current_calculator = 'cryspy'
-
-# %% [markdown]
 # #### Set Minimizer
 
 # %%
-project.analysis.current_minimizer = 'lmfit (leastsq)'
+project.analysis.current_minimizer = 'lmfit'
 
 # %% [markdown]
 # #### Set Fit Mode
 
 # %%
-project.analysis.fit_mode = 'joint'
+project.analysis.fit_mode.mode = 'joint'
 
 # %% [markdown]
 # #### Set Free Parameters
 
 # %%
-model.atom_sites['Ca'].b_iso.free = True
-model.atom_sites['Al'].b_iso.free = True
-model.atom_sites['Na'].b_iso.free = True
-model.atom_sites['F1'].b_iso.free = True
-model.atom_sites['F2'].b_iso.free = True
-model.atom_sites['F3'].b_iso.free = True
+structure.atom_sites['Ca'].b_iso.free = True
+structure.atom_sites['Al'].b_iso.free = True
+structure.atom_sites['Na'].b_iso.free = True
+structure.atom_sites['F1'].b_iso.free = True
+structure.atom_sites['F2'].b_iso.free = True
+structure.atom_sites['F3'].b_iso.free = True
 
 # %%
 expt56.linked_phases['ncaf'].scale.free = True
diff --git a/tutorials/ed-9.py b/tutorials/ed-9.py
index a5fe1647..34da9359 100644
--- a/tutorials/ed-9.py
+++ b/tutorials/ed-9.py
@@ -11,38 +11,38 @@
 # %%
 from easydiffraction import ExperimentFactory
 from easydiffraction import Project
-from easydiffraction import SampleModelFactory
+from easydiffraction import StructureFactory
 from easydiffraction import download_data
 
 # %% [markdown]
-# ## Define Sample Models
+# ## Define Structures
 #
-# This section shows how to add sample models and modify their
+# This section shows how to add structures and modify their
 # parameters.
 #
-# ### Create Sample Model 1: LBCO
+# ### Create Structure 1: LBCO
 
 # %%
-model_1 = SampleModelFactory.create(name='lbco')
+structure_1 = StructureFactory.from_scratch(name='lbco')
 
 # %% [markdown]
 # #### Set Space Group
 
 # %%
-model_1.space_group.name_h_m = 'P m -3 m'
-model_1.space_group.it_coordinate_system_code = '1'
+structure_1.space_group.name_h_m = 'P m -3 m'
+structure_1.space_group.it_coordinate_system_code = '1'
 
 # %% [markdown]
 # #### Set Unit Cell
 
 # %%
-model_1.cell.length_a = 3.8909
+structure_1.cell.length_a = 3.8909
 
 # %% [markdown]
 # #### Set Atom Sites
 
 # %%
-model_1.atom_sites.add(
+structure_1.atom_sites.create(
     label='La',
     type_symbol='La',
     fract_x=0,
@@ -52,7 +52,7 @@
     b_iso=0.2,
     occupancy=0.5,
 )
-model_1.atom_sites.add(
+structure_1.atom_sites.create(
     label='Ba',
     type_symbol='Ba',
     fract_x=0,
@@ -62,7 +62,7 @@
     b_iso=0.2,
     occupancy=0.5,
 )
-model_1.atom_sites.add(
+structure_1.atom_sites.create(
     label='Co',
     type_symbol='Co',
     fract_x=0.5,
@@ -71,7 +71,7 @@
     wyckoff_letter='b',
     b_iso=0.2567,
 )
-model_1.atom_sites.add(
+structure_1.atom_sites.create(
     label='O',
     type_symbol='O',
     fract_x=0,
@@ -82,29 +82,29 @@
 )
 
 # %% [markdown]
-# ### Create Sample Model 2: Si
+# ### Create Structure 2: Si
 
 # %%
-model_2 = SampleModelFactory.create(name='si')
+structure_2 = StructureFactory.from_scratch(name='si')
 
 # %% [markdown]
 # #### Set Space Group
 
 # %%
-model_2.space_group.name_h_m = 'F d -3 m'
-model_2.space_group.it_coordinate_system_code = '2'
+structure_2.space_group.name_h_m = 'F d -3 m'
+structure_2.space_group.it_coordinate_system_code = '2'
 
 # %% [markdown]
 # #### Set Unit Cell
 
 # %%
-model_2.cell.length_a = 5.43146
+structure_2.cell.length_a = 5.43146
 
 # %% [markdown]
 # #### Set Atom Sites
 
 # %%
-model_2.atom_sites.add(
+structure_2.atom_sites.create(
     label='Si',
     type_symbol='Si',
     fract_x=0.0,
@@ -118,7 +118,7 @@
 # ## Define Experiment
 #
 # This section shows how to add experiments, configure their parameters,
-# and link the sample models defined in the previous step.
+# and link the structures defined in the previous step.
 #
 # #### Download Data
 
@@ -129,7 +129,7 @@
 # #### Create Experiment
 
 # %%
-experiment = ExperimentFactory.create(
+experiment = ExperimentFactory.from_data_path(
     name='mcstas',
     data_path=data_path,
     sample_form='powder',
@@ -173,31 +173,31 @@
 # Add background points.
 
 # %%
-experiment.background.add(id='1', x=45000, y=0.2)
-experiment.background.add(id='2', x=50000, y=0.2)
-experiment.background.add(id='3', x=55000, y=0.2)
-experiment.background.add(id='4', x=65000, y=0.2)
-experiment.background.add(id='5', x=70000, y=0.2)
-experiment.background.add(id='6', x=75000, y=0.2)
-experiment.background.add(id='7', x=80000, y=0.2)
-experiment.background.add(id='8', x=85000, y=0.2)
-experiment.background.add(id='9', x=90000, y=0.2)
-experiment.background.add(id='10', x=95000, y=0.2)
-experiment.background.add(id='11', x=100000, y=0.2)
-experiment.background.add(id='12', x=105000, y=0.2)
-experiment.background.add(id='13', x=110000, y=0.2)
+experiment.background.create(id='1', x=45000, y=0.2)
+experiment.background.create(id='2', x=50000, y=0.2)
+experiment.background.create(id='3', x=55000, y=0.2)
+experiment.background.create(id='4', x=65000, y=0.2)
+experiment.background.create(id='5', x=70000, y=0.2)
+experiment.background.create(id='6', x=75000, y=0.2)
+experiment.background.create(id='7', x=80000, y=0.2)
+experiment.background.create(id='8', x=85000, y=0.2)
+experiment.background.create(id='9', x=90000, y=0.2)
+experiment.background.create(id='10', x=95000, y=0.2)
+experiment.background.create(id='11', x=100000, y=0.2)
+experiment.background.create(id='12', x=105000, y=0.2)
+experiment.background.create(id='13', x=110000, y=0.2)
 
 # %% [markdown]
 # #### Set Linked Phases
 
 # %%
-experiment.linked_phases.add(id='lbco', scale=4.0)
-experiment.linked_phases.add(id='si', scale=0.2)
+experiment.linked_phases.create(id='lbco', scale=4.0)
+experiment.linked_phases.create(id='si', scale=0.2)
 
 # %% [markdown]
 # ## Define Project
 #
-# The project object is used to manage sample models, experiments, and
+# The project object is used to manage structures, experiments, and
 # analysis.
 #
 # #### Create Project
@@ -206,23 +206,23 @@
 project = Project()
 
 # %% [markdown]
-# #### Add Sample Models
+# #### Add Structures
 
 # %%
-project.sample_models.add(sample_model=model_1)
-project.sample_models.add(sample_model=model_2)
+project.structures.add(structure_1)
+project.structures.add(structure_2)
 
 # %% [markdown]
-# #### Show Sample Models
+# #### Show Structures
 
 # %%
-project.sample_models.show_names()
+project.structures.show_names()
 
 # %% [markdown]
 # #### Add Experiments
 
 # %%
-project.experiments.add(experiment=experiment)
+project.experiments.add(experiment)
 
 # %% [markdown]
 # #### Set Excluded Regions
@@ -236,8 +236,8 @@
 # Add excluded regions.
 
 # %%
-experiment.excluded_regions.add(id='1', start=0, end=40000)
-experiment.excluded_regions.add(id='2', start=108000, end=200000)
+experiment.excluded_regions.create(id='1', start=0, end=40000)
+experiment.excluded_regions.create(id='2', start=108000, end=200000)
 
 # %% [markdown]
 # Show excluded regions.
@@ -263,28 +263,22 @@
 # This section outlines the analysis process, including how to configure
 # calculation and fitting engines.
 #
-# #### Set Calculator
-
-# %%
-project.analysis.current_calculator = 'cryspy'
-
-# %% [markdown]
 # #### Set Minimizer
 
 # %%
-project.analysis.current_minimizer = 'lmfit (leastsq)'
+project.analysis.current_minimizer = 'lmfit'
 
 # %% [markdown]
 # #### Set Fitting Parameters
 #
-# Set sample model parameters to be optimized.
+# Set structure 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
+structure_1.cell.length_a.free = True
+structure_1.atom_sites['Co'].b_iso.free = True
+structure_1.atom_sites['O'].b_iso.free = True
 
-model_2.cell.length_a.free = True
+structure_2.cell.length_a.free = True
 
 # %% [markdown]
 # Set experiment parameters to be optimized.
diff --git a/tutorials/index.json b/tutorials/index.json
index 14808266..138b0e51 100644
--- a/tutorials/index.json
+++ b/tutorials/index.json
@@ -3,14 +3,14 @@
     "url": "https://easyscience.github.io/diffraction-lib/{version}/tutorials/ed-1/ed-1.ipynb",
     "original_name": "quick_from-cif_pd-neut-cwl_LBCO-HRPT",
     "title": "Quick Start: LBCO from CIF",
-    "description": "Minimalistic Rietveld refinement of La0.5Ba0.5CoO3 using sample model and experiment defined via CIF files",
+    "description": "Minimalistic Rietveld refinement of La0.5Ba0.5CoO3 using structure and experiment defined via CIF files",
     "level": "quick"
   },
   "2": {
     "url": "https://easyscience.github.io/diffraction-lib/{version}/tutorials/ed-2/ed-2.ipynb",
     "original_name": "quick_from-code_pd-neut-cwl_LBCO-HRPT",
     "title": "Quick Start: LBCO from Code",
-    "description": "Minimalistic Rietveld refinement of La0.5Ba0.5CoO3 with sample model and experiment defined directly in code",
+    "description": "Minimalistic Rietveld refinement of La0.5Ba0.5CoO3 with structure and experiment defined directly in code",
     "level": "quick"
   },
   "3": {
@@ -96,5 +96,19 @@
     "title": "Crystal Structure: Tb2TiO7, HEiDi",
     "description": "Crystal structure refinement of Tb2TiO7 using single crystal neutron diffraction data from HEiDi at FRM II",
     "level": "intermediate"
+  },
+  "15": {
+    "url": "https://easyscience.github.io/diffraction-lib/{version}/tutorials/ed-15/ed-15.ipynb",
+    "original_name": "",
+    "title": "Crystal Structure: Taurine, SENJU (TOF)",
+    "description": "Crystal structure refinement of Taurine using time-of-flight neutron single crystal diffraction data from SENJU at J-PARC",
+    "level": "intermediate"
+  },
+  "16": {
+    "url": "https://easyscience.github.io/diffraction-lib/{version}/tutorials/ed-16/ed-16.ipynb",
+    "original_name": "advanced_joint-fit_bragg-pdf_pd-neut-tof_Si",
+    "title": "Advanced: Si Joint Bragg+PDF Fit",
+    "description": "Joint refinement of Si crystal structure combining Bragg diffraction (SEPD) and pair distribution function (NOMAD) analysis",
+    "level": "advanced"
   }
 }

From 4e71972aabaaca2374ff4243eca0c0bec5fc5887 Mon Sep 17 00:00:00 2001
From: Andrew Sazonov 
Date: Fri, 27 Mar 2026 09:57:53 +0100
Subject: [PATCH 3/5] Switch to the new easyscience/templates system (#129)

* Apply new templates

* pixi run nonpy-format-fix

* pixi run py-lint-fix

* Disable some rules temporary

* pixi run py-format-fix

* Temporarily disable pydoclint until we are ready

* Update license headers

* Convert notebooks

* Clean up

* Add missing deps to test workflow

* Add parameter-property consistency checker

* Use structural type extraction in param consistency checker

* Enhance property docstrings and type hints for clarity and consistency

* Enhance docstrings by including type annotations for setter arguments

* Ignore E501 lint rule in favor of formatter and W505

* Update line length settings in pyproject.toml for consistency and clarity

* Simplify property docstring template and checker

* Wrap long property docstrings to satisfy max-doc-length

* Switch from docformatter to format-docstring

* Apply formatting

* Apply formatting

* Convert docstrings from google to numpy style: Step 1

* Remove docstring-format-check

* Convert docstrings from google to numpy style: Step 2

* Convert docstrings from google to numpy style: Step 3

* Enable ANN rules

* Apply linting and formatting

* Run docstring format fix for src/ only

* Add types to the Parameters sections in docstrings

* Apply docstrings formatting

* More rules

* Bump dependencies

* Add some missing docstrings to public methods

* New combination of rules

* Make all docstring summaries to take single line

* More docstring fixes

* Remove unused script

* Fix D401: use imperative mood in all docstring summaries

* More formatting and linting

* More fromatting

* Add missing D101 class docstrings across 9 files

* Add missing D105 magic method docstrings across 3 files

* Minor changes

* Fix broken nav entry, tutorial links, and anchor in docs

* Bump dependencies

* Refactor tutorial display to remove description and update title in index

* Add platform-specific dependencies for GNU Scientific Library and libc++ on macOS

* Consolidate GNU Scientific Library dependency for macOS in pixi.toml and test.yml
---
 .badgery.yaml                                 |   12 +-
 .copier-answers.yml                           |   27 +
 .github/actions/download-artifact/action.yml  |   50 +
 .github/actions/github-script/action.yml      |   19 +
 .../actions/setup-easyscience-bot/action.yml  |   40 +
 .github/actions/setup-pixi/action.yml         |   44 +
 .github/actions/upload-artifact/action.yml    |   49 +
 .github/actions/upload-codecov/action.yml     |   42 +
 .github/configs/pages-deployment.json         |    6 +
 .github/configs/rulesets-develop.json         |   37 +
 .github/configs/rulesets-gh-pages.json        |   19 +
 .github/configs/rulesets-master.json          |   30 +
 .github/copilot-instructions.md               |  184 +-
 .github/scripts/backmerge-conflict-issue.js   |   69 +
 .github/workflows/backmerge.yaml              |   66 -
 .github/workflows/backmerge.yml               |  109 +
 .../workflows/{cleanup.yaml => cleanup.yml}   |   18 +-
 .../workflows/{coverage.yaml => coverage.yml} |   81 +-
 .../{dashboard.yaml => dashboard.yml}         |   85 +-
 .github/workflows/{docs.yaml => docs.yml}     |  131 +-
 .github/workflows/issues-labels.yml           |   42 +
 .github/workflows/lint-format.yml             |  124 +
 .../workflows/{labels.yaml => pr-labels.yml}  |   19 +-
 .github/workflows/pypi-publish.yaml           |   43 -
 .github/workflows/pypi-publish.yml            |   46 +
 .github/workflows/pypi-test.yaml              |   97 -
 .github/workflows/pypi-test.yml               |   80 +
 .github/workflows/quality.yaml                |  123 -
 .../{release-notes.yaml => release-notes.yml} |   23 +-
 .github/workflows/release-pr.yaml             |   57 -
 .github/workflows/release-pr.yml              |   55 +
 .github/workflows/security.yaml               |   39 -
 .github/workflows/security.yml                |   93 +
 .../{test-trigger.yaml => test-trigger.yml}   |   16 +-
 .github/workflows/test.yaml                   |  259 --
 .github/workflows/test.yml                    |  279 ++
 .github/workflows/tutorial-tests-colab.yaml   |   11 +-
 ...rigger.yaml => tutorial-tests-trigger.yml} |   16 +-
 ...tutorial-tests.yaml => tutorial-tests.yml} |   49 +-
 .gitignore                                    |   32 +-
 .pre-commit-config.yaml                       |   54 +-
 .prettierignore                               |   28 +-
 CONTRIBUTING.md                               |  494 ++-
 DEVELOPMENT.md                                |  150 -
 LICENSE                                       |    2 +-
 README.md                                     |   72 +-
 codecov.yml                                   |    5 -
 docs/architecture/architecture.md             |  430 ++-
 docs/architecture/issues_closed.md            |   75 +-
 docs/architecture/issues_open.md              |  246 +-
 docs/{ => docs}/api-reference/analysis.md     |    0
 docs/{ => docs}/api-reference/core.md         |    0
 .../api-reference/crystallography.md          |    0
 .../api-reference/datablocks/experiment.md    |    0
 .../api-reference/datablocks/structure.md     |    0
 docs/{ => docs}/api-reference/display.md      |    0
 docs/{ => docs}/api-reference/index.md        |   17 +-
 docs/{ => docs}/api-reference/io.md           |    0
 docs/{ => docs}/api-reference/project.md      |    0
 docs/{ => docs}/api-reference/summary.md      |    0
 docs/{ => docs}/api-reference/utils.md        |    0
 docs/docs/assets/images/favicon.png           |  Bin 0 -> 34184 bytes
 docs/docs/assets/images/logo_dark.svg         |   41 +
 docs/docs/assets/images/logo_light.svg        |   41 +
 .../data-acquisition_2d-raw-data.jpg          |  Bin
 .../data-acquisition_instrument.png           |  Bin
 .../images/user-guide/data-analysis_model.png |  Bin
 .../user-guide/data-analysis_refinement.png   |  Bin
 .../user-guide/data-reduction_1d-pattern.png  |  Bin
 docs/docs/assets/javascripts/extra.js         |   27 +
 docs/docs/assets/javascripts/mathjax.js       |   33 +
 docs/docs/assets/stylesheets/extra.css        |  359 ++
 docs/{ => docs}/index.md                      |   27 +-
 .../installation-and-setup/index.md           |   82 +-
 docs/{ => docs}/introduction/index.md         |   32 +-
 docs/docs/tutorials/ed-1.ipynb                |  203 ++
 {tutorials => docs/docs/tutorials}/ed-1.py    |    0
 docs/docs/tutorials/ed-10.ipynb               |  222 ++
 {tutorials => docs/docs/tutorials}/ed-10.py   |    0
 docs/docs/tutorials/ed-11.ipynb               |  254 ++
 {tutorials => docs/docs/tutorials}/ed-11.py   |    0
 docs/docs/tutorials/ed-12.ipynb               |  303 ++
 {tutorials => docs/docs/tutorials}/ed-12.py   |    0
 docs/docs/tutorials/ed-13.ipynb               | 2923 ++++++++++++++++
 {tutorials => docs/docs/tutorials}/ed-13.py   |    0
 docs/docs/tutorials/ed-14.ipynb               |  319 ++
 {tutorials => docs/docs/tutorials}/ed-14.py   |    0
 docs/docs/tutorials/ed-15.ipynb               |  296 ++
 {tutorials => docs/docs/tutorials}/ed-15.py   |    0
 docs/docs/tutorials/ed-16.ipynb               |  623 ++++
 {tutorials => docs/docs/tutorials}/ed-16.py   |    0
 docs/docs/tutorials/ed-2.ipynb                |  344 ++
 {tutorials => docs/docs/tutorials}/ed-2.py    |    0
 docs/docs/tutorials/ed-3.ipynb                | 1805 ++++++++++
 {tutorials => docs/docs/tutorials}/ed-3.py    |    0
 docs/docs/tutorials/ed-4.ipynb                |  705 ++++
 {tutorials => docs/docs/tutorials}/ed-4.py    |    0
 docs/docs/tutorials/ed-5.ipynb                |  638 ++++
 {tutorials => docs/docs/tutorials}/ed-5.py    |    0
 docs/docs/tutorials/ed-6.ipynb                |  849 +++++
 {tutorials => docs/docs/tutorials}/ed-6.py    |    0
 docs/docs/tutorials/ed-7.ipynb                |  741 ++++
 {tutorials => docs/docs/tutorials}/ed-7.py    |    0
 docs/docs/tutorials/ed-8.ipynb                |  747 ++++
 {tutorials => docs/docs/tutorials}/ed-8.py    |    0
 docs/docs/tutorials/ed-9.ipynb                |  704 ++++
 {tutorials => docs/docs/tutorials}/ed-9.py    |    0
 {tutorials => docs/docs/tutorials}/index.json |    2 +-
 docs/docs/tutorials/index.md                  |   95 +
 .../user-guide/analysis-workflow/analysis.md  |  184 +-
 .../analysis-workflow/experiment.md           |  130 +-
 .../user-guide/analysis-workflow/index.md     |   37 +
 .../user-guide/analysis-workflow/model.md     |   71 +-
 .../user-guide/analysis-workflow/project.md   |   59 +-
 .../user-guide/analysis-workflow/summary.md   |   30 +-
 docs/{ => docs}/user-guide/concept.md         |   73 +-
 docs/{ => docs}/user-guide/data-format.md     |   88 +-
 docs/{ => docs}/user-guide/first-steps.md     |  140 +-
 docs/{ => docs}/user-guide/glossary.md        |   24 +-
 docs/{ => docs}/user-guide/index.md           |   25 +-
 docs/{ => docs}/user-guide/parameters.md      |  110 +-
 .../parameters/_diffrn_radiation.md           |    8 +-
 .../_diffrn_radiation_wavelength.md           |    4 +-
 .../user-guide/parameters/_exptl_crystal.md   |    0
 .../user-guide/parameters/_extinction.md      |    0
 .../user-guide/parameters/_pd_calib.md        |    4 +-
 .../user-guide/parameters/atom_site.md        |   29 +-
 .../user-guide/parameters/background.md       |   19 +-
 docs/{ => docs}/user-guide/parameters/cell.md |    4 +-
 .../user-guide/parameters/expt_type.md        |    4 +-
 .../user-guide/parameters/instrument.md       |   24 +-
 .../user-guide/parameters/linked_phases.md    |    8 +-
 .../user-guide/parameters/pd_meas.md          |    4 +-
 docs/{ => docs}/user-guide/parameters/peak.md |    4 +-
 .../user-guide/parameters/space_group.md      |   10 +-
 docs/includes/abbreviations.md                |   15 +
 docs/mkdocs.yml                               |  169 +-
 docs/overrides/.icons/app.svg                 |    4 +
 docs/overrides/.icons/easydiffraction.svg     |    3 +
 docs/overrides/.icons/easyscience.svg         |   20 +
 docs/overrides/.icons/google-colab.svg        |    7 +
 docs/overrides/main.html                      |   39 +
 docs/overrides/partials/logo.html             |   15 +
 docs/tutorials/index.md                       |   90 -
 docs/user-guide/analysis-workflow/index.md    |   34 -
 pixi.lock                                     | 2522 ++++++++------
 pixi.toml                                     |  251 +-
 prettierrc.toml                               |   19 +-
 pycrysfml.md                                  |    3 +-
 pyproject.toml                                |  387 +-
 pytest.ini                                    |   13 -
 src/easydiffraction/__init__.py               |    2 +-
 src/easydiffraction/__main__.py               |   10 +-
 src/easydiffraction/analysis/__init__.py      |    2 +-
 src/easydiffraction/analysis/analysis.py      |  155 +-
 .../analysis/calculators/__init__.py          |    2 +-
 .../analysis/calculators/base.py              |   30 +-
 .../analysis/calculators/crysfml.py           |  121 +-
 .../analysis/calculators/cryspy.py            |  147 +-
 .../analysis/calculators/factory.py           |   12 +-
 .../analysis/calculators/pdffit.py            |   48 +-
 .../analysis/categories/__init__.py           |    2 +-
 .../analysis/categories/aliases/__init__.py   |    2 +-
 .../analysis/categories/aliases/default.py    |   37 +-
 .../analysis/categories/aliases/factory.py    |    2 +-
 .../categories/constraints/__init__.py        |    2 +-
 .../categories/constraints/default.py         |   38 +-
 .../categories/constraints/factory.py         |    2 +-
 .../analysis/categories/fit_mode/__init__.py  |    2 +-
 .../analysis/categories/fit_mode/enums.py     |    4 +-
 .../analysis/categories/fit_mode/factory.py   |    2 +-
 .../analysis/categories/fit_mode/fit_mode.py  |   23 +-
 .../joint_fit_experiments/__init__.py         |    2 +-
 .../joint_fit_experiments/default.py          |   36 +-
 .../joint_fit_experiments/factory.py          |    6 +-
 .../analysis/fit_helpers/__init__.py          |    2 +-
 .../analysis/fit_helpers/metrics.py           |  137 +-
 .../analysis/fit_helpers/reporting.py         |   87 +-
 .../analysis/fit_helpers/tracking.py          |   75 +-
 src/easydiffraction/analysis/fitting.py       |  122 +-
 .../analysis/minimizers/__init__.py           |    2 +-
 .../analysis/minimizers/base.py               |  127 +-
 .../analysis/minimizers/dfols.py              |   46 +-
 .../analysis/minimizers/factory.py            |    2 +-
 .../analysis/minimizers/lmfit.py              |  106 +-
 src/easydiffraction/core/__init__.py          |    2 +-
 src/easydiffraction/core/category.py          |   55 +-
 src/easydiffraction/core/collection.py        |   69 +-
 src/easydiffraction/core/datablock.py         |   52 +-
 src/easydiffraction/core/diagnostic.py        |   90 +-
 src/easydiffraction/core/factory.py           |  168 +-
 src/easydiffraction/core/guard.py             |   67 +-
 src/easydiffraction/core/identity.py          |   21 +-
 src/easydiffraction/core/metadata.py          |   80 +-
 src/easydiffraction/core/singleton.py         |   54 +-
 src/easydiffraction/core/validation.py        |  115 +-
 src/easydiffraction/core/variable.py          |  180 +-
 .../crystallography/__init__.py               |    2 +-
 .../crystallography/crystallography.py        |   50 +-
 .../crystallography/space_groups.py           |   13 +-
 src/easydiffraction/datablocks/__init__.py    |    2 +-
 .../datablocks/experiment/__init__.py         |    2 +-
 .../experiment/categories/__init__.py         |    2 +-
 .../categories/background/__init__.py         |    2 +-
 .../experiment/categories/background/base.py  |    5 +-
 .../categories/background/chebyshev.py        |   48 +-
 .../experiment/categories/background/enums.py |    2 +-
 .../categories/background/factory.py          |    2 +-
 .../categories/background/line_segment.py     |   55 +-
 .../experiment/categories/data/__init__.py    |    2 +-
 .../experiment/categories/data/bragg_pd.py    |  163 +-
 .../experiment/categories/data/bragg_sc.py    |  152 +-
 .../experiment/categories/data/factory.py     |    2 +-
 .../experiment/categories/data/total_pd.py    |  109 +-
 .../categories/excluded_regions/__init__.py   |    2 +-
 .../categories/excluded_regions/default.py    |   42 +-
 .../categories/excluded_regions/factory.py    |    6 +-
 .../categories/experiment_type/__init__.py    |    2 +-
 .../categories/experiment_type/default.py     |   64 +-
 .../categories/experiment_type/factory.py     |    2 +-
 .../categories/extinction/__init__.py         |    2 +-
 .../categories/extinction/factory.py          |    2 +-
 .../experiment/categories/extinction/shelx.py |   30 +-
 .../categories/instrument/__init__.py         |    2 +-
 .../experiment/categories/instrument/base.py  |    8 +-
 .../experiment/categories/instrument/cwl.py   |   42 +-
 .../categories/instrument/factory.py          |    2 +-
 .../experiment/categories/instrument/tof.py   |   66 +-
 .../categories/linked_crystal/__init__.py     |    2 +-
 .../categories/linked_crystal/default.py      |   27 +-
 .../categories/linked_crystal/factory.py      |    2 +-
 .../categories/linked_phases/__init__.py      |    2 +-
 .../categories/linked_phases/default.py       |   25 +-
 .../categories/linked_phases/factory.py       |    2 +-
 .../experiment/categories/peak/__init__.py    |    2 +-
 .../experiment/categories/peak/base.py        |    2 +-
 .../experiment/categories/peak/cwl.py         |    7 +-
 .../experiment/categories/peak/cwl_mixins.py  |  115 +-
 .../experiment/categories/peak/factory.py     |    2 +-
 .../experiment/categories/peak/tof.py         |    7 +-
 .../experiment/categories/peak/tof_mixins.py  |  128 +-
 .../experiment/categories/peak/total.py       |    2 +-
 .../categories/peak/total_mixins.py           |   79 +-
 .../datablocks/experiment/collection.py       |   72 +-
 .../datablocks/experiment/item/__init__.py    |    2 +-
 .../datablocks/experiment/item/base.py        |  200 +-
 .../datablocks/experiment/item/bragg_pd.py    |   32 +-
 .../datablocks/experiment/item/bragg_sc.py    |   24 +-
 .../datablocks/experiment/item/enums.py       |   91 +-
 .../datablocks/experiment/item/factory.py     |  100 +-
 .../datablocks/experiment/item/total_pd.py    |   13 +-
 .../datablocks/structure/__init__.py          |    2 +-
 .../structure/categories/__init__.py          |    2 +-
 .../categories/atom_sites/__init__.py         |    2 +-
 .../categories/atom_sites/default.py          |  162 +-
 .../categories/atom_sites/factory.py          |    2 +-
 .../structure/categories/cell/__init__.py     |    2 +-
 .../structure/categories/cell/default.py      |  104 +-
 .../structure/categories/cell/factory.py      |    2 +-
 .../categories/space_group/__init__.py        |    2 +-
 .../categories/space_group/default.py         |   68 +-
 .../categories/space_group/factory.py         |    2 +-
 .../datablocks/structure/collection.py        |   32 +-
 .../datablocks/structure/item/__init__.py     |    2 +-
 .../datablocks/structure/item/base.py         |   78 +-
 .../datablocks/structure/item/factory.py      |   67 +-
 src/easydiffraction/display/__init__.py       |   15 +-
 src/easydiffraction/display/base.py           |   55 +-
 .../display/plotters/__init__.py              |    9 +-
 src/easydiffraction/display/plotters/ascii.py |  109 +-
 src/easydiffraction/display/plotters/base.py  |   97 +-
 .../display/plotters/plotly.py                |  238 +-
 src/easydiffraction/display/plotting.py       |  373 +-
 .../display/tablers/__init__.py               |   13 +-
 src/easydiffraction/display/tablers/base.py   |   84 +-
 src/easydiffraction/display/tablers/pandas.py |  115 +-
 src/easydiffraction/display/tablers/rich.py   |  108 +-
 src/easydiffraction/display/tables.py         |   52 +-
 src/easydiffraction/display/utils.py          |    6 +-
 src/easydiffraction/io/__init__.py            |    2 +-
 src/easydiffraction/io/cif/__init__.py        |    2 +-
 src/easydiffraction/io/cif/handler.py         |    7 +-
 src/easydiffraction/io/cif/parse.py           |    2 +-
 src/easydiffraction/io/cif/serialize.py       |   80 +-
 src/easydiffraction/project/__init__.py       |    2 +-
 src/easydiffraction/project/project.py        |  114 +-
 src/easydiffraction/project/project_info.py   |   44 +-
 src/easydiffraction/summary/__init__.py       |    2 +-
 src/easydiffraction/summary/summary.py        |   33 +-
 src/easydiffraction/utils/__init__.py         |    2 +-
 .../utils/_vendored/__init__.py               |   27 +-
 .../_vendored/jupyter_dark_detect/__init__.py |    6 +-
 .../_vendored/jupyter_dark_detect/detector.py |   13 +-
 .../utils/_vendored/theme_detect.py           |   44 +-
 src/easydiffraction/utils/environment.py      |   71 +-
 src/easydiffraction/utils/logging.py          |  274 +-
 src/easydiffraction/utils/utils.py            |  467 +--
 tests/integration/fitting/test_multi.py       |    2 +-
 .../test_pair-distribution-function.py        |    2 +-
 ..._powder-diffraction_constant-wavelength.py |    2 +-
 .../test_powder-diffraction_joint-fit.py      |    2 +-
 .../test_powder-diffraction_time-of-flight.py |    2 +-
 .../test_single-crystal-diffraction.py        |    2 +-
 .../scipp-analysis/dream/conftest.py          |    3 +-
 .../dream/test_analyze_reduced_data.py        |    3 +-
 .../dream/test_package_import.py              |    3 +-
 .../dream/test_read_reduced_data.py           |    3 +-
 .../dream/test_validate_meta_data.py          |    3 +-
 .../dream/test_validate_physical_data.py      |    3 +-
 .../analysis/calculators/test_base.py         |    3 +-
 .../analysis/calculators/test_crysfml.py      |    2 +-
 .../analysis/calculators/test_cryspy.py       |    3 +-
 .../analysis/calculators/test_factory.py      |    2 +-
 .../analysis/calculators/test_pdffit.py       |    2 +-
 .../analysis/categories/test_aliases.py       |    6 +-
 .../analysis/categories/test_constraints.py   |    6 +-
 .../categories/test_joint_fit_experiments.py  |    6 +-
 .../analysis/fit_helpers/test_metrics.py      |    2 +-
 .../analysis/fit_helpers/test_reporting.py    |    3 +-
 .../analysis/fit_helpers/test_tracking.py     |    2 +-
 .../analysis/minimizers/test_base.py          |    2 +-
 .../analysis/minimizers/test_dfols.py         |    2 +-
 .../analysis/minimizers/test_factory.py       |    3 +-
 .../analysis/minimizers/test_lmfit.py         |    2 +-
 .../easydiffraction/analysis/test_analysis.py |    4 +-
 .../analysis/test_analysis_access_params.py   |    7 +-
 .../analysis/test_analysis_show_empty.py      |    3 +-
 .../easydiffraction/analysis/test_fitting.py  |    3 +-
 .../easydiffraction/core/test_category.py     |    6 +-
 .../easydiffraction/core/test_collection.py   |    8 +-
 .../easydiffraction/core/test_datablock.py    |    8 +-
 .../easydiffraction/core/test_diagnostic.py   |    2 +-
 .../unit/easydiffraction/core/test_factory.py |    5 +-
 tests/unit/easydiffraction/core/test_guard.py |    4 +-
 .../easydiffraction/core/test_identity.py     |    3 +-
 .../easydiffraction/core/test_parameters.py   |   12 +-
 .../easydiffraction/core/test_singletons.py   |    2 +-
 .../easydiffraction/core/test_validation.py   |    3 +-
 .../crystallography/test_crystallography.py   |    3 +-
 .../crystallography/test_space_groups.py      |    3 +-
 .../categories/background/test_base.py        |    4 +-
 .../categories/background/test_chebyshev.py   |    2 +-
 .../categories/background/test_enums.py       |    8 +-
 .../categories/background/test_factory.py     |    6 +-
 .../background/test_line_segment.py           |    2 +-
 .../categories/data/test_bragg_pd.py          |    3 +-
 .../categories/data/test_bragg_sc.py          |    3 +-
 .../categories/data/test_factory.py           |   18 +-
 .../categories/data/test_total_pd.py          |    3 +-
 .../categories/instrument/test_base.py        |    3 +-
 .../categories/instrument/test_cwl.py         |    2 +-
 .../categories/instrument/test_factory.py     |    6 +-
 .../categories/instrument/test_tof.py         |    2 +-
 .../experiment/categories/peak/test_base.py   |    2 +-
 .../experiment/categories/peak/test_cwl.py    |    3 +-
 .../categories/peak/test_cwl_mixins.py        |    2 +-
 .../categories/peak/test_factory.py           |    2 +-
 .../experiment/categories/peak/test_tof.py    |    2 +-
 .../categories/peak/test_tof_mixins.py        |   12 +-
 .../experiment/categories/peak/test_total.py  |    2 +-
 .../categories/peak/test_total_mixins.py      |    2 +-
 .../categories/test_excluded_regions.py       |    6 +-
 .../categories/test_experiment_type.py        |    3 +-
 .../experiment/categories/test_extinction.py  |    4 +-
 .../categories/test_linked_crystal.py         |    4 +-
 .../categories/test_linked_phases.py          |    3 +-
 .../datablocks/experiment/item/test_base.py   |    3 +-
 .../experiment/item/test_bragg_pd.py          |    4 +-
 .../experiment/item/test_bragg_sc.py          |    3 +-
 .../datablocks/experiment/item/test_enums.py  |    3 +-
 .../experiment/item/test_factory.py           |    4 +-
 .../experiment/item/test_total_pd.py          |    2 +-
 .../datablocks/experiment/test_collection.py  |    5 +-
 .../structure/categories/test_space_group.py  |    2 +-
 .../datablocks/structure/item/test_base.py    |    2 +-
 .../datablocks/structure/item/test_factory.py |    2 +-
 .../datablocks/structure/test_collection.py   |    6 +-
 .../display/plotters/test_ascii.py            |    2 +-
 .../display/plotters/test_base.py             |   41 +-
 .../display/plotters/test_plotly.py           |    3 +-
 .../easydiffraction/display/test_plotting.py  |   19 +-
 .../easydiffraction/io/cif/test_handler.py    |    3 +-
 .../easydiffraction/io/cif/test_serialize.py  |    3 +-
 .../io/cif/test_serialize_more.py             |    5 +-
 .../easydiffraction/project/test_project.py   |    4 +-
 .../project/test_project_info.py              |    3 +-
 .../test_project_load_and_summary_wrap.py     |    3 +-
 .../project/test_project_save.py              |    3 +-
 .../easydiffraction/summary/test_summary.py   |    8 +-
 .../summary/test_summary_details.py           |    3 +-
 tests/unit/easydiffraction/test___init__.py   |    3 +-
 tests/unit/easydiffraction/test___main__.py   |    2 +-
 .../easydiffraction/utils/test_logging.py     |    3 +-
 .../utils/test_theme_detect.py                |  215 +-
 .../unit/easydiffraction/utils/test_utils.py  |    6 +-
 tmp/show_d401.py                              |   26 +
 tmp/show_w505.py                              |   27 +
 tools/add_assets_to_docs.sh                   |   16 -
 tools/cleanup_docs.sh                         |   10 -
 tools/convert_google_docstrings_to_numpy.py   |  539 +++
 tools/create_mkdocs_yml.py                    |  158 -
 tools/license_headers.py                      |  315 ++
 tools/param_consistency.py                    |  677 ++++
 tools/test_scripts.py                         |   10 +-
 tools/update_docs_assets.py                   |   91 +
 tools/update_github_labels.py                 |  341 ++
 tools/update_spdx.py                          |  109 -
 tutorials/data/ed-3.xye                       | 3099 -----------------
 408 files changed, 24321 insertions(+), 10182 deletions(-)
 create mode 100644 .copier-answers.yml
 create mode 100644 .github/actions/download-artifact/action.yml
 create mode 100644 .github/actions/github-script/action.yml
 create mode 100644 .github/actions/setup-easyscience-bot/action.yml
 create mode 100644 .github/actions/setup-pixi/action.yml
 create mode 100644 .github/actions/upload-artifact/action.yml
 create mode 100644 .github/actions/upload-codecov/action.yml
 create mode 100644 .github/configs/pages-deployment.json
 create mode 100644 .github/configs/rulesets-develop.json
 create mode 100644 .github/configs/rulesets-gh-pages.json
 create mode 100644 .github/configs/rulesets-master.json
 create mode 100644 .github/scripts/backmerge-conflict-issue.js
 delete mode 100644 .github/workflows/backmerge.yaml
 create mode 100644 .github/workflows/backmerge.yml
 rename .github/workflows/{cleanup.yaml => cleanup.yml} (88%)
 rename .github/workflows/{coverage.yaml => coverage.yml} (50%)
 rename .github/workflows/{dashboard.yaml => dashboard.yml} (66%)
 rename .github/workflows/{docs.yaml => docs.yml} (66%)
 create mode 100644 .github/workflows/issues-labels.yml
 create mode 100644 .github/workflows/lint-format.yml
 rename .github/workflows/{labels.yaml => pr-labels.yml} (70%)
 delete mode 100644 .github/workflows/pypi-publish.yaml
 create mode 100644 .github/workflows/pypi-publish.yml
 delete mode 100644 .github/workflows/pypi-test.yaml
 create mode 100644 .github/workflows/pypi-test.yml
 delete mode 100644 .github/workflows/quality.yaml
 rename .github/workflows/{release-notes.yaml => release-notes.yml} (81%)
 delete mode 100644 .github/workflows/release-pr.yaml
 create mode 100644 .github/workflows/release-pr.yml
 delete mode 100644 .github/workflows/security.yaml
 create mode 100644 .github/workflows/security.yml
 rename .github/workflows/{test-trigger.yaml => test-trigger.yml} (61%)
 delete mode 100644 .github/workflows/test.yaml
 create mode 100644 .github/workflows/test.yml
 rename .github/workflows/{tutorial-tests-trigger.yaml => tutorial-tests-trigger.yml} (61%)
 rename .github/workflows/{tutorial-tests.yaml => tutorial-tests.yml} (55%)
 delete mode 100644 DEVELOPMENT.md
 rename docs/{ => docs}/api-reference/analysis.md (100%)
 rename docs/{ => docs}/api-reference/core.md (100%)
 rename docs/{ => docs}/api-reference/crystallography.md (100%)
 rename docs/{ => docs}/api-reference/datablocks/experiment.md (100%)
 rename docs/{ => docs}/api-reference/datablocks/structure.md (100%)
 rename docs/{ => docs}/api-reference/display.md (100%)
 rename docs/{ => docs}/api-reference/index.md (79%)
 rename docs/{ => docs}/api-reference/io.md (100%)
 rename docs/{ => docs}/api-reference/project.md (100%)
 rename docs/{ => docs}/api-reference/summary.md (100%)
 rename docs/{ => docs}/api-reference/utils.md (100%)
 create mode 100644 docs/docs/assets/images/favicon.png
 create mode 100644 docs/docs/assets/images/logo_dark.svg
 create mode 100644 docs/docs/assets/images/logo_light.svg
 rename docs/{ => docs}/assets/images/user-guide/data-acquisition_2d-raw-data.jpg (100%)
 rename docs/{ => docs}/assets/images/user-guide/data-acquisition_instrument.png (100%)
 rename docs/{ => docs}/assets/images/user-guide/data-analysis_model.png (100%)
 rename docs/{ => docs}/assets/images/user-guide/data-analysis_refinement.png (100%)
 rename docs/{ => docs}/assets/images/user-guide/data-reduction_1d-pattern.png (100%)
 create mode 100644 docs/docs/assets/javascripts/extra.js
 create mode 100644 docs/docs/assets/javascripts/mathjax.js
 create mode 100644 docs/docs/assets/stylesheets/extra.css
 rename docs/{ => docs}/index.md (53%)
 rename docs/{ => docs}/installation-and-setup/index.md (80%)
 rename docs/{ => docs}/introduction/index.md (90%)
 create mode 100644 docs/docs/tutorials/ed-1.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-1.py (100%)
 create mode 100644 docs/docs/tutorials/ed-10.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-10.py (100%)
 create mode 100644 docs/docs/tutorials/ed-11.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-11.py (100%)
 create mode 100644 docs/docs/tutorials/ed-12.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-12.py (100%)
 create mode 100644 docs/docs/tutorials/ed-13.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-13.py (100%)
 create mode 100644 docs/docs/tutorials/ed-14.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-14.py (100%)
 create mode 100644 docs/docs/tutorials/ed-15.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-15.py (100%)
 create mode 100644 docs/docs/tutorials/ed-16.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-16.py (100%)
 create mode 100644 docs/docs/tutorials/ed-2.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-2.py (100%)
 create mode 100644 docs/docs/tutorials/ed-3.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-3.py (100%)
 create mode 100644 docs/docs/tutorials/ed-4.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-4.py (100%)
 create mode 100644 docs/docs/tutorials/ed-5.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-5.py (100%)
 create mode 100644 docs/docs/tutorials/ed-6.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-6.py (100%)
 create mode 100644 docs/docs/tutorials/ed-7.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-7.py (100%)
 create mode 100644 docs/docs/tutorials/ed-8.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-8.py (100%)
 create mode 100644 docs/docs/tutorials/ed-9.ipynb
 rename {tutorials => docs/docs/tutorials}/ed-9.py (100%)
 rename {tutorials => docs/docs/tutorials}/index.json (98%)
 create mode 100644 docs/docs/tutorials/index.md
 rename docs/{ => docs}/user-guide/analysis-workflow/analysis.md (68%)
 rename docs/{ => docs}/user-guide/analysis-workflow/experiment.md (84%)
 create mode 100644 docs/docs/user-guide/analysis-workflow/index.md
 rename docs/{ => docs}/user-guide/analysis-workflow/model.md (77%)
 rename docs/{ => docs}/user-guide/analysis-workflow/project.md (85%)
 rename docs/{ => docs}/user-guide/analysis-workflow/summary.md (72%)
 rename docs/{ => docs}/user-guide/concept.md (60%)
 rename docs/{ => docs}/user-guide/data-format.md (76%)
 rename docs/{ => docs}/user-guide/first-steps.md (56%)
 rename docs/{ => docs}/user-guide/glossary.md (80%)
 rename docs/{ => docs}/user-guide/index.md (57%)
 rename docs/{ => docs}/user-guide/parameters.md (90%)
 rename docs/{ => docs}/user-guide/parameters/_diffrn_radiation.md (87%)
 rename docs/{ => docs}/user-guide/parameters/_diffrn_radiation_wavelength.md (95%)
 rename docs/{ => docs}/user-guide/parameters/_exptl_crystal.md (100%)
 rename docs/{ => docs}/user-guide/parameters/_extinction.md (100%)
 rename docs/{ => docs}/user-guide/parameters/_pd_calib.md (94%)
 rename docs/{ => docs}/user-guide/parameters/atom_site.md (82%)
 rename docs/{ => docs}/user-guide/parameters/background.md (75%)
 rename docs/{ => docs}/user-guide/parameters/cell.md (95%)
 rename docs/{ => docs}/user-guide/parameters/expt_type.md (95%)
 rename docs/{ => docs}/user-guide/parameters/instrument.md (73%)
 rename docs/{ => docs}/user-guide/parameters/linked_phases.md (86%)
 rename docs/{ => docs}/user-guide/parameters/pd_meas.md (97%)
 rename docs/{ => docs}/user-guide/parameters/peak.md (95%)
 rename docs/{ => docs}/user-guide/parameters/space_group.md (87%)
 create mode 100644 docs/includes/abbreviations.md
 create mode 100644 docs/overrides/.icons/app.svg
 create mode 100644 docs/overrides/.icons/easydiffraction.svg
 create mode 100644 docs/overrides/.icons/easyscience.svg
 create mode 100644 docs/overrides/.icons/google-colab.svg
 create mode 100644 docs/overrides/main.html
 create mode 100644 docs/overrides/partials/logo.html
 delete mode 100644 docs/tutorials/index.md
 delete mode 100644 docs/user-guide/analysis-workflow/index.md
 delete mode 100644 pytest.ini
 create mode 100644 tmp/show_d401.py
 create mode 100644 tmp/show_w505.py
 delete mode 100755 tools/add_assets_to_docs.sh
 delete mode 100755 tools/cleanup_docs.sh
 create mode 100644 tools/convert_google_docstrings_to_numpy.py
 delete mode 100644 tools/create_mkdocs_yml.py
 create mode 100644 tools/license_headers.py
 create mode 100644 tools/param_consistency.py
 create mode 100644 tools/update_docs_assets.py
 create mode 100644 tools/update_github_labels.py
 delete mode 100644 tools/update_spdx.py
 delete mode 100644 tutorials/data/ed-3.xye

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

-**EasyDiffraction** is a Python package for calculating neutron powder -diffraction patterns based on a structural model and refining its parameters -against experimental data. +**EasyDiffraction** is a software for calculating neutron powder +diffraction patterns based on a structural model and refining its +parameters against experimental data. -**EasyDiffraction** is built upon the [EasyScience] framework, which provides -essential tools for developing scientific libraries and applications. + -## Useful Links - -- [Main Website] - Learn more about EasyDiffraction. -- [Documentation] - Access the full documentation. -- [Discussions] - Ask questions or share ideas. -- [Issue Tracker] - Report bugs or request new features. -- [Source Code] - Explore the source code repository. - -## Contributing +**EasyDiffraction** is developed both as a Python library and as a +cross-platform desktop application. -We welcome contributions! Our vision is for **EasyDiffraction** to be a -community-driven, open-source project supported by a diverse group of -contributors. +Here, we focus on the Python library. For the graphical user interface +(GUI), please see the corresponding +[GUI resources](https://github.com/easyscience/diffraction-app). -The project is currently maintained by the [European Spallation Source (ESS)]. +License: +[BSD 3-Clause](https://github.com/easyscience/diffraction-lib/blob/master/LICENSE) -If you'd like to contribute, please refer to our [Contributing Guidelines] for -information about our code of conduct and instructions on submitting pull -requests. - -## License - -**EasyDiffraction** is licensed under the [BSD 3-Clause License]. +## Useful Links - -[BSD 3-Clause License]: https://github.com/easyscience/diffraction-lib/blob/master/LICENSE -[Contributing Guidelines]: https://github.com/easyscience/diffraction-lib/blob/master/CONTRIBUTING.md -[EasyScience]: https://easyscience.software -[European Spallation Source (ESS)]: https://ess.eu -[Main Website]: https://easydiffraction.org -[Documentation]: https://docs.easydiffraction.org/lib -[Discussions]: https://github.com/easyscience/diffraction-lib/discussions -[Issue Tracker]: https://github.com/easyscience/diffraction-lib/issues -[Source Code]: https://github.com/easyscience/diffraction-lib - +### For Users + +- 📖 + [Documentation](https://easyscience.github.io/diffraction-lib/latest) +- 🚀 + [Getting Started](https://easyscience.github.io/diffraction-lib/latest/introduction) +- 🧪 + [Tutorials](https://easyscience.github.io/diffraction-lib/latest/tutorials) +- 💬 + [Get in Touch](https://easyscience.github.io/diffraction-lib/latest/introduction/#get-in-touch) +- 🧾 + [Citation](https://easyscience.github.io/diffraction-lib/latest/introduction/#citation) + +### For Contributors + +- 🧑‍💻 [Source Code](https://github.com/easyscience/diffraction-lib) +- 🐞 + [Issue Tracker](https://github.com/easyscience/diffraction-lib/issues) +- 💡 + [Discussions](https://github.com/easyscience/diffraction-lib/discussions) +- 🤝 + [Contributing Guide](https://github.com/easyscience/diffraction-lib/blob/master/CONTRIBUTING.md) +- 🛡 + [Code of Conduct](https://github.com/easyscience/.github/blob/master/CODE_OF_CONDUCT.md) diff --git a/codecov.yml b/codecov.yml index ffe29da9..f62b13ab 100644 --- a/codecov.yml +++ b/codecov.yml @@ -11,8 +11,3 @@ coverage: default: # Require patch coverage but with threshold threshold: 1% - -ignore: - # Vendored third-party code - not our code to test - - 'src/easydiffraction/utils/_vendored/**' - - 'src/easydiffraction/utils/_vendored/jupyter_dark_detect/**' diff --git a/docs/architecture/architecture.md b/docs/architecture/architecture.md index 3770f3ea..884ed4f1 100644 --- a/docs/architecture/architecture.md +++ b/docs/architecture/architecture.md @@ -8,11 +8,11 @@ ## 1. Overview -EasyDiffraction is a Python library for crystallographic diffraction analysis -(Rietveld refinement, pair-distribution-function fitting, etc.). It models the -domain using **CIF-inspired abstractions** — datablocks, categories, and -parameters — while providing a high-level, user-friendly API through a single -`Project` façade. +EasyDiffraction is a Python library for crystallographic diffraction +analysis (Rietveld refinement, pair-distribution-function fitting, +etc.). It models the domain using **CIF-inspired abstractions** — +datablocks, categories, and parameters — while providing a high-level, +user-friendly API through a single `Project` façade. ### 1.1 Supported Experiment Dimensions @@ -25,8 +25,8 @@ Every experiment is fully described by four orthogonal axes: | Beam mode | constant wavelength, time-of-flight | `BeamModeEnum` | | Radiation probe | neutron, X-ray | `RadiationProbeEnum` | -> **Planned extensions:** 1D / 2D data dimensionality, polarised / unpolarised -> neutron beam. +> **Planned extensions:** 1D / 2D data dimensionality, polarised / +> unpolarised neutron beam. ### 1.2 Calculation Engines @@ -56,50 +56,56 @@ GuardedBase # Controlled attribute access, parent lin └── DatablockItem # CIF data block (e.g. Structure, Experiment) ``` -`CollectionBase` provides a unified dict-like API over an ordered item list with -name-based indexing. All key operations — `__getitem__`, `__setitem__`, -`__delitem__`, `__contains__`, `remove()` — resolve keys through a single -`_key_for(item)` method that returns `category_entry_name` for category items or -`datablock_entry_name` for datablock items. Subclasses `CategoryCollection` and +`CollectionBase` provides a unified dict-like API over an ordered item +list with name-based indexing. All key operations — `__getitem__`, +`__setitem__`, `__delitem__`, `__contains__`, `remove()` — resolve keys +through a single `_key_for(item)` method that returns +`category_entry_name` for category items or `datablock_entry_name` for +datablock items. Subclasses `CategoryCollection` and `DatablockCollection` inherit this consistently. ### 2.2 GuardedBase — Controlled Attribute Access -`GuardedBase` is the root ABC. It enforces that only **declared `@property` -attributes** are accessible publicly: +`GuardedBase` is the root ABC. It enforces that only **declared +`@property` attributes** are accessible publicly: -- **`__getattr__`** rejects any attribute not declared as a `@property` on the - class hierarchy. Shows diagnostics with closest-match suggestions on typos. +- **`__getattr__`** rejects any attribute not declared as a `@property` + on the class hierarchy. Shows diagnostics with closest-match + suggestions on typos. - **`__setattr__`** distinguishes: - **Private** (`_`-prefixed) — always allowed, no diagnostics. - - **Read-only public** (property without setter) — blocked with a clear error. - - **Writable public** (property with setter) — goes through the property - setter, which is where validation happens. - - **Unknown** — blocked with diagnostics showing allowed writable attrs. -- **Parent linkage** — when a `GuardedBase` child is assigned to another, the - child's `_parent` is set automatically, forming an implicit ownership tree. -- **Identity** — every instance gets an `_identity: Identity` object for lazy - CIF-style name resolution (`datablock_entry_name`, `category_code`, - `category_entry_name`) by walking the `_parent` chain. - -**Key design rule:** if a parameter has a public setter, it is writable for the -user. If only a getter — it is read-only. If internal code needs to set it, a -private method (underscore prefix) is used. See § 2.2.1 below for the full -pattern. + - **Read-only public** (property without setter) — blocked with a + clear error. + - **Writable public** (property with setter) — goes through the + property setter, which is where validation happens. + - **Unknown** — blocked with diagnostics showing allowed writable + attrs. +- **Parent linkage** — when a `GuardedBase` child is assigned to + another, the child's `_parent` is set automatically, forming an + implicit ownership tree. +- **Identity** — every instance gets an `_identity: Identity` object for + lazy CIF-style name resolution (`datablock_entry_name`, + `category_code`, `category_entry_name`) by walking the `_parent` + chain. + +**Key design rule:** if a parameter has a public setter, it is writable +for the user. If only a getter — it is read-only. If internal code needs +to set it, a private method (underscore prefix) is used. See § 2.2.1 +below for the full pattern. #### 2.2.1 Public Property Convention — Editable vs Read-Only -Every public parameter or descriptor exposed on a `GuardedBase` subclass follows -one of two patterns: +Every public parameter or descriptor exposed on a `GuardedBase` subclass +follows one of two patterns: | Kind | Getter | Setter | Internal mutation | | ------------- | ------ | ------ | ---------------------------------- | | **Editable** | yes | yes | Via the public setter | | **Read-only** | yes | no | Via a private `_set_` method | -**Editable property** — the user can both read and write the value. The setter -runs through `GuardedBase.__setattr__` and into the property setter, where -validation happens: +**Editable property** — the user can both read and write the value. The +setter runs through `GuardedBase.__setattr__` and into the property +setter, where validation happens: ```python @property @@ -113,11 +119,11 @@ def name(self, new: str) -> None: self._name = new ``` -**Read-only property** — the user can read but cannot assign. Any attempt to set -the attribute is blocked by `GuardedBase.__setattr__` with a clear error -message. If _internal_ code (factory builders, CIF loaders, etc.) needs to set -the value, it calls a private `_set_` method instead of exposing a public -setter: +**Read-only property** — the user can read but cannot assign. Any +attempt to set the attribute is blocked by `GuardedBase.__setattr__` +with a clear error message. If _internal_ code (factory builders, CIF +loaders, etc.) needs to set the value, it calls a private `_set_` +method instead of exposing a public setter: ```python @property @@ -133,12 +139,13 @@ def _set_sample_form(self, value: str) -> None: **Why this matters:** -- `GuardedBase.__setattr__` uses the presence of a setter to decide writability. - Adding a setter "just for internal use" would open the attribute to users. +- `GuardedBase.__setattr__` uses the presence of a setter to decide + writability. Adding a setter "just for internal use" would open the + attribute to users. - Private `_set_` methods keep the public API surface minimal and intention-clear, while remaining greppable and type-safe. -- The pattern avoids string-based dispatch — every mutator has an explicit named - method. +- The pattern avoids string-based dispatch — every mutator has an + explicit named method. ### 2.3 CategoryItem and CategoryCollection @@ -153,8 +160,8 @@ def _set_sample_form(self, value: str) -> None: | Display | `show()` on concrete subclasses | `show()` on concrete subclasses | | Building items | N/A | `add(item)`, `create(**kwargs)` | -**Update priority:** lower values run first. This ensures correct execution -order within a datablock (e.g. background before data). +**Update priority:** lower values run first. This ensures correct +execution order within a datablock (e.g. background before data). ### 2.4 DatablockItem and DatablockCollection @@ -170,8 +177,9 @@ order within a datablock (e.g. background before data). | Dirty flag | `_need_categories_update` | N/A | When any `Parameter.value` is set, it propagates -`_need_categories_update = True` up to the owning `DatablockItem`. Serialisation -(`as_cif`) and plotting trigger `_update_categories()` if the flag is set. +`_need_categories_update = True` up to the owning `DatablockItem`. +Serialisation (`as_cif`) and plotting trigger `_update_categories()` if +the flag is set. ### 2.5 Variable System — Parameters and Descriptors @@ -191,18 +199,19 @@ CIF-bound concrete classes add a `CifHandler` for serialisation: | `NumericDescriptor` | `GenericNumericDescriptor` | Read-only or writable number | | `Parameter` | `GenericParameter` | Fittable numeric value | -**Initialisation rule:** all Parameters/Descriptors are initialised with their -default values from `value_spec` (an `AttributeSpec`) **without any validation** -— we trust internal definitions. Changes go through public property setters, -which run both type and value validation. +**Initialisation rule:** all Parameters/Descriptors are initialised with +their default values from `value_spec` (an `AttributeSpec`) **without +any validation** — we trust internal definitions. Changes go through +public property setters, which run both type and value validation. -**Mixin safety:** Parameter/Descriptor classes must not have init arguments so -they can be used as mixins safely (e.g. `PdTofDataPointMixin`). +**Mixin safety:** Parameter/Descriptor classes must not have init +arguments so they can be used as mixins safely (e.g. +`PdTofDataPointMixin`). ### 2.6 Validation -`AttributeSpec` bundles `default`, `data_type`, `validator`, `allow_none`. -Validators include: +`AttributeSpec` bundles `default`, `data_type`, `validator`, +`allow_none`. Validators include: | Validator | Purpose | | --------------------- | -------------------------------------- | @@ -217,13 +226,14 @@ Validators include: ### 3.1 Experiment Type -An experiment's type is defined by the four enum axes and is **immutable after -creation**. This avoids the complexity of transforming all internal state when -the experiment type changes. The type is stored in an `ExperimentType` category -with four `StringDescriptor`s validated by `MembershipValidator`s. Public -properties are read-only; factory and CIF-loading code use private setters -(`_set_sample_form`, `_set_beam_mode`, `_set_radiation_probe`, -`_set_scattering_type`) during construction only. +An experiment's type is defined by the four enum axes and is **immutable +after creation**. This avoids the complexity of transforming all +internal state when the experiment type changes. The type is stored in +an `ExperimentType` category with four `StringDescriptor`s validated by +`MembershipValidator`s. Public properties are read-only; factory and +CIF-loading code use private setters (`_set_sample_form`, +`_set_beam_mode`, `_set_radiation_probe`, `_set_scattering_type`) during +construction only. ### 3.2 Experiment Hierarchy @@ -245,8 +255,8 @@ Each concrete experiment class carries: ### 3.3 Category Ownership -Every experiment owns its categories as private attributes with public read-only -or read-write properties: +Every experiment owns its categories as private attributes with public +read-only or read-write properties: ```python # Read-only — user cannot replace the object, only modify its contents @@ -265,9 +275,10 @@ experiment.excluded_regions_type = 'default' # triggers ExcludedRegionsFactory. experiment.linked_phases_type = 'default' # triggers LinkedPhasesFactory.create(...) ``` -**Type switching pattern:** `expt.background_type = 'chebyshev'` rather than -`expt.background.type = 'chebyshev'`. This keeps the API at the experiment level -and makes it clear that the entire category object is being replaced. +**Type switching pattern:** `expt.background_type = 'chebyshev'` rather +than `expt.background.type = 'chebyshev'`. This keeps the API at the +experiment level and makes it clear that the entire category object is +being replaced. --- @@ -286,8 +297,8 @@ A `Structure` contains three categories: - `SpaceGroup` — symmetry information (`CategoryItem`) - `AtomSites` — atomic positions collection (`CategoryCollection`) -Symmetry constraints (cell metric, atomic coordinates, ADPs) are applied via the -`crystallography` module during `_update_categories()`. +Symmetry constraints (cell metric, atomic coordinates, ADPs) are applied +via the `crystallography` module during `_update_categories()`. --- @@ -308,13 +319,13 @@ All factories inherit from `FactoryBase`, which provides: | Display | `show_supported(**filters)` | Pretty-print table of type + description | | Tag listing | `supported_tags()` | List of all registered tags | -Each `__init_subclass__` gives every factory its own independent `_registry` and -`_default_rules`. +Each `__init_subclass__` gives every factory its own independent +`_registry` and `_default_rules`. ### 5.2 Default Rules -`_default_rules` maps frozensets of `(axis_name, enum_value)` tuples to tag -strings (preferably enum values for type safety): +`_default_rules` maps frozensets of `(axis_name, enum_value)` tuples to +tag strings (preferably enum values for type safety): ```python class PeakFactory(FactoryBase): @@ -333,13 +344,14 @@ class PeakFactory(FactoryBase): } ``` -Resolution uses **largest-subset matching**: the rule whose frozenset is the -biggest subset of the given conditions wins. `frozenset()` acts as a universal -fallback. +Resolution uses **largest-subset matching**: the rule whose frozenset is +the biggest subset of the given conditions wins. `frozenset()` acts as a +universal fallback. ### 5.3 Metadata on Registered Classes -Every `@Factory.register`-ed class carries three frozen dataclass attributes: +Every `@Factory.register`-ed class carries three frozen dataclass +attributes: ```python @PeakFactory.register @@ -365,8 +377,9 @@ class CwlPseudoVoigt(PeakBase, CwlBroadeningMixin): ### 5.4 Registration Trigger -Concrete classes use `@Factory.register` decorators. To trigger registration, -each package's `__init__.py` must **explicitly import** every concrete class: +Concrete classes use `@Factory.register` decorators. To trigger +registration, each package's `__init__.py` must **explicitly import** +every concrete class: ```python # datablocks/experiment/categories/background/__init__.py @@ -397,12 +410,13 @@ from .line_segment import LineSegmentBackground | `CalculatorFactory` | Calculation engines | `CryspyCalculator`, `CrysfmlCalculator`, `PdffitCalculator` | | `MinimizerFactory` | Minimisers | `LmfitMinimizer`, `DfolsMinimizer`, … | -> **Note:** `ExperimentFactory` and `StructureFactory` are _builder_ factories -> with `from_cif_path`, `from_cif_str`, `from_data_path`, and `from_scratch` -> classmethods. `ExperimentFactory` inherits `FactoryBase` and uses `@register` -> on all four concrete experiment classes; `_resolve_class` looks up the -> registered class via `default_tag()` + `_supported_map()`. `StructureFactory` -> is a plain class without `FactoryBase` inheritance (only one structure type +> **Note:** `ExperimentFactory` and `StructureFactory` are _builder_ +> factories with `from_cif_path`, `from_cif_str`, `from_data_path`, and +> `from_scratch` classmethods. `ExperimentFactory` inherits +> `FactoryBase` and uses `@register` on all four concrete experiment +> classes; `_resolve_class` looks up the registered class via +> `default_tag()` + `_supported_map()`. `StructureFactory` is a plain +> class without `FactoryBase` inheritance (only one structure type > exists today). ### 5.6 Tag Naming Convention @@ -502,9 +516,9 @@ Tags are the user-facing identifiers for selecting types. They must be: | `lmfit (least_squares)` | `LmfitMinimizer` (method=`least_squares`) | | `dfols` | `DfolsMinimizer` | -> **Note:** minimizer variant tags (`lmfit (leastsq)`, `lmfit (least_squares)`) -> are planned but not yet re-implemented after the `FactoryBase` migration. See -> `issues_open.md` for details. +> **Note:** minimizer variant tags (`lmfit (leastsq)`, +> `lmfit (least_squares)`) are planned but not yet re-implemented after +> the `FactoryBase` migration. See `issues_open.md` for details. ### 5.7 Metadata Classification — Which Classes Get What @@ -514,16 +528,17 @@ Tags are the user-facing identifiers for selecting types. They must be: > `compatibility`, and `calculator_support`.** > > **If a `CategoryItem` only exists as a child row inside a -> `CategoryCollection`, it does NOT get these attributes — the collection -> does.** +> `CategoryCollection`, it does NOT get these attributes — the +> collection does.** #### Rationale -A `LineSegment` item (a single background control point) is never selected, -created, or queried by a factory. It is always instantiated internally by its -parent `LineSegmentBackground` collection. The meaningful unit of selection is -the _collection_, not the item. The user picks "line-segment background" (the -collection type), not individual line-segment points. +A `LineSegment` item (a single background control point) is never +selected, created, or queried by a factory. It is always instantiated +internally by its parent `LineSegmentBackground` collection. The +meaningful unit of selection is the _collection_, not the item. The user +picks "line-segment background" (the collection type), not individual +line-segment points. #### Singleton CategoryItems — factory-created (get all three) @@ -601,28 +616,32 @@ collection type), not individual line-segment points. ### 6.1 Calculator -The calculator performs the actual diffraction computation. It is attached -per-experiment on the `ExperimentBase` object. Each experiment auto-resolves its -calculator on first access based on the data category's `calculator_support` -metadata and `CalculatorFactory._default_rules`. The `CalculatorFactory` filters -its registry by `engine_imported` (whether the third-party library is available -in the environment). +The calculator performs the actual diffraction computation. It is +attached per-experiment on the `ExperimentBase` object. Each experiment +auto-resolves its calculator on first access based on the data +category's `calculator_support` metadata and +`CalculatorFactory._default_rules`. The `CalculatorFactory` filters its +registry by `engine_imported` (whether the third-party library is +available in the environment). The experiment exposes the standard switchable-category API: -- `calculator` — read-only property (lazy, auto-resolved on first access) +- `calculator` — read-only property (lazy, auto-resolved on first + access) - `calculator_type` — getter + setter -- `show_supported_calculator_types()` — filtered by data category support +- `show_supported_calculator_types()` — filtered by data category + support - `show_current_calculator_type()` ### 6.2 Minimiser -The minimiser drives the optimisation loop. `MinimizerFactory` creates instances -by tag (e.g. `'lmfit'`, `'dfols'`). +The minimiser drives the optimisation loop. `MinimizerFactory` creates +instances by tag (e.g. `'lmfit'`, `'dfols'`). ### 6.3 Fitter -`Fitter` wraps a minimiser instance and orchestrates the fitting workflow: +`Fitter` wraps a minimiser instance and orchestrates the fitting +workflow: 1. Collect `free_parameters` from structures + experiments. 2. Record start values. @@ -634,10 +653,12 @@ by tag (e.g. `'lmfit'`, `'dfols'`). `Analysis` is bound to a `Project` and provides the high-level API: -- Minimiser selection: `current_minimizer`, `show_available_minimizers()` -- Fit mode: `fit_mode` (`CategoryItem` with a `mode` descriptor validated by - `FitModeEnum`); `'single'` fits each experiment independently, `'joint'` fits - all simultaneously with weights from `joint_fit_experiments`. +- Minimiser selection: `current_minimizer`, + `show_available_minimizers()` +- Fit mode: `fit_mode` (`CategoryItem` with a `mode` descriptor + validated by `FitModeEnum`); `'single'` fits each experiment + independently, `'joint'` fits all simultaneously with weights from + `joint_fit_experiments`. - Joint-fit weights: `joint_fit_experiments` (`CategoryCollection` of per-experiment weight entries); sibling of `fit_mode`, not a child. - Parameter tables: `show_all_params()`, `show_fittable_params()`, @@ -845,26 +866,26 @@ project.experiments['xray_pdf'].peak_profile_type = 'gaussian-damped-sinc' ### 9.1 Naming and CIF Conventions -- Follow CIF naming conventions where possible. Deviate for better API design - when necessary, but keep the spirit of CIF names. +- Follow CIF naming conventions where possible. Deviate for better API + design when necessary, but keep the spirit of CIF names. - Reuse the concept of datablocks and categories from CIF. -- `DatablockItem` = one CIF `data_` block, `DatablockCollection` = set of - blocks. +- `DatablockItem` = one CIF `data_` block, `DatablockCollection` = set + of blocks. - `CategoryItem` = one CIF category, `CategoryCollection` = CIF loop. ### 9.2 Immutability of Experiment Type -The experiment type (the four enum axes) can only be set at creation time. It -cannot be changed afterwards. This avoids the complexity of maintaining -different state transformations when switching between fundamentally different -experiment configurations. +The experiment type (the four enum axes) can only be set at creation +time. It cannot be changed afterwards. This avoids the complexity of +maintaining different state transformations when switching between +fundamentally different experiment configurations. ### 9.3 Category Type Switching -In contrast to experiment type, categories that have multiple implementations -(peak profiles, backgrounds, instruments) can be switched at runtime by the -user. The API pattern uses a type property on the **experiment**, not on the -category itself: +In contrast to experiment type, categories that have multiple +implementations (peak profiles, backgrounds, instruments) can be +switched at runtime by the user. The API pattern uses a type property on +the **experiment**, not on the category itself: ```python # ✅ Correct — type property on the experiment @@ -874,16 +895,16 @@ expt.background_type = 'chebyshev' expt.background.type = 'chebyshev' ``` -This makes it clear that the entire category object is being replaced and -simplifies maintenance. +This makes it clear that the entire category object is being replaced +and simplifies maintenance. ### 9.4 Switchable-Category Convention -Categories whose concrete implementation can be swapped at runtime (background, -peak profile, etc.) are called **switchable categories**. **Every category must -be factory-based** — even if only one implementation exists today. This ensures -a uniform API, consistent discoverability, and makes adding a second -implementation trivial. +Categories whose concrete implementation can be swapped at runtime +(background, peak profile, etc.) are called **switchable categories**. +**Every category must be factory-based** — even if only one +implementation exists today. This ensures a uniform API, consistent +discoverability, and makes adding a second implementation trivial. | Facet | Naming pattern | Example | | --------------- | -------------------------------------------- | ------------------------------------------------ | @@ -894,26 +915,31 @@ implementation trivial. The convention applies universally: -- **Experiment:** `calculator_type`, `background_type`, `peak_profile_type`, - `extinction_type`, `linked_crystal_type`, `excluded_regions_type`, - `linked_phases_type`, `instrument_type`, `data_type`. +- **Experiment:** `calculator_type`, `background_type`, + `peak_profile_type`, `extinction_type`, `linked_crystal_type`, + `excluded_regions_type`, `linked_phases_type`, `instrument_type`, + `data_type`. - **Structure:** `cell_type`, `space_group_type`, `atom_sites_type`. - **Analysis:** `aliases_type`, `constraints_type`, `fit_mode_type`, `joint_fit_experiments_type`. **Design decisions:** -- The **experiment owns** the `_type` setter because switching replaces the - entire category object (`self._background = BackgroundFactory.create(...)`). -- The **experiment owns** the `show_*` methods because they are one-liners that - delegate to `Factory.show_supported(...)` and can pass experiment-specific - context (e.g. `scattering_type`, `beam_mode` for peak filtering). -- Concrete category subclasses provide a public `show()` method for displaying - the current content (not on the base `CategoryItem`/`CategoryCollection`). +- The **experiment owns** the `_type` setter because switching replaces + the entire category object + (`self._background = BackgroundFactory.create(...)`). +- The **experiment owns** the `show_*` methods because they are + one-liners that delegate to `Factory.show_supported(...)` and can pass + experiment-specific context (e.g. `scattering_type`, `beam_mode` for + peak filtering). +- Concrete category subclasses provide a public `show()` method for + displaying the current content (not on the base + `CategoryItem`/`CategoryCollection`). ### 9.5 Discoverable Supported Options -The user can always discover what is supported for the current experiment: +The user can always discover what is supported for the current +experiment: ```python expt.show_supported_peak_profile_types() @@ -935,36 +961,38 @@ project.analysis.show_supported_joint_fit_experiments_types() project.analysis.show_available_minimizers() ``` -Available calculators are filtered by `engine_imported` (whether the library is -installed) and by the experiment's data category `calculator_support` metadata. +Available calculators are filtered by `engine_imported` (whether the +library is installed) and by the experiment's data category +`calculator_support` metadata. ### 9.6 Enums for Finite Value Sets -Every attribute, descriptor, or configuration option that accepts a **finite, -closed set of values** must be represented by a `(str, Enum)` class. This -applies to: +Every attribute, descriptor, or configuration option that accepts a +**finite, closed set of values** must be represented by a `(str, Enum)` +class. This applies to: - Factory tags (§5.6) — e.g. `PeakProfileTypeEnum`, `CalculatorEnum`. - Experiment-axis values — e.g. `SampleFormEnum`, `BeamModeEnum`. - Category descriptors with enumerated choices — e.g. fit mode (`FitModeEnum.SINGLE`, `FitModeEnum.JOINT`). -The enum serves as the **single source of truth** for valid values, their -user-facing string representations, and their descriptions. Benefits: +The enum serves as the **single source of truth** for valid values, +their user-facing string representations, and their descriptions. +Benefits: -- **Autocomplete and typo safety** — IDEs list valid members; misspellings are - caught at assignment time. -- **Greppable** — searching for `FitModeEnum.JOINT` finds every code path that - handles joint fitting. -- **Type-safe dispatch** — `if mode == FitModeEnum.JOINT:` is checked by type - checkers; `if mode == 'joint':` is not. -- **Consistent validation** — use `MembershipValidator` with the enum members - instead of `RegexValidator` with hand-written patterns. +- **Autocomplete and typo safety** — IDEs list valid members; + misspellings are caught at assignment time. +- **Greppable** — searching for `FitModeEnum.JOINT` finds every code + path that handles joint fitting. +- **Type-safe dispatch** — `if mode == FitModeEnum.JOINT:` is checked by + type checkers; `if mode == 'joint':` is not. +- **Consistent validation** — use `MembershipValidator` with the enum + members instead of `RegexValidator` with hand-written patterns. -**Rule:** internal code must compare against enum members, never raw strings. -User-facing setters accept either the enum member or its string value (because -`str(EnumMember) == EnumMember.value` for `(str, Enum)`), but internal dispatch -always uses the enum: +**Rule:** internal code must compare against enum members, never raw +strings. User-facing setters accept either the enum member or its string +value (because `str(EnumMember) == EnumMember.value` for `(str, Enum)`), +but internal dispatch always uses the enum: ```python # ✅ Correct — compare with enum @@ -976,10 +1004,10 @@ if self._fit_mode.mode.value == 'joint': ### 9.7 Flat Category Structure — No Nested Categories -Following CIF conventions, categories are **flat siblings** within their owner -(datablock or analysis object). A category must never be a child of another -category of a different type. Categories can reference each other via IDs, but -the ownership hierarchy is always: +Following CIF conventions, categories are **flat siblings** within their +owner (datablock or analysis object). A category must never be a child +of another category of a different type. Categories can reference each +other via IDs, but the ownership hierarchy is always: ``` Owner (DatablockItem / Analysis) @@ -999,7 +1027,8 @@ Owner **Example — `fit_mode` and `joint_fit_experiments`:** `fit_mode` is a `CategoryItem` holding the active strategy (`'single'` or `'joint'`). `joint_fit_experiments` is a separate `CategoryCollection` holding -per-experiment weights. Both are direct children of `Analysis`, not nested: +per-experiment weights. Both are direct children of `Analysis`, not +nested: ```python # ✅ Correct — sibling categories on Analysis @@ -1022,13 +1051,80 @@ npd 0.7 xrd 0.3 ``` +### 9.8 Property Docstring and Type-Hint Template + +Every public property backed by a private `Parameter`, +`NumericDescriptor`, or `StringDescriptor` attribute must follow the +template below. The `description` field on the descriptor is the +**single source of truth**; docstrings and type hints are mechanically +derived from it. + +**Definitions:** + +| Symbol | Meaning | +| --------- | -------------------------------------------------------------------------------------- | +| `{desc}` | `description` string without trailing period | +| `{units}` | `units` string; omit the `({units})` parenthetical when absent/empty | +| `{Type}` | Descriptor class name: `Parameter`, `NumericDescriptor`, or `StringDescriptor` | +| `{ann}` | Setter value annotation: `float` for numeric descriptors, `str` for string descriptors | + +**Template — writable property:** + +```python +@property +def length_a(self) -> Parameter: + """Length of the a axis of the unit cell (Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ + return self._length_a + +@length_a.setter +def length_a(self, value: float) -> None: + self._length_a.value = value +``` + +**Template — read-only property:** + +```python +@property +def length_a(self) -> Parameter: + """Length of the a axis of the unit cell (Å). + + Reading this property returns the underlying ``Parameter`` + object. + """ + return self._length_a +``` + +**Quick-reference table:** + +| Element | Text | +| ---------------------- | -------------------------------------------------------------------------------------------------------------- | +| Getter summary line | `"""{desc} ({units}).` (or `"""{desc}.` when unitless) | +| Getter body (writable) | `Reading this property returns the underlying ``{Type}`` object. Assigning to it updates the parameter value.` | +| Getter body (readonly) | `Reading this property returns the underlying ``{Type}`` object.` | +| Setter docstring | _(none — not rendered by griffe / MkDocs)_ | +| Getter annotation | `-> {Type}` | +| Setter annotation | `value: {ann}` and `-> None` | + +**Notes:** + +- Getter docstrings have **no** `Args:` or `Returns:` sections. +- Setters have **no** docstring. +- Avoid markdown emphasis (`*a*`) in docstrings; use plain text to stay + in sync with the `description` field. +- The CI tool `pixi run param-consistency-check` validates compliance; + `pixi run param-consistency-fix` auto-fixes violations. + --- ## 10. Issues - **Open:** [`issues_open.md`](issues_open.md) — prioritised backlog. -- **Closed:** [`issues_closed.md`](issues_closed.md) — resolved items for - reference. +- **Closed:** [`issues_closed.md`](issues_closed.md) — resolved items + for reference. When a resolution affects the architecture described above, the relevant sections of this document are updated accordingly. diff --git a/docs/architecture/issues_closed.md b/docs/architecture/issues_closed.md index bc8ea19b..a67edcbe 100644 --- a/docs/architecture/issues_closed.md +++ b/docs/architecture/issues_closed.md @@ -6,26 +6,29 @@ Issues that have been fully resolved. Kept for historical reference. ## Dirty-Flag Guard Was Disabled -**Resolution:** added `_set_value_from_minimizer()` on `GenericDescriptorBase` -that writes `_value` directly (no validation) but sets the dirty flag on the -parent `DatablockItem`. Both `LmfitMinimizer` and `DfolsMinimizer` now use it. -The guard in `DatablockItem._update_categories()` is enabled and skips redundant -updates on the user-facing path (CIF export, plotting). During fitting the guard -is bypassed (`called_by_minimizer=True`) because experiment calculations depend -on structure parameters owned by a different `DatablockItem`. +**Resolution:** added `_set_value_from_minimizer()` on +`GenericDescriptorBase` that writes `_value` directly (no validation) +but sets the dirty flag on the parent `DatablockItem`. Both +`LmfitMinimizer` and `DfolsMinimizer` now use it. The guard in +`DatablockItem._update_categories()` is enabled and skips redundant +updates on the user-facing path (CIF export, plotting). During fitting +the guard is bypassed (`called_by_minimizer=True`) because experiment +calculations depend on structure parameters owned by a different +`DatablockItem`. --- ## Move Calculator from Global to Per-Experiment -**Resolution:** removed the global calculator from `Analysis`. Each experiment -now owns its calculator, auto-resolved on first access from -`CalculatorFactory._default_rules` (maps `scattering_type` → default tag) and -filtered by the data category's `calculator_support` metadata (e.g. `PdCwlData` -→ `{CRYSPY}`, `TotalData` → `{PDFFIT}`). Calculator classes no longer carry -`compatibility` attributes — limitations are expressed on categories. The -experiment exposes the standard switchable-category API: `calculator` -(read-only, lazy), `calculator_type` (getter + setter), +**Resolution:** removed the global calculator from `Analysis`. Each +experiment now owns its calculator, auto-resolved on first access from +`CalculatorFactory._default_rules` (maps `scattering_type` → default +tag) and filtered by the data category's `calculator_support` metadata +(e.g. `PdCwlData` → `{CRYSPY}`, `TotalData` → `{PDFFIT}`). Calculator +classes no longer carry `compatibility` attributes — limitations are +expressed on categories. The experiment exposes the standard +switchable-category API: `calculator` (read-only, lazy), +`calculator_type` (getter + setter), `show_supported_calculator_types()`, `show_current_calculator_type()`. Tutorials, tests, and docs updated. @@ -33,28 +36,32 @@ Tutorials, tests, and docs updated. ## Add Universal Factories for All Categories -**Resolution:** converted every category to use the `FactoryBase` pattern. Each -former single-file category is now a package with `factory.py` (trivial -`FactoryBase` subclass), `default.py` (concrete class with `@register` + -`type_info`), and `__init__.py` (re-exports preserving import compatibility). +**Resolution:** converted every category to use the `FactoryBase` +pattern. Each former single-file category is now a package with +`factory.py` (trivial `FactoryBase` subclass), `default.py` (concrete +class with `@register` + `type_info`), and `__init__.py` (re-exports +preserving import compatibility). -Experiment categories: `Extinction` → `ShelxExtinction` / `ExtinctionFactory` -(tag `shelx`), `LinkedCrystal` / `LinkedCrystalFactory` (tag `default`), -`ExcludedRegions` / `ExcludedRegionsFactory`, `LinkedPhases` / -`LinkedPhasesFactory`, `ExperimentType` / `ExperimentTypeFactory`. +Experiment categories: `Extinction` → `ShelxExtinction` / +`ExtinctionFactory` (tag `shelx`), `LinkedCrystal` / +`LinkedCrystalFactory` (tag `default`), `ExcludedRegions` / +`ExcludedRegionsFactory`, `LinkedPhases` / `LinkedPhasesFactory`, +`ExperimentType` / `ExperimentTypeFactory`. Structure categories: `Cell` / `CellFactory`, `SpaceGroup` / `SpaceGroupFactory`, `AtomSites` / `AtomSitesFactory`. Analysis categories: `Aliases` / `AliasesFactory`, `Constraints` / -`ConstraintsFactory`, `JointFitExperiments` / `JointFitExperimentsFactory`. - -`ShelxExtinction` and `LinkedCrystal` get the full switchable-category API on -`ScExperimentBase` (`extinction_type`, `linked_crystal_type` getter+setter, -`show_supported_*_types()`, `show_current_*_type()`). `ExcludedRegions` and -`LinkedPhases` get the same API on `PdExperimentBase`. `Cell`, `SpaceGroup`, and -`AtomSites` get it on `Structure`. `Aliases` and `Constraints` get it on -`Analysis`. Architecture §3.3, §5.5, §5.7, §9.4, §9.5 updated. Copilot -instructions updated with universal switchable-category scope and -architecture-first workflow rule. Unit tests extended with factory tests for -extinction and linked-crystal. +`ConstraintsFactory`, `JointFitExperiments` / +`JointFitExperimentsFactory`. + +`ShelxExtinction` and `LinkedCrystal` get the full switchable-category +API on `ScExperimentBase` (`extinction_type`, `linked_crystal_type` +getter+setter, `show_supported_*_types()`, `show_current_*_type()`). +`ExcludedRegions` and `LinkedPhases` get the same API on +`PdExperimentBase`. `Cell`, `SpaceGroup`, and `AtomSites` get it on +`Structure`. `Aliases` and `Constraints` get it on `Analysis`. +Architecture §3.3, §5.5, §5.7, §9.4, §9.5 updated. Copilot instructions +updated with universal switchable-category scope and architecture-first +workflow rule. Unit tests extended with factory tests for extinction and +linked-crystal. diff --git a/docs/architecture/issues_open.md b/docs/architecture/issues_open.md index d8bd37a2..05ed175f 100644 --- a/docs/architecture/issues_open.md +++ b/docs/architecture/issues_open.md @@ -1,9 +1,10 @@ # EasyDiffraction — Open Issues -Prioritised list of issues, improvements, and design questions to address. Items -are ordered by a combination of user impact, blocking potential, and -implementation readiness. When an item is fully implemented, remove it from this -file and update `architecture.md` if needed. +Prioritised list of issues, improvements, and design questions to +address. Items are ordered by a combination of user impact, blocking +potential, and implementation readiness. When an item is fully +implemented, remove it from this file and update `architecture.md` if +needed. **Legend:** 🔴 High · 🟡 Medium · 🟢 Low @@ -13,15 +14,16 @@ file and update `architecture.md` if needed. **Type:** Completeness -`save()` serialises all components to CIF files but `load()` is a stub that -raises `NotImplementedError`. Users cannot round-trip a project. +`save()` serialises all components to CIF files but `load()` is a stub +that raises `NotImplementedError`. Users cannot round-trip a project. **Why first:** this is the highest-severity gap. Without it the save -functionality is only half useful — CIF files are written but cannot be read -back. Tutorials that demonstrate save/load are blocked. +functionality is only half useful — CIF files are written but cannot be +read back. Tutorials that demonstrate save/load are blocked. -**Fix:** implement `load()` that reads CIF files from the project directory and -reconstructs structures, experiments, and analysis settings. +**Fix:** implement `load()` that reads CIF files from the project +directory and reconstructs structures, experiments, and analysis +settings. **Depends on:** nothing (standalone). @@ -35,11 +37,12 @@ After the `FactoryBase` migration only `'lmfit'` and `'dfols'` remain as registered tags. The ability to select a specific lmfit algorithm (e.g. `'lmfit (leastsq)'`, `'lmfit (least_squares)'`) raises a `ValueError`. -The root cause is that `FactoryBase` assumes one class ↔ one tag; registering -the same class twice with different constructor arguments is not supported. +The root cause is that `FactoryBase` assumes one class ↔ one tag; +registering the same class twice with different constructor arguments is +not supported. -**Fix:** decide on an approach (thin subclasses, extended registry, or two-level -selection) and implement. Thin subclasses is the quickest. +**Fix:** decide on an approach (thin subclasses, extended registry, or +two-level selection) and implement. Thin subclasses is the quickest. **Planned tags:** @@ -58,8 +61,8 @@ selection) and implement. Thin subclasses is the quickest. | **B. Extend registry to store `(class, kwargs)` tuples** | No extra classes; factory handles variants natively | `_supported_map` changes shape; `TypeInfo` moves from class attribute to registration-time data | | **C. Two-level selection** (`engine` + `algorithm`) | Clean separation; engine maps to class, algorithm is a constructor arg | More complex API (`current_minimizer = ('lmfit', 'least_squares')`); needs new `FactoryBase` protocol | -**Depends on:** nothing (standalone, but should be decided before more factories -adopt variants). +**Depends on:** nothing (standalone, but should be decided before more +factories adopt variants). --- @@ -67,13 +70,14 @@ adopt variants). **Type:** Fragility -`joint_fit_experiments` is created once when `fit_mode` becomes `'joint'`. If -experiments are added, removed, or renamed afterwards, the weight collection is -stale. Joint fitting can fail with missing keys or run with incorrect weights. +`joint_fit_experiments` is created once when `fit_mode` becomes +`'joint'`. If experiments are added, removed, or renamed afterwards, the +weight collection is stale. Joint fitting can fail with missing keys or +run with incorrect weights. -**Fix:** rebuild or validate `joint_fit_experiments` at the start of every joint -fit. At minimum, `fit()` should assert that the weight keys exactly match -`project.experiments.names`. +**Fix:** rebuild or validate `joint_fit_experiments` at the start of +every joint fit. At minimum, `fit()` should assert that the weight keys +exactly match `project.experiments.names`. **Depends on:** nothing. @@ -85,18 +89,20 @@ fit. At minimum, `fit()` should assert that the weight keys exactly match `ConstraintsHandler` is only synchronised from `analysis.aliases` and `analysis.constraints` when the user explicitly calls -`project.analysis.apply_constraints()`. The normal fit / serialisation path -calls `constraints_handler.apply()` directly, so newly added or edited aliases -and constraints can be ignored until that manual sync step happens. +`project.analysis.apply_constraints()`. The normal fit / serialisation +path calls `constraints_handler.apply()` directly, so newly added or +edited aliases and constraints can be ignored until that manual sync +step happens. -**Why high:** this produces silently incorrect results. A user can define -constraints, run a fit, and believe they were applied when the active singleton -still contains stale state from a previous run or no state at all. +**Why high:** this produces silently incorrect results. A user can +define constraints, run a fit, and believe they were applied when the +active singleton still contains stale state from a previous run or no +state at all. **Fix:** before any automatic constraint application, always refresh the -singleton from the current `Aliases` and `Constraints` collections. The sync -should happen inside `Analysis._update_categories()` or inside the constraints -category itself, not only in a user-facing helper method. +singleton from the current `Aliases` and `Constraints` collections. The +sync should happen inside `Analysis._update_categories()` or inside the +constraints category itself, not only in a user-facing helper method. **Depends on:** nothing. @@ -106,10 +112,11 @@ category itself, not only in a user-facing helper method. **Type:** Consistency -`Analysis` owns categories (`Aliases`, `Constraints`, `JointFitExperiments`) but -does not extend `DatablockItem`. Its ad-hoc `_update_categories()` iterates over -a hard-coded list and does not participate in standard category discovery, -parameter enumeration, or CIF serialisation. +`Analysis` owns categories (`Aliases`, `Constraints`, +`JointFitExperiments`) but does not extend `DatablockItem`. Its ad-hoc +`_update_categories()` iterates over a hard-coded list and does not +participate in standard category discovery, parameter enumeration, or +CIF serialisation. **Fix:** make `Analysis` extend `DatablockItem`, or extract a shared `_update_categories()` protocol. @@ -122,20 +129,22 @@ parameter enumeration, or CIF serialisation. **Type:** Correctness + Data safety -`Experiment.data_type` currently validates against all registered data tags -rather than only those compatible with the experiment's `sample_form` / -`scattering_type` / `beam_mode`. This allows users to switch an experiment to an -incompatible data collection class. The setter also replaces the existing data -object with a fresh empty instance, discarding loaded data without warning. +`Experiment.data_type` currently validates against all registered data +tags rather than only those compatible with the experiment's +`sample_form` / `scattering_type` / `beam_mode`. This allows users to +switch an experiment to an incompatible data collection class. The +setter also replaces the existing data object with a fresh empty +instance, discarding loaded data without warning. -**Why high:** the current API can create internally inconsistent experiments and -silently lose measured data, which is especially dangerous for notebook and -tutorial workflows. +**Why high:** the current API can create internally inconsistent +experiments and silently lose measured data, which is especially +dangerous for notebook and tutorial workflows. -**Fix:** filter supported data types through `DataFactory.supported_for(...)` -using the current experiment context, and warn or block when a switch would -discard existing data. If runtime data-type switching is not a real user need, -consider making `data` effectively fixed after experiment creation. +**Fix:** filter supported data types through +`DataFactory.supported_for(...)` using the current experiment context, +and warn or block when a switch would discard existing data. If runtime +data-type switching is not a real user need, consider making `data` +effectively fixed after experiment creation. **Depends on:** nothing. @@ -145,16 +154,17 @@ consider making `data` effectively fixed after experiment creation. **Type:** Fragility -Single-fit mode creates a throw-away `Experiments` collection per experiment, -manually forces `_parent` via `object.__setattr__`, and passes it to `Fitter`. -This bypasses `GuardedBase` parent tracking and is fragile. +Single-fit mode creates a throw-away `Experiments` collection per +experiment, manually forces `_parent` via `object.__setattr__`, and +passes it to `Fitter`. This bypasses `GuardedBase` parent tracking and +is fragile. -**Fix:** make `Fitter.fit()` accept a list of experiment objects (or a single -experiment) instead of requiring an `Experiments` collection. Or add a -`fit_single(experiment)` method. +**Fix:** make `Fitter.fit()` accept a list of experiment objects (or a +single experiment) instead of requiring an `Experiments` collection. Or +add a `fit_single(experiment)` method. -**Depends on:** nothing, but simpler after issue 5 (Analysis refactor) clarifies -the fitting orchestration. +**Depends on:** nothing, but simpler after issue 5 (Analysis refactor) +clarifies the fitting orchestration. --- @@ -162,14 +172,15 @@ the fitting orchestration. **Type:** API safety -`CategoryCollection.create(**kwargs)` accepts arbitrary keyword arguments and -applies them via `setattr`. Typos are silently dropped (GuardedBase logs a -warning but does not raise), so items are created with incorrect defaults. +`CategoryCollection.create(**kwargs)` accepts arbitrary keyword +arguments and applies them via `setattr`. Typos are silently dropped +(GuardedBase logs a warning but does not raise), so items are created +with incorrect defaults. -**Fix:** concrete collection subclasses (e.g. `AtomSites`, `Background`) should -override `create()` with explicit parameters for IDE autocomplete and typo -detection. The base `create(**kwargs)` remains as an internal implementation -detail. +**Fix:** concrete collection subclasses (e.g. `AtomSites`, `Background`) +should override `create()` with explicit parameters for IDE autocomplete +and typo detection. The base `create(**kwargs)` remains as an internal +implementation detail. **Depends on:** nothing. @@ -179,7 +190,8 @@ detail. **Type:** Design improvement -The four current experiment axes will be extended with at least two more: +The four current experiment axes will be extended with at least two +more: | New axis | Options | Enum (proposed) | | ------------------- | ---------------------- | ------------------------ | @@ -187,12 +199,12 @@ The four current experiment axes will be extended with at least two more: | Beam polarisation | unpolarised, polarised | `PolarisationEnum` | These should follow the same `str, Enum` pattern and integrate into -`Compatibility` (new `FrozenSet` fields), `_default_rules`, and `ExperimentType` -(new `StringDescriptor`s with `MembershipValidator`s). +`Compatibility` (new `FrozenSet` fields), `_default_rules`, and +`ExperimentType` (new `StringDescriptor`s with `MembershipValidator`s). -**Migration path:** existing `Compatibility` objects that don't specify the new -fields use `frozenset()` (empty = "any"), so all existing classes remain -compatible without changes. +**Migration path:** existing `Compatibility` objects that don't specify +the new fields use `frozenset()` (empty = "any"), so all existing +classes remain compatible without changes. **Depends on:** nothing. @@ -202,17 +214,18 @@ compatible without changes. **Type:** Maintainability -`Project._update_categories(expt_name)` hard-codes the update order (structures -→ analysis → one experiment). The `_update_priority` system exists on categories -but is not used across datablocks. The `expt_name` parameter means only one -experiment is updated per call, inconsistent with joint-fit workflows. +`Project._update_categories(expt_name)` hard-codes the update order +(structures → analysis → one experiment). The `_update_priority` system +exists on categories but is not used across datablocks. The `expt_name` +parameter means only one experiment is updated per call, inconsistent +with joint-fit workflows. -**Fix:** consider a project-level `_update_priority` on datablocks, or at -minimum document the required update order. For joint fitting, all experiments -should be updateable in a single call. +**Fix:** consider a project-level `_update_priority` on datablocks, or +at minimum document the required update order. For joint fitting, all +experiments should be updateable in a single call. -**Depends on:** benefits from issue 5 (Analysis as DatablockItem) and issue 7 -(fitter refactor). +**Depends on:** benefits from issue 5 (Analysis as DatablockItem) and +issue 7 (fitter refactor). --- @@ -220,17 +233,18 @@ should be updateable in a single call. **Type:** Maintainability -`_update()` is an optional override with a no-op default. A clearer contract -would help contributors: +`_update()` is an optional override with a no-op default. A clearer +contract would help contributors: -- **Active categories** (those that compute something, e.g. `Background`, - `Data`) should have an explicit `_update()` implementation. +- **Active categories** (those that compute something, e.g. + `Background`, `Data`) should have an explicit `_update()` + implementation. - **Passive categories** (those that only store parameters, e.g. `Cell`, `SpaceGroup`) keep the no-op default. The distinction is already implicit in the code; making it explicit in -documentation (and possibly via a naming convention or flag) would reduce -confusion for new contributors. +documentation (and possibly via a naming convention or flag) would +reduce confusion for new contributors. **Depends on:** nothing. @@ -240,10 +254,10 @@ confusion for new contributors. **Type:** Quality -Ensuring every parameter survives a `save()` → `load()` cycle is critical for -reproducibility. A systematic integration test that creates a project, populates -all categories, saves, reloads, and compares all parameter values would -strengthen confidence in the serialisation layer. +Ensuring every parameter survives a `save()` → `load()` cycle is +critical for reproducibility. A systematic integration test that creates +a project, populates all categories, saves, reloads, and compares all +parameter values would strengthen confidence in the serialisation layer. **Depends on:** issue 1 (`Project.load()` implementation). @@ -253,17 +267,18 @@ strengthen confidence in the serialisation layer. **Type:** Performance -Symmetry constraint application (cell metric, atomic coordinates, ADPs) goes -through the public `value` setter for each parameter, setting the dirty flag -repeatedly during what is logically a single batch operation. +Symmetry constraint application (cell metric, atomic coordinates, ADPs) +goes through the public `value` setter for each parameter, setting the +dirty flag repeatedly during what is logically a single batch operation. No correctness issue — the dirty-flag guard handles this correctly. The -redundant sets are a minor inefficiency that only matters if profiling shows it -is a bottleneck. +redundant sets are a minor inefficiency that only matters if profiling +shows it is a bottleneck. **Fix:** introduce a private `_set_value_no_notify()` method on -`GenericDescriptorBase` for internal batch operations, or a context manager / -flag on the owning datablock to suppress notifications during a batch. +`GenericDescriptorBase` for internal batch operations, or a context +manager / flag on the owning datablock to suppress notifications during +a batch. **Depends on:** nothing, but low priority. @@ -273,11 +288,12 @@ flag on the owning datablock to suppress notifications during a batch. **Type:** Performance -The current dirty-flag approach (`_need_categories_update` on `DatablockItem`) -triggers a full update of all categories when any parameter changes. This is -simple and correct. If performance becomes a concern with many categories, a -more granular approach could track which specific categories are dirty. Only -implement when profiling proves it is needed. +The current dirty-flag approach (`_need_categories_update` on +`DatablockItem`) triggers a full update of all categories when any +parameter changes. This is simple and correct. If performance becomes a +concern with many categories, a more granular approach could track which +specific categories are dirty. Only implement when profiling proves it +is needed. **Depends on:** nothing, but low priority. @@ -287,14 +303,16 @@ implement when profiling proves it is needed. **Type:** Correctness -Joint-fit weights currently allow invalid numeric values such as negatives or an -all-zero set. The residual code then normalises by the total weight and applies -`sqrt(weight)`, which can produce division-by-zero or `nan` residuals. +Joint-fit weights currently allow invalid numeric values such as +negatives or an all-zero set. The residual code then normalises by the +total weight and applies `sqrt(weight)`, which can produce +division-by-zero or `nan` residuals. -**Fix:** require weights to be strictly positive, or at minimum validate that -all weights are non-negative and their total is greater than zero before -normalisation. This should fail with a clear user-facing error instead of -letting invalid floating-point values propagate into the minimiser. +**Fix:** require weights to be strictly positive, or at minimum validate +that all weights are non-negative and their total is greater than zero +before normalisation. This should fail with a clear user-facing error +instead of letting invalid floating-point values propagate into the +minimiser. **Depends on:** related to issue 3, but independent. @@ -304,14 +322,16 @@ letting invalid floating-point values propagate into the minimiser. **Type:** Completeness -The current architecture moved calculator selection to the experiment level via -`calculator_type`, but this selection is not written to CIF during `save()` / -`show_as_cif()`. Reloading or exporting a project therefore loses explicit -calculator choices and falls back to auto-resolution. +The current architecture moved calculator selection to the experiment +level via `calculator_type`, but this selection is not written to CIF +during `save()` / `show_as_cif()`. Reloading or exporting a project +therefore loses explicit calculator choices and falls back to +auto-resolution. -**Fix:** serialise `calculator_type` as part of the experiment or analysis -state, and make sure `load()` restores it. The saved project should represent -the exact active calculator configuration, not just a re-derivable default. +**Fix:** serialise `calculator_type` as part of the experiment or +analysis state, and make sure `load()` restores it. The saved project +should represent the exact active calculator configuration, not just a +re-derivable default. **Depends on:** issue 1 (`Project.load()` implementation). diff --git a/docs/api-reference/analysis.md b/docs/docs/api-reference/analysis.md similarity index 100% rename from docs/api-reference/analysis.md rename to docs/docs/api-reference/analysis.md diff --git a/docs/api-reference/core.md b/docs/docs/api-reference/core.md similarity index 100% rename from docs/api-reference/core.md rename to docs/docs/api-reference/core.md diff --git a/docs/api-reference/crystallography.md b/docs/docs/api-reference/crystallography.md similarity index 100% rename from docs/api-reference/crystallography.md rename to docs/docs/api-reference/crystallography.md diff --git a/docs/api-reference/datablocks/experiment.md b/docs/docs/api-reference/datablocks/experiment.md similarity index 100% rename from docs/api-reference/datablocks/experiment.md rename to docs/docs/api-reference/datablocks/experiment.md diff --git a/docs/api-reference/datablocks/structure.md b/docs/docs/api-reference/datablocks/structure.md similarity index 100% rename from docs/api-reference/datablocks/structure.md rename to docs/docs/api-reference/datablocks/structure.md diff --git a/docs/api-reference/display.md b/docs/docs/api-reference/display.md similarity index 100% rename from docs/api-reference/display.md rename to docs/docs/api-reference/display.md diff --git a/docs/api-reference/index.md b/docs/docs/api-reference/index.md similarity index 79% rename from docs/api-reference/index.md rename to docs/docs/api-reference/index.md index 6a6f8be4..351d7f56 100644 --- a/docs/api-reference/index.md +++ b/docs/docs/api-reference/index.md @@ -7,19 +7,20 @@ icon: material/code-braces-box This section contains the reference detailing the functions and modules available in EasyDiffraction: -- [core](core.md) – Contains core utilities and foundational objects used across - the package. -- [crystallography](crystallography.md) – Handles crystallographic calculations, - space groups, and symmetry operations. +- [core](core.md) – Contains core utilities and foundational objects + used across the package. +- [crystallography](crystallography.md) – Handles crystallographic + calculations, space groups, and symmetry operations. - [utils](utils.md) – Miscellaneous utility functions for formatting, decorators, and general helpers. - datablocks - - [experiments](datablocks/experiment.md) – Manages experimental setups and - instrument parameters, as well as the associated diffraction data. + - [experiments](datablocks/experiment.md) – Manages experimental + setups and instrument parameters, as well as the associated + diffraction data. - [structures](datablocks/structure.md) – Defines structures, such as crystallographic structures, and manages their properties. - [display](display.md) – Tools for plotting data and rendering tables. - [project](project.md) – Defines the project and manages its state. -- [analysis](analysis.md) – Provides tools for analyzing diffraction data, - including fitting and minimization. +- [analysis](analysis.md) – Provides tools for analyzing diffraction + data, including fitting and minimization. - [summary](summary.md) – Provides a summary of the project. diff --git a/docs/api-reference/io.md b/docs/docs/api-reference/io.md similarity index 100% rename from docs/api-reference/io.md rename to docs/docs/api-reference/io.md diff --git a/docs/api-reference/project.md b/docs/docs/api-reference/project.md similarity index 100% rename from docs/api-reference/project.md rename to docs/docs/api-reference/project.md diff --git a/docs/api-reference/summary.md b/docs/docs/api-reference/summary.md similarity index 100% rename from docs/api-reference/summary.md rename to docs/docs/api-reference/summary.md diff --git a/docs/api-reference/utils.md b/docs/docs/api-reference/utils.md similarity index 100% rename from docs/api-reference/utils.md rename to docs/docs/api-reference/utils.md diff --git a/docs/docs/assets/images/favicon.png b/docs/docs/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..27628988594ba1ed4a6ece48d766e31457481367 GIT binary patch literal 34184 zcmW(+1yEbv5)KyJ-QAtyZY>mdcXur=!J);AdnxWv+^x8z6nD4c9{lBhlguP@?_|!+ zoIShy$$nK+kwZrzK>+{&=%3|fH2?q@=vNp35)k@f1F&P0X&Yf1RBGX;tLuo(#+9HkbICPVeY+e#Z)X*O%w(;nql$7Yy%!71ky1*{$sf6hdQGxL5fSc89#mZ~5F^%=mgH?%X#`85={u_X+IDfX zC*9nxqh#2%pU_Zr2>j9yeH`@5i8A9r?aw*G)z+hmXxs5&UD(@0u$I~(DL4UgvKO|m zv7iO^OGZwyEhC*!Mm+J#O3M3GIWn**0ru+*?t-AY4_sS6U>JB2xUe7X!u@#H_!eP3 z8blKqPUY$ld;jQuJEMI1>#Y|{%CP_9rRrPNw=zhQt`<67me7Y`1c>^OE=j>1<>p(D z+5BkdO|m@;SdT#Lb6Tzqbo87UWB!88N0eF?WmS7$g`ul*L2*a+#O6}D`MZivg~1#9 zHgV{k$8Vc90F!6a-u392kGDWGqA+J9;>H`xq=twtYpyK*ETt?wr|>Z6?=2Bc)Oy3f za3ms`+rX#+#8fRmy_w$2G)%#K@syTg{mM>(%zh+jq@ck_Jjv&A8gblUx)!Xv5?^aa zjY@p-ZSE6~>a)jdfb&K1S}dEp)^qV^n=`ImFeW(RpdTsQ@#QrtIcCkTJ8Ow5=8u1| zoP{W1|8*WtYDH(zVDQvWdDYKpuO3obv7)j5-higGtR^u)B=fV->Ej|_sw&G~!-m5V zQK=t|4&khlzuN7`A89|MnTM)t5J@d)oac9UKhk$-aQ{IJ-qj}rV+9r%ji>4o(`<5F z-r@M}+zpu$Ie%WPUAj(9D;8W235+JB?Pt;>>P_^PsXuaCTa4S-U@d$({T!vCaa&Z`rI?0U zWk(<0-`j3&7+6so01bTqEmJYA1N2rry^3etbZS%Qn83t7QrS_j48>pi-oFDDb#)9W z4#(N^4eYO-Du$%jhpvW|FF%_BN|WSilL|ZJ3(Lb338{*8+ zlzwRH=39i$6sBjcu`KZ735 z_nA3*KAkq&GfPC)w~_if@9e{i8j^??E;1VgtZNYHUN~&2h)zICQ3-c?AJiZ*4W3P` zM`V)VVECi@6X}WpCv-V(DE81}O`zb=j{Dgke6l>#>MQp+_v?e&ie&Z z%L!*vy8+!IdAQZz@E!U4PT{zk#3n7AVDb<8*Dcs%NIy$U+zir85#J;t=ew*g4>*g8 z203%GdUjJ4CvIxKHr*L$LR4w4e}t@q7XNrjKs+XP8}@+(9UA-X8m#p@K5f!;bnACM zCz^I)BZCTX+0`}XnUaT;+H9I_el%08m;`=TP6=VoUnY^Z*PVm*%CD(S@6PhUjA;GO zGpjwKb76#{aK5W!1g)y8xtz}eSy3+`%-8Gi59cywH-C??-VA1UXG(DR0;|GnCcVy z#!%``vA7$p+EuTLYVrqdD??QXqQGKF`u+#!(H+}wY&f^*)m|jWzkUBIGLodA%V^(X zepv~U6bFf>e`$aK?M@2@=f8d@`eX$=r~s1_j6O~bB;zRd-4CVVT-W`P$wLz#S?Hwd zx1<|nI58^$X8_$U)C}Y zd;bVWx=`oBfabcwY4UH0f!f|P45`v-R*RIb*#Sq+##kyXMeBf*or6Xcp?k1x6^Ve z`2MBmY(d74=Yf>ndfOFgpA1JZMan3nASpO+DnZ}r1l)Is`UNxl_{(9bm4^5uL~J~| z&PU-}Rcv4}2UTV+&7zE(?`A7sw5F~koN{)$_YnwcPZ0(I!8h&q%0iiZAA|e2ATVG5 z+;#T)Ai@wfS<`jgX5@F=$C*DTu$qP!*d(=jV=A% zH+~y#R^P)TwE}%|0`#@!H!ro{t5_yl`Y?MXMUhRh(6)3{1NLU62WL9jWrDG{NsGyO zM9!Z{VDU!|Kj>Bl-bzOfMXg4ri7CCfw?r4xx*lP0g*xjX@Ja<#PTDw|_*i|{C1d*D zDzJ)ZQGZzR&-C#!J$n!rm)V!v7suoQZ7a`KG*Ek%e~%PBx%I1??)T3DC}p~&fm@F} zjJl-0IZANqva2Iiu2egefIFTYTaw#s*Pp9F+!q$lWFPT~zQf$MEQ)gra;NS0=aja2 zv4m59=i;DAe6EzJtpp+LypEi_v|V=C0HA%&;8k;G!`>z|`yojU#Hs1|tRv`3~bqml&=>bF_C+a1)uQk>wp=|THTcmX?-@xTE z<8VPx*P%t?%Z2+o)=!Yd&PDindGN~G1b;-A^Y>PCkCx91JoRY(IQ`l~cWE6bnx3MS z33Btt->C2igm?*n*04pDu&a{*=~c89oUPI^G*izT-d5E^E;|2zC*^3@%sq&Z&l9Ph z`%xo?re9u-`FhTY`Z&dR-2#Vq9F{@t_M>&kUBVklq(@Kt>0oH=KQ0aNXpd|vITgjc ztt!3fPnelbAE;3eISrR)@_aS~mX7*2p4_<3Mjf&$gUi;g^&A9^s+;u6g+kpMGl&|zGH`I>%!KlK}vIJtoMho2FR_PXrdD+u^dKhmn#JKgMQD!0kKQ| zBw$5XPs7(h`#pU2_HCg_IcUObPw>k+SBPogaR1$W{$%F+B-Z1{P`CzMTsP5uOQ&Qc|4-E%+DiV7p&dm~$@?JZ%A{I+{_>JVO*;h1W; zaUczcZ3G_mHpVBCLO)`!9JdFcLSH*a$F`xHK4ZF)CylJHh&z14F!eIi5N z#zE9qmv^T|?}{YBMDxylIvxCQ$i4FR4Dxh|S2eDM_rGMYAbmKOpt_1~a{T57T|D66 zTFJJW-IIjyi@%9|n`npL{-gsflHOlk5~5_}-`pW>9fR^Pw2HJ+t1Zl;Zf?--f`3YO z#lRMpd$Xjt(p7hV5zk>UeD+ViE#N}%Z0A|&z-iYOZTlUu`04ohP=WpJb`k~J0rZco zNV^S^yaBOaQ*c2W{B5qLp}4d9Y%;^&SL-OhSm5Uy)ZUatqRgjD(BUqzYnwgKg`7*s z!liAIy5{Z6bLZ`){-_vtt@@`*9F-dElvsduY7hKBSTQY|O~2xwO}c=gX<&w*F{dyo zzb3TR)h}4m|ixeFD=1?(z9o)dmp{e!k@Io~ULHjGLsx-?PC%TS{ZR1GZ!zqNo?tmHZ&} z6uZaq5sp+y-%Uy1O848Z&2!4IC~}0t2hDR2Sw9%^4xyGLhl#Qo$Bo_5`iqfUb`pr-0 zoeke(12P*(q-Hyt|6Ll80P`|j!BA@tb1D$=?i3Tq80zkgYWMtc{_gSQQ0MXY5Q5)V{iC|_#}fRh~MsJ@Ye!Usau zzrDrO83mb5?`k)i4Z20D(&)e(a8)}7n8ugh$8d?5zty{-KX zU*C7}Ul}zRTm^-P-U3ChSYbjb$NG#Tw>GFCp1zRmmo9p9Em243vVZh3M31Sp1U#IxUf#7%bLTZYAuwofy(S#KC)tmdU`hl; zVg|pe-=mx|123mPP*iq+dEz$lwkZc0Toev3Fs@9in|$Ao@l|T(a7|W~`vAk;&ks$u zqyvtas%|G=S~#!3E0@=w+t?9_EoBhoQOHI40tL0h!FB!LXQPn;Wx6M#UmE7xjfG5T zjf?^ezd05No9`}BGZed72u}#C^Mm`PD_y@>Xa^NkzJ@_&^6;_v-j zdQVqk1Mv5<15MB@St0b6N`38({m6l=VtVv?+&4B4e7~s}^4*~%jjKD$`mb%hCgT3> z3V2ZNTbztfgizbrPGhVD9m~E%3w-pwGD^sbYgV2{3b5HcrkE=Q(QH0U?pm~uJ?Hm~ zH?rRS5!5`9sDus7Fu&vnFIE(QI+|B;f%ky_H@Oo@7UI?~P9kPy4Oz%1Pl*6k_C-JN zY^+g*&=opn<7iZ6<6G}i3mW!${)DE}bEX`h*Ji|Z_Az=gDE9~aS&z5yk(|Xiz9xas z)B%?;D_yb`UTExI4B5{PI^X7aG`TH<=Aq|;p79S8%^a&0E(V>>T<~vc_M>1Hyx_O2 zbz9iZalnc&Xo1(asxJqduQPe!^ZT6CW)Z6P#?37a>T8SyHd;N?6Zz{M9D2eF6#7}Z zX02<7)Mes0E$7F#&L*-;NpP}YiRAC55Z~{A!jkb01Ni)Sq^=I1!f`|CnbETLIc^ppe+QliN*UusK2BLR33LFnAIo@c#|?X!&K+X(PzKdb)R zZAd44jPdIA!yiujWcd{x+IUbqM-~|(Cp?&lAsjm|Jq|u8l3rFa3uY#qb2<~}W{!Is z7sd$h6Oqf&@`<7i3@^NY%9`&Od>8VIXC$mj=Fh)hX~BOBnw+h`R-j`?o$#MXwEzL;ImLm?9(s&5p;}y_iVZ-X4x3GvceWM*E;7 z@(YTI5b-Ug_K_VFjS9P%i9p0~KKyWxx9_W?maf>owq+#rwGlFdRrhA2q8(7Ra^4l; zY2@#{ygiqP6`2NX1plSa;DMr?3uT%v^vrK$xqV~%zzdP~re$R>5wlzo4FNl>B5}gP zXV%c6_X9S=SE3f;z%4xjmnj^fmRx6#a;~GP7U%D6QSEfxf3u0F6Pi}sAdM-DzTPDS zq`M!(o8S3uhaOq*A^vqDBnmfkzqXf;4IN6%y8Nf+tAZwToQ zp~7x1%?%Xw^n&PiHx1~?Tjf+2vUl{QFcNoGo^oYPMQBvcVTUK!GXJ$73BL>a9z9|o z@{RT!YWO0!E9!O~ucZ>(4xj+d=mLGYD-d=a1oA(WeXr*xlSga`QA3su-<{}kK1Js< z`$HXv-vGzUP_O2ERmNE7QSsNv$9CPnt0rV4{IZHlPUB!2WnW}r6%@QMr0Wqis`~YE zxSoexu3#v8q9E5`N26p&k8n|i2oS))o7OOD@agC;cSz2!{*wIlh*bDpBttX>taK-q z?(htKeriYAd8Z#!mPIEqVwPTE11*NdeDed5wstNdFJw z2|iUcnYU)7_pX4a^`FUDzmMM=Z8QA5oBQ*Z^nX$n!nmit;B4Ss>1p&uO(L9eIv7C1D~jV+KWn-=79nVxJj(eST5_M_?u!w1upMzoD5E6dgB z)*2{w%tx0c*6yCg@)N0dT?a=`Nhj4^HlR8}i?iP%vV{>9YG355|Z#w&a-Xj{F~jtS{6!) z)=5j$eGa22f0V8{tl*yglewCdRZ^S4RiZxV=9g?&64AH<-^}%h>6hriY1uD#%lIU< zz35RL@Yci4zgi*h;pXytwGQ9TD~#vk4z!7A){1a?nywya6jd zY&{=kd*0jUD_?%^u9l%Vp~Cr&Fe7S4?4oU>9wrV!7!1~ zk;fDHEBcteUxZ)^=1GQ8Z#@d=^({$8d|>4hzh1*(m$>Xv`_dS$7V;0SPZj1l9QArP zy5U1eYu=Y~p;~JsiI~eLWzJ5)P}qVFqn$?Gl}`zUc5WAn0Cvu`e%+7`Lio!y=Y9jE zm|lVp>7|Dc@&2B0z;I3zlU>w|#g+)w(``7@ipy~A5rNiSJGW}IE!zTz*z=PDcQdNaiQ8DX66i`+^=7CPLzcmgc%eY{3l?N*N&?> z(|ZJgrY+bEPqfuw^NV|VE8XQmlYL;fJ(6pXMVFFU^|`U%YTw;meas6yc1ED=kGX5e zYU4!e{mNO7dw2`66a{)akp9NjFqMZVLapdkHN?OyQEfCU9-*Z-r7nw>-Z>>!G0zXcuEW;7s_}&2J z7{t#sT-rZzI|udl!F6AVxb)=L1-`tHK#_AmE<}Wu;@k^ShlGP2%s4r+iZCs(b^mfc zJ-0~ZUoY1tzjPg_lVgl#rsl$@qJVlV;z&Aa&311cemX|{K|W_A0QRJ6r%AIs!+-*H zo*))Iy0kOaBC~HfjijKXejr=`j6l6HsMw4C) z>({~dsLEJ8Nj7B@T=aTd%{)SufI+?W?EEdN&J5NLWA2U!QaX3K@0{Te`Knd!8vc(+O0FSED1nJfD(JOzLAe4t?Ap`VcZ+|`bMZme5v1>NHu_yik0qU{Fp>=L0|hHoF#@Zzuv1-t>r7bi7qiu(q;TL`@RTXCJn274p(M zHFrR~o>^dnt}h%TvPyb)A8eF|kJCYwS>M|IU-& z>GkMPkZ7e0x0>|Z80?FLlW#PL6<#-%eWJ`;{@wHI^B$Z_0>3?OU|E*3!2#=2R{z z+hM%(0x)+$!_zgfV%ViMq9=i;eM%TPmN*l;DRXNelsV6E;9zCPG<`n&+y#A%(PkW% z>X{lsM8m^|W`&oRG9H3W_Xl$ff)CC)l1K6&3Cn96Rrj|7k*HX<52?+&augNZX)RdE zi0H`$bTrkiRpXnv#s^BS*VFDeW=2-|Ql4-wwibWxled(HEm&~RgQ+FgqhsT#P%o7n z0&BC$^B%KU0xjl-)* z2M^iPQV}DocNq%AsE}P&(=G%1O<2_E1nakp^f1|cd`^h`;5P?pwcee68P?1G65n1p z>bAni%jWS<-dloezt7&_`WK8n87w`TGQZWBBdzh}9aLc+3US+~aKl+#1;BxsPWUss z=%Lq9{wWn(xH|$`mG+s=wUmB)l@a^e6izPB8>li#m9Tm)G}XO`jNG6^e;X76qE7-g z+*kT}(3}P#2B89x@#H_w{YAf+$48w+cC7;=&^6rl3hhP<#(sB|L`@LZ>DWDzC`Kq# zigyA*-t2-5)z75AbLkG;%Z%1B|Gh8sLDmfZxES@agAZeMS=j_yX6^)FiUefa+B0d$ zEiqbU+dv!>x~CIKtyX3dAr-0pj+j>Mj>6=mT7s@QYrM(i7WqX~pY7Vj3%dW{Gt$Dl z!kV(vnC`N^?2Pp%A|Tp0hO!iY{Erj*)h+YSZJ&{#D%YUmEUD8~x_6E4PMCtml5}O^ ze!7-_RG)!UM6K@|!3CggEy9s~$u!FFmm3|NrZ?8eX3`Yd8spMFN$}Q^XCaL3KeuLl zvPZAzT%;55T^grJ)#aLjD&U*@zc*xa7VBS}jzbEI50k}yV@f2R@~gjK?PYw20)ZpC z`TC9@cMi>{r#v0KDnav{`Gl>yNiA z`)Z{P`KW#|%}-`#H_iS7`o=+AkdGm=jPG(_6jYvVj%r2FRY;w?m;u-eK^?Q zsjd637`7@Mj>%Dm_VjCR*nN9>1O--4bFQZt84$<7=%xR;=C#eaUYF!>Qh*ipsF}Rk zr#nosTx&M6*e_(T5l=B8pam$x94myg}{w}?-b5whqPpr z00XOU7gd`>yU6>U{I-9yZ{yM{n!>?@VGHg*Jp=NH&yfn-brwOJ1PiSs;x8}%$sC4v z6M|jwUJsiNuLbhN$up_Sa0&QMz|ukZB>u=86z}4NY<~3XihNlNOMR19mFx)CG@vS{ z4*x#!seK_*U@J?OlnNV8(F`yAL=s|19R_*ubM7;$u7ch7!Cmt>S@+HhuK2hvy`q1^ zahzfG1dbe(V!gw*92pCV$Rkro!p^n)#^M3(zvaMn4SJ|E_OxF?xg)_dNr|IUKO~3I zrf*tf>nPxyTr@FY2BY`GqLb!#p&Mxs7p9e@a6*aFUUHrU%K2E#;_= zd%EbLX3B(FK(JkF;%^9--%kj!xcaQez<9T6(RV!gj?wscToGmMbb+A!XOy3S!(ZOYZuk{@L z*yhQhZ9e|UU?TD(Ij1{4)f=5)TqPyV=OIe{@s?nB>R@Edg@=&u=DpIe858K!9J=iR z?{wJe=!v8>o2QVMaaD==y)sk03+kl|7^ZJeld+?%AJgnR#1oBJcOQj`;D~%5l>eH* z_oK5S(B;d_i5`LBsr+mIB99-dJNV}sI?~{r%C=AweZqqLoi&Sg3q*%C@jkJU{UTA@ z$85Rw8ai4un$R0oM&eiKpmnKjNvj<|uDd1+k%tZ6p1cWy9{8R6c8g-V;!EfBLeLh3 z?beZV0eI;NZot2{K#(KSj@0W#%IjVV!*g-}PH@6MEONeO zhq=EyEPHk1EQca3U2f{mU3kXjSsyzE3v`qeKygp|2IB)lFbL*Fv}^m;7~hUZlK&}M zVwNgPd2d2p61N9$`>~lMVWP^k|`$WDdn@S-GaYu!q3Ih%6v}nA{r%ppWd1MQRuw)u*y-X7hj6HoUTh{nVS0 zKAP9(I6#mPQ?KXa>tC_L*Xv%LtI|M?U3%70h*&CjeA5^-;o>W8-)k^BCR zDW;MSiHSWe`<~H@hn;dfA*RCnTZr*N>~36@YqPQ1I^WmGNLxwj?Wf>DdQoci$_JkO zBMg7n_|p#VkI`C{ON7LeZ9p6`q`kq@tBP?l7j}a@p)f?|bE>@$U#}?mqLZRuV|q;~ zgMO@C+xrLaF+3X$75+;QX!O{k+;%S{p`cO^$==oQe5$@nreCoNH?CkZKlo(KX~Ws5pBObt(oCsRukIea5)bYOMM+0cLR316K(Ulsb!Dror0Gqt=K$T zq{gw(mYvvjU@Z;gd`z7EjcP=ydAsksA0jNj<~q)eNotAYB`!PGkmAJ`@hk`qVEQFT zKoEJpJF^?Qf(mk-o&4DQGzNF)3}Q2;6Bvbn@H?-XRqW#`@VnT0l|tC<6w$B1B4S)s zjV{?C7HidSzX>LWK16x5w%c|!UiBd;ih?jvDtrsztboC)Uxj`-&;;UM%_Q*r@2V~(+~3!gZGJAc#C#;7H z|Js6=J}O-g^Cbwq{8)Ax0M|HM30~p;iv~Db?aji9C&;v) zguW>P)2Y|E^LEeMMcd)G3!|kRSMW@D^R|a(i8XR)l;ir~g?ll^kvd{!c_K0klwq#E zTS!_`4DqJ!c2wEek+T$Zq?*0zN0c8WhR22t{(K2ayo6>rN@(2=YX1TP9fohxW);&$ubAgLjLx#qnoD+H0Ppwte@Zj8*94E%# z{=F>;1Wrb#ejX**xt||IN@B#2kAgace_f$ z>+Q8OSotz?ow?F@M)iRe-g7_F%e0IOn$uOM?iPFH$x4k){cI`*3z~wgM#5x<^1X6w4H^2CQ^69BqwC~+MxzNgnCYy7 z529o%7UIq;+C<<&KuPcK?-~lN9?}}8awN`lHht;Ea3Pyh~9MR=h3X1PzVG7#6 zbAGNyFilID(B?=dp$!hB&Xk3n^H!~#b89nl;Yw4EEC4e3JRceNmHC@GTL2_(v~Xv! zwC=p9Qnq74ZFL=d^IgmW_xMQu#sEW!Tnom`NoOp2ab&%_oq2}xG?`0h4?g`EtyR#p zaAQLBQ7YAQe9UEO_H`&BW`)g$Wn|E(<~7nWTnGLz&ysxj;ZFoZX9n#4vz#8KrGh9{ zaWu*Kx#)9@L}718+4$_loupXKC>=_y`01GB;lRxCK8vl6FpS3F@Up2_e09fu`#mcb zYJQz)tHypp`_3@Qicye4(qh1PS@883gG9h$)cbD+(h%yfr);2~GM>jcdxoduomH$C zmF1RA#J9ew6k-NV9X5xA076ij5?3-l~14c{+>c!G+||RGiZt#p>5kvL^6_49b@eco59MJO%9H)2_F-0;^;-@&(Om4=5e$5(F*OzRUen&r~o>)>qTocI!V_Kn`#^(uyqd%h|={SFLloL(~nkYicBJ zoB~MqggZUbc6=cEtB^LCUGOU+Tt8>EjAxA}YsYNbhQ~UIA6SsoeL~TzS;cQ)@HJkWuM!gcr*UtSXf&SDGOyB9;}H6x;^z1R7)S{_E{)JdxsqcOB4^z zH#Ib#rA}`$f4yeP^**VCfg7)b-xB+uHbpQ(o1>Yf?1NqxT&a%df?-FnAQ6>B_<5hfS zB2Mx~0~VN0x$9T7W{#Bztc3~O)Dk`(q4k)A^TXLazf;0tILiBc^C#-)uF8^2S;}lG3FqF)bL8R3v)t7MRbo$EpKF8OmEQ7|ckMcwqJaq=ry8wIh&i`m`zOrM zbjt3QCUPKjSmW}7E6!}TY6BzSgo&|~Cve1hc1^1Z_v|U}K_Biwqn;E0#gHr9HoYO) zV2v>*X7#r4p#K{xDtp^P#CKGRSK`n*=oHT5NUQRPWFAQ8;iCV}$p!osBX5&2VJB$v z+t$9}8Xk~X3*qHc|WrU0=XTJHOZ{@4HvUyLKEZon2b&CM0bPB7b=UxJ3T>2Z1Z z=C{rtysrTCZJ!C!K2FRc0eMH+`|5QKOkDQklW{(P5tP zEL)@p&!7^VekWP85@-#)9mx-G!qq=8SVwm?{BZzz)lM>Zg^lVGH>iJ1ShdB$2K7fE zGHjn5Y)!w|{oX_^?F;4Yav|yQCrGg3e~Kqbq#g*DWAwc|l9gqPhWRRi95??@LUP&M z>)pKIj@V`Pj#+@*?8e>CHbt#%NSpTX>5MEEXAfV3Y<^2&z5fpQ$~8ziRLz?!PvYP^ z-nhTeLKYyV>s~I45IslbF1z_h?PMR(9KIV(DodU?@Z_s~0>NpI#rrmImp#FG4$^PP z8LTGk4rrEbO#;TAHXnH@>hexN)qOLt2bGX|k`ws~Gta)<^tGG?`Qy8~S&o+e;)A}k zQ~BvSk?VV~o~(_m)hquNwHbdkw`n+S-<0yLiWNKt2@L|2cs+D)d3&0d@uA z^mqUnYJ3@z8qs@>7wj?cHMV_;M6NJ3dX4?)u7cOn--|mzXygt_LQHJJYR_Y6FjJQB z<~@5{@Gz1AAW>5|#2*Il8nLvQAi}a@8S5V(nd4V~PcigI7JVit3yg#ZgpTh7YT(m5F(d#8vhdb;ach8NB=H7`o`LCBz$?bx-`Hg=r#Jz+fTiNF}Z9U54Y*$tDR^ z^rro2*05mFu<#B428x&=d-KBhx`;Z6vakUIhXf>|Eo(7GjCt$t3iDqjMF&5GVN9<` z&_KPS_<{!VL;qfV_O~}^g5XNd@g~c*nrGE`i@42*2S^iDxN@31F7YhSDCc)J?HO^t zoRHe6}I^f!7Cm}&8_EBVV+oy}>#6%xhEL^Ee#@_YS4CB?9N!J>6nS$p6gE(8(E zB|2WM8RV_nTsJWFX!tx{(kB)nlM=w3d$gf{J81yDiwd=3 zWepj~lbjpVze&lyQ=@}KkP;3xT@jJdNh6UEOFfQL5e(Ea6GvD?;lZ-LHafEBv$irV0Y8ewgjjxFotmGeGU?hyY(XgGyyD~|Q>mT1 zk~W4zw1&U3KJ*}XIGgyGFakK|fTsu!z*j=nN>NnS`}*S|NGJzt9eLbppReJhH8=WQ z@Z+@yEJyY4Z0VJ@?t>^iUZW|n1fUe{PS_t_p)eoOmiM$ z`Fcz#Jm@x_-hnh(_v}5*>0pOh%%EO+EE_!$$%_3esHBm7;vCn0<1t|-W9szLu2|~_ zba2hmbRl8w6|;2v-dD;_wXYbTt1?<=Aft-<@*2)hJg8l^WWU){@f7ch@z3)!_UaNk zh*tV{uJ$ue2s7mrsrVi~k9l_0PiL{8lsv#kBDfVe+42@6if#jxxG-e5zcY6MpzjOD zj($){+`M^N$_z{sX9SO|BHa|8bDzk{P4{#`9GVzWDam?WV||q%}~FO@><9 zUXgh|@R4kwTH9HBgv|_Ndp*C1+f%zUT&r z44kmo3S!*SV`wYD;wyu)T-V-OztUh9PZN>SQ?JltWr@C`WN0gXgdm^_RSr!faX(Bg>@XD(QmwEpiZ>q!gaNlF~l+gmp z?VK{PZp$0Vm6?2=L~)I}?7OVPM~%TvK$0Z6eY+IVLri0B>Oh1wl4cK1M{ipt>7wZ_ zQgfZUt1LtP*iQ-wR3(E>oTO(?>XEToWr9maSH-?k^UtqCR<%nGAZ+u7MwLx^_JR>| z?dK!#(!%n;k@$a@8HN@F${y>Bv=xE6L#ac-16c-{&d9%7`u_(`L9xD(Vw!VnjjpFr zXxeMUAJgZSKZmS8tnT0S%JF$iMR@!c4Q%C*8vr&wKTdHYH(dhP772j7}&&Tl; z*AJ3JY$5*8{`wj1`dOU&w5OYYW-=C51XHn%K$pH=A!WoH0w>evy8R{`NxhE~?)<~@ zh`wb8VJjx1c(VMngEGOm_SnfDrq#D zLB+YDv;KC~ks#Ai82u|j^}^IKmv%uNZvL{Z_!VO)y3nCY6GmT$i4<_Bk>qV zrJZPX#8=(O5Y_X8su1ikq~iMXx7>SApkIxuTSZJLym*2gyI)`3k2Lg;`1XgoavIc0 znb8z(8`rP?-+_z~&4VpNu#9yjjlE0!ByVG2!(Ui)<1qsGom+sNdA0x6jW_`*L*f_! z8gWxj`;T$2F@Z?#8K-8%l!@wNKxUANxzlUO-nsHSESNFlH>Aw~B9}rEe8z1^<0Af1 z_5P@FZr|=7R0$;bsH^_IYRWJ~24Q(gFp&KDP)uM1t{dB5615;9E(Tx(;|^}R*B~yr zNNnPfr12C7)=zN!;ezEj7DJHtw>3dE11@2Hb-&UGd* ziV?4$T9B}h-oB#x)W8ub3cQ5tdoM%W96VKA-)Hl;PPpqs%=+7gH8}M1LDu)Eo9$** zJva@E-eC+wnuZ1$p+Zjppq~i32bsWjNCTmd2Vex$fk~bT5a2i9c%CgD|Lg!(TT9j&QP>k8M;}*H_m*j`{*-AjpHFjmBFA(#McOim({`t_ zHjoXj$U7cOwI&zVsw`ET;Y@5>ME?Z4e}~!?EW^MMi3}PfLd9}?)(pX;ohrN=OhZVS zeRktvo^RTjVK7OctAR*@8&M_b^bezF{oPbVw>&R3U9MJJ%w?_6YKyHv4JclRF2R?Z zffN&l!KSM`UuqEky$0O%H$93o#wn&D94KZvKAmSO6Wm4U_LYgQ8~h_c);2jZmE%A$ z%W|#Bvr8ows}1}($NNFXI+l>*irr=a5#SlHVi}@qAT2_If*1&C%M5F~zbXLRC<*j5 zFo;S3+r$WB+KXfe!6l~WOHE!pw>2QB02LRmwyr-0(C~xcpkW{aYfXo%)fS<|AG^qh z5cvUA$)_ytm@9HsZxfIW@R5`s^qT#qvHF|y>g7_2RV&j=@ z8A$XRZ~3Yv!xSk2p`S_w(pH44YCi)=;sJMl8PrnS(_W~^rG2Um@bw2y_L;Dl!l{)y z&4}|pk02%Y^>!S1B_c8~1kavZYxG23>e#0HA+6_8_#a)5gYPz%PxJD_GaQ}F#filO zgSJKOTV{*A?8q!r*?74=zs>bf{ydJ zrc+0^{Mx_G?(q-72*$+>1kRNjT&cD~^?U&?HUe9~^#Y#4scLYF5eBXgPcBy5*MPeS ze-K080Gr2wVwU?3PBWFYM?P$egSkS6`;N>pn@h)!Yh=AYs@Ud2X>%(B=o(eiGNfeq zN(HF*Y)b7yLe@5Vd+FQprkCSn0^21G48sT#jj6YjaHddhc|5&Ti>%%7;h8FZ(}eF8 zxYC3R&4}G!0_*?jmHKAX#Nw^zQTQJ{<$`zfg$(x`m?USLi9S^)Zba`-qOYtlk+He= z@GMi=^yW%CDEfC0owheZPo@lRok(%4U^AIAunocJ z47e=;w0vcy<#D#`aJJ-7iHSfW5Uc;zLWT&rcGTstCIl7? zk+P99{1$LEOgL3lvbKr6#YrVj71#lFpo8V4NQtx!XngfyRfFy^CbSv2p3kQ*l_EXk zI<|nv%0rK4+hL&v50|42mLl-fQZ)z!8p@Ql&!ax1+=b)A6Iu2bGO6b|MrUbQdF*Br@nbJ6bW4s&EG&%R=Y!PjvV@h1@d+hCgp z8OtIjBG8qUW~*h?eAD+;FGo!xW0~!s725w}^j(>8OdyJ2`yel=fiSYE9bg2#vqylz=*{no+8pE=v)_;eu_ zk10cV`Fxhw9LO@AG7^2=&L^ACntbKK9Cy#8V;!_?)8Ll5$&d}Sr@`onM?dc~*pz7o zCwS3X$K{)fS>JF~5#dsFLk3y9E3jK4CeV*l6GMz)e$ARrtY*ANbXTJebvhi z*QeuudP!BiO(BEY3)40Y3Mnh5-yxYm(szTX1S6{ju`fc3jFB0%;mlf{hc1>PTNI$| z!6#M&PrKcQrVpQ63v~Y|Fa$1@n|$=#a^NkOnDCQ}{2fS{28W8-Sn>Qt(FG%R1!#{62u}mw6XCpf-y1!Rrtuc6;3YKBE?G~aI*R6GMuaLo=u?U!6#SY z$qKOytM2;z<+F=itG2?ueg`6awut=Q&!;jr#dP?2zXEd^lY3{jn}#C%m@)!$`7~ApbsNBorqUE_V{A-Qf!ValZIjUn98#9aOg_`6 zl|i2O0TCuMDUi)qfj6{RT?Q^GV3>wr3M&LsKqyRR>~{3lkb#Xu5{R5@gk?A>g0_Vb z49E;N*$fia7kGB1&YzuLr0zr?HwjSl;nQpIiIw2$@ODGX4|e>&TMBgk*l9@M-|%;5 zmNt@q3`6*k=sTJsOl6|etWBg$juxzK5t(i-+T^To#v&q2XHya3xrU*ocrXovVkSj* zFwo7lMebeA{RQa53l22Jwn@*2&ixTc|qik6o>yJX-br*_^ zEYwpkNuQglw=0Z2$6wz9@m0DX_Eh&3K6-AMoNe$m$7fXlb8lz6AZ9U2X73PhMfM)_dbGDbq~Kv^O9XWBC>dM0Gzr57l*WEVjD)t5oiLG3_$#-l1tfSW75qVBB%FFWPCxee)7%~Kk`T&;90;& zSvMVacy!t{naQTOR<1{kF3kNlf(byM;2dox01?h2Jufyr3V9!bprFWrj380-LokD= z>TPg*<*%Pz!ts=^yJd!SNZ^p(9+wmo*1noKo4I7Nc{+R?Ovnf)h8TH!pmD3{dyD!fNK|tCR}w6sETz&k*MG8J_OB zGO&4+37Nq9<33CvH0n=GyIcB&zKlTnl-bl)SJ^~#-uvZ---cVB&)=R|qUv~j^KEk! zQ)cYNpiWN}FOUYFtp!M7U?NLZt)*LO-fWYN@os|ki;2Ht!<8~WEm2xsu@NPGfYhU18@NmW^)+~LpHJkbvLP$ zeAUyPCM{S`0D4Z5i-a%$0p{~*OvAwQeFlr!*N+Jh{(c=v(ns_jMJ?#6l*n&?SZ2^$ zFj0j~;HmQW&#$o3aQKI}&2w}j9eEno-Fl~orVrOzL2!|DJf%ZtZP%faSP6nr-WQ^!Ueg5?~s_Ogaw;6X#u27 z10Nhictn+N?XD#4$>mcPlNp=n_)`ho;@fzMwm zaca57R~(+=frAqqDx@}I-3{1eqHf!$FEu@$TCDK#^D8{NTnmV*uPJ4mgdhH*<@z*S zk8CRZC~%?f(egDmA?r|h0cxJla?^`gCPLluLZSzck@(v%o6lewQC^AK@|DL$;MeaD zVo+{!^7mAqF+~;?4sDr&2!|$eJYuYD=sJB9Y7EN+V0e`vqWvgFu>QQ06vDtVHmtd- z;P^hSubXwafo%rs6_XX~6pbRT_OrEy%ilh`$Y(CDa`$|Jmmi$u*kp!W$KpK#GwAH} z5S&IVRvjK+sPOsAYn(4NaJ@c90|TA64Nv%;!8*QAsp&AC+v*G?0?SR0YjuyK1#7%V zfdZGRE~QAPEoH6VY~O>3na7C4Zx{yqi@9~f?xuEwfh~eQqq;H5bq1h8p$ot>z@ZTP zA@hX{xs*wz*%SG6+;RMq_zOS++dxG7oi_rRl*M#5#bm~&n6k;Gg7eyL2GH_+YK}*_ z>9E>pvD$EGINnAh1Hk~sK#i%=1FH>(PdvZE=P#EyR7`W%T!GtXa_rBiD5lI!o}62Z z%OPkCbY`FBhQqnF22U)MIkQq{rQrmOFe>rfz?3y2;rC2vxz^yOseEX$mZvBM27Bl(urYJx??LXry=es*wX8?ti!IipS znY8(g!W*}YPoa>qn9ZfBG@Pw7O&Z5vBHo1|BZ)Ks1F8jzA;M%f#gT~|^Mwp~+hRTc zNyvxD*=CR!LNj=(R9h}rD@`tzYLuGp20;uBgM9rMGoC=h^*FO!8GX)M5v+L=&!Wd@MSPT&+(m@)jqmx-Kl^dL0u2AWY z{WhlEC3YZj(C)tJdR!@cTq!sC0>L?S$`sOOkTAkBg5k1jQcN^qnKm)EMaTsORt zaS(rdH1mZlmT7Ea09~y%75$Wf@O!T)u0OOkHvu9%26_?Yc<(p`I5?5RGL5!DBaCQA zF%AN;wt)mrJ7H>p2Ir$kr}Nx4I}t>P9RcN?dKt^)mgzk6`7|dN%Umke1N+F(2E=WE zuC9Gm8m`iCI>x9FoOga58^L$?6aT2si=Nlv(Pa_KG}vFv#hZ z!ebo*=_V9yCs>%wq~gasNcirg4iOqzA*7&g8hpw!xNH9uckiECC;o9jr;B3B;w1;B zxP7kJb8E5-A6vqK?a-NB7_58x$c8T8ZOj`WmSKPFXzZh{FZ(ptubjY2t;NN)YOK%0 zO3UM;muoz=+M*p5D-jq1O<#Frxyj#Osl^8e1FK53kdvDjfFXimNK@gHQ4`9iEDlX(duI_+xE~YfSr}X1$5l(t2r`zzJqM;aK2ul^ zkT7mg0tw@`xgvM%o5W6X%+%34c7-`;bl)S3_aWntozj>Hf4y+9QJq~XQ*Ju3OrYZU zeDZ3Ik6o^FvFfhhx-?`Xv+nxu1P*g&%MKs9SmSdGbsApGElq*tYJ>C3<-X$`9hCjW z9N8YbN6**zWZpKLt)8REO)9{zy)?(ad$@`!e{UMH3QYBOWr%RoRDmb1tZpPjjMU-d zjtP6}4XP4AU$wwC4emNH#gWNeB2;(V0=iEDI)5kehrf9`kFUy8*VXdE?hrfsZ@B^2e{4 z+EjN-%E@;9C)z)++4^4DU(7I(wpp!ri#9RDIEY;UNMs;fe-+Ls3=wXho8ZV~zW=_V zU>m|j%3v}bB!=iXi!?l+lH;@1^eH(Wp5I@kKR%tO;d-22>Zki%Z!iea4pm?X0@29g zmr6BqDT_PyO~qx&MF{*HE>&GF)m#kP(s28P#Mkyc0`0#M2!S#6smn_&R_c9@sK)>b zg^bNyA=9=$ZEaoQaaDaHY$#h8K+ZB)bNn?EK1vt^0P-oDgA+McYfUy=-bY~qFuF9* zE~CnU$sEUKio}5u5dKXCo7*Q-%wkQjdsCz!w>K-T8T3oC;k!i0XSUe|Y zi>%ch7OLH$a(B@fI24RZ@QDkJmGIX~sB&hpglQO@n45@Y0?_r%Y1_lP+LrbI#nh)w z{8yGazg!w|en~;uSIm)3MF#*;)sLnvvs4esT)ag#ddIz4QU-Xwenf-3fm>8WQ-qsm z3YZ8q=_zQ1iOSlT`qk6&8kxy988k{JJYHVlI!(}g(ZR#Es!%k}ufS50oMA6BE~ zDlJbR(I7^^R@AC;Xd=f{F2yEJyk((Bz*v%bM8nbPJd>GJG)DxE4~3icEogaIZc z{LNdp=PS=HuJNS{i9Tg%l3Uwr;5=a)-6M*L_H{9|7+8%I-58}LXJe+Xp& zP0#0RZpkmI>W3rxC7-gmX{r#aa76skQ!bfBcm3Cd(m>X>I5L@!J(@X`vv}Qs9MfrI zD5JL$#Fu;hfh=>Gn9xfG9G&Vpj~+<~&;*SPC{>I!mQjg*kR4wKBm5qesVbM&s(kL; zHO?%RXnB4j!)_FGjDU5=<9Z{Km-hv+AEF`v<;Mnn7HthDT7z-&oYr3 zj`+KWKASQ5ihbG0K$EILMCI9hIswBYIbDzQC;5yAuehc7fV=%LJAUZnjrWzN!xNX6 z`0R66cy6ggM=utU{--<=lv54bIlZvTXU<;c=_|`LT4C1y?N#k;D-i#~q7ApFUqbk+ zOQk07dG$nS8&*`~zA6e|M0ptD^2n{z8T;r8=7vHST4D;3%AN)OK`$ zs+!=@)-t{ZBKu0MNj|m4Y$3~RKEq@tMcOj4Ok;DX?dY{oo2Er(w0HP z^)|nVZFM_g%!_kmqVKA=jfB4mB#4<;X*yJz4$m)_NSP)X+a%Rah---8`AW<4Xt^E@ z$HQ^G_T{fmJ7HaJGx4{_e{`luswWDvZZ7hdh*2_Qnu*Lb|2r=$(DZzK%4QXA zLv%krQ)D7-$BTJ`ERdlSc_#|mBvQVmVaizlfa1lL0Rz~f#W7ac!av5rP?3*g-it^QB6SCheZ6xyGF@nA{|N_Q^`UCZd#2^Zk13V`%;zmqrqKl6uh0rBBO;ub zp9nGs^%KKFxpn+RlQRM#Yg?fzgaSF+pkN!@@{LqsGG#_fA3~6^rhB+!+>pmJ3ATyY zdm2|w*i;|m-k@s7AO`2qt~I*a4u8ayZE^F=M31jr*Zfpl0)J9WTdk%S9}78_0sO*U z+0OU+Fz`s|{M*LBgOm9PMD!Ee5{hhnK!h#nkh}ZKn8G&EKS2AQy~MoljyTfuvt5#V zVyz(JMOVM3VUV#c@@boiY?{ezknuKaTY=H6^PM(bXc}av?;UiK$HR&H;f-(owQnE0 zK>Wc;SB}|2WF8B&rWOv{tS=K^e4HLd2OxX_HC~(1Nfbn7x@0qR{;LhGUVR~+_VKB zsvMmvu&(v0B%uIJO=-(eR(+x`<1 z1#UYq&5`Mez+lmTi zL7_fP_hU-CXS5N+;KckC8QTm``xX4}uUf8uGEyDxfkdU=^+1uFW%`bLl9%pphsiOf6 zA|v0rZ;ChFb&MAuo+D#NXOkJRD-02i&Q9>)i*Dxr;|Iv3tccTq0p~x=aj_;S)B9=< z9M_1zt@Bes)}IJWsq!ID`OF~Mj}kKg*Y`+$)AlHgq3?&62&ye7hyxc9pZ!wJ8*&R0_Njm^X zW{SMw&SM;!E+(ke-9Qq;@9v2{gul0U{SE}X|0FrvM!RXKOBF}?s40BZ6nOV*CKI*YY@7z1< z%LEE0OxrMJLEZvOKu|mt>OPblSaG4`g440h#ogiL=KzTD5<@|7=bERN;Phto!Al~wNvF<-R1KO4PL!JOUfAjL_i4AL4WpI zJ^T_-yG*(1aII4B8EaU{?Sw$=f90)*I67PGOH&Hu47e!^$Fp!C4aGL$Z-|Utr!NXM zAC_A1TpiBVVa16#1R|j8VkX6_PaNUn?m4cmRYu4~Fe2fvzurTH-v-kVZr?xMw$X%- zPvP$cJ}9F6!B-^T{b2wVpaZTdX~QV_s=u$Qjox@+0hdy4o+)y8ve4&4_OlIimFfF9 z*n-QAR^W>eLsO^LT6}JyLDScvNKXW6uJWmCb;&ZHx>Ii6tD&EF2J|!hp(H17Z+g0X8oRn-}9pccOi;qz%1S{1OI40ymlHU z``mw0g~?o+uQ+jpiEMh)I!Xp3>iL`M>^}tOtL+= zQ|4OT<0F@AoL*~1mlY^ATAW|0Y;-y{2x9^f1~r8f`=+_$z)as3=WO`eIe617983p< z?UQWi9XkO8g#W-KeDgjyk?XGx%unQa>CFeR5}jT5%rVx6*hkObW4eAX*p|s1`={4w zDC{c>e9#pBMo|7}0NuX>12X^!f`ST^ebxV}uohNERXIGFA}q^zQF>%M7D?4L<4 z2@WGQZhEzM3BNPWqtiw9Pvjy4egLZq|J6~-lc^*d?N%6C`TSR(DWi~1oASHBzX)wy zV4>3BZ=X6(x#|J9AsyVfuK|S_ zSb|Tj21deAdx^kGwZY##_AI4Zqiql9cOo>v%e`pei-AJg<_)(UVY-vln5fzB?#za~8g4 zHZbySCu#T!pIw8`tP&}i3GnFiSNPnSi(AJsEa7i@eSHwW0QcRrj~5-7*?I!L0hfK% zZxP|M?-`Q#VMrCA1D+3SP49EQ>btwg*!y$G{;A-)@X&eWhf@uz%@&VcSY)-3E;l5gpq`VgUc` zfg%%W(+BxoA5Qi1ULTTU?c_rfw9jpc}7)#jkX_1?~ku=Xfnrb z`$ijVHwuz{+n_+H(d3KIU*ox@5}uzt6?7612^^44FRbyU3ky^`M!&IS+Z}dLaMRo* zX*+uNzdZ-HWOtT4l)#zj<&yz32pzh>O>Me)IFD~+>^Ve9e zHUjxz1V#{q9#k>hdtgoF| z@vi4{tz75f^9y|L+*PiYs-p$`=*J+!7)I2;z)T^_L@pgELf)K(`PA-_O4@+C3egP6 zHVqC=^|>doWw*ah3BL^~%jBNJbL&ZMy3>?&NdQ+B-eCx9{nMBvgRz9kea~yB_`c7T z@qPV-7`)36c}rNiPUN5$9hl~k3yWj^WZLyl%*lZS;h&#dT;t+ug{fScxk8rdT!wtw z#x@O1*>DX~6yH}I&!^ICv0MwnUai$zL6DDtGmLd6{ihnw1ZE34?1+m#w!kg9z{h`^ zq9gPlK5#e_r~^w*_-C3c=CDiyw>QYkb!O2|3b*Z_3L^cqnOcN&^e)HuAF>SLkB4yj z34^i4$^N}p6!}-5E&HncHo{i}FApmx0(T#p;aa)QrPazd5l@F8;u(SCc`TG_ER<{Q zpqD0T%L?m+5X$}eX0$gDj^O_5>Z*B z%0(53EI2l>O@n+yym%$>>(6b2!_S~#5-_4CLr|eW+BV6hY|6DpJMG*H6S66bdk)Q! zv8+(yZwmx#3co1G1%J$6ZY(x{4*br`3wWMS)A9cT^!M6%M7C&YzQTct94|RM*A}<8 z8@fgir!q>!&k)@tKdOxS8D^S_Z5ly-gb0}Gy4bU;Ai-Fo>;RTwkW1Uc z`l>&p$R*bw)~!4qwuJ$7j1&da_^_(}I;!5Lj4^F?r>f*r7O%KzAH~crTc)Dagn`F2 zCUG9ZnLsSV(Dv=cnB*xthR^;G%Zs)#k;|@vLLp=G(xdZ1fX}X@nx4<;3HrZvlTERnAvszL@3yo90Pbdu4g{e2S52c^mZo2=M$M zK}r;Twi}e;qgc*w;fIR-W@Xv4hr4OU{ndAPy3_%XyByeM_dq5pzgCP-pRcnK+_Kx zQq&iByzLQtcMA*=UUYCah{>qYOGn{RRDWERm8NblJ!~rj_|5xsR9Y?>%X|{$EeeZK zWkukwgEQQ|e|oQ|s4);DA?zk`2{^7t(+v+nrWyq9+ynF2RhG={|9 z1tEy*fBU}Z_1#`Ri>mq)BJz|E{GZot-|i1Ppc4M?*UwOHxh&S4zw%Xo88!~udyRvZ zA>4O#j$^aQvYPB2=w%4oB`NfWmgiAxhQ}!sfn_Jyd3PIB11F%Up|m_dxK;TAfg{sJ z?mjddIG7Qs2(|?MrMxMBCBXYfyy=$&c0}oZ_p2wF$yq+iZ>jR`E}PY+P1M&QdBn?a z+QPH1{3dM?PhT zF6H%4t@`)A>d&-%t&9@kXHeKtvwPiDJYS#leElJXrz7#EFq=>Fs^bTl%U|7UU8L8-uTKZLNa(dq7tiVUiQs6!#z7&vY)` z!=tt9R9pdl3iuC#oZIO+?nqxu#i!iv+z*TIP#uRMN`eEJ3x zND7|kaeiqn^0?1ac(@dt2<$Wy@PS9luo~631iOE!-nc&Xe}!Dy=DwTuv#*$IpY?Yu zx|=SUtSR#2ceJkkttrBLc1Z8nRCTFiL$KvYoN<1-+c}QGq!9%OTF=W&+A;~Tb- zFSBg}@KAnF;HM3!|H(Go@EZhn-TD51ylMj5G}dfG-lD4igD~Ha^~9rac&fmwZaws6 zdO9!w2&`2bJayq(q-(GcSmHmj3{O=zo{f1JEVkh9mf%7&x_uG2u(Zax#ntO;_g5%p zQ@s4x0S-*$*RA|pd_IDIeBciZ;U^GE+i|^jP}p?_0PwB{iqt(_aeVzTRejIKEr~Yf zI#D|baCvYd$E$BS*!B~+!382g0x96a@>(QK2~hFi!%IQtp4OO-2Rm&%TZ6w`fb)%b z;;%GXJbLa5&h>QmuR(U>mmfQ@VepH--KW624fH>HzOKA)7sdW=*i)?byFOJyRri~Q z{HDOSZdtWm$Q2P5D-Aw(?kd+xyL6^?J%B2+#XJw*bAri;NWl=SP7}B_7sTz~pB^r7 zXy-GOTzIq`{PgoV@O+<7pL(9BFD~phwg!T^LY9{v-N#fevo4{A;X{Le-!H-ssB-oF zdtm49z}||u@J(;#P%&HeeEo!o+$3-pF>5EEw%I?Cqvp7j>a77f`Z59$>P?4w%i+-U z1a`!g>&`-5a^Oroa2HM)kTrt99i6mkVG+|h_xe2zj`tp~z~8R~4o6YRKsP{n^1=d- zKYw-8-T1u>0vw(ya{o>Hn8>Eri9ZZC?VbPqpg-gzSEe({7k}@Uc6$n&=CY^Y@c(y? z2XC9kSKTMbuZZwn(xa+$FB3s%xGs-eSme~T)zRYfzkor2I}XkBz->nZpY#|sbzWtR zAb-H2bP&LE!VaEw@+A5is0Zga96aA3@6xcKYsJ zus4_Q36=1Yny6cO01Ewtdr>hS5)AaRq)~(YFXJzyEd9yFL6+*wbkL zu1~IE2$Lf6a|&+;>`euF@b3f9EtP^CeGO-i&u(`>fLjmD^1!V}$YoLk*-$m6&BXmn z5Vh#33)lGK*^9IsZ|{F64b;uM4$X3OW&(r02T%_1+otf7s;un=Ykw5%b=31OKT`rI zcs_3h{=Gn^-PNA^`j?hVeCfH%TwS|9F8Y`v+%!AE ziw@4V19)zJ--|AR72xL$;kSUsUODQEgX@9;0Q~b$t)XD5^6et>c7>yz2}B*gxW00F zVU;H?Em6Nt^voRv1;Y>yOcl86$bJsZOpvn7b<_2zAOgOxELCbeeQ|-a*OqBH?%sFw zQOKk$?l>^R@!84sC_a7QyugoTEqR~o*{1@P! zB=xS9Ydm^!k!z*e4d=lyP{lG$_DvQzabT81GZW;}DNNZ2Sdj!e8I~1jIvxw9D$ibB z;=*bHEW%9g#hp+UGI^XLGzUAXf%x5#y z9QR%VCh*42yU-7%!MdwEF3((D;ndaD;K=E^oB0laDu!WD%%(XoRph`_k(ok{LMDZ6 znd`@Yo2t+b;qCj>nhq<~I@d~7F0YhWsni0`zUzSSQAk+^$LFUwv2U7;Z6;xS3VcM+ zAM$-3Gfd@AUVHrzKU}9|@SP8>p#rKLF@&EI|?8xwKm4u}h0AR~x%MjlB!) zl`n=t+P0X;rkTj5$)!`+rh#D?Z9f3T^@8ZVl}3}bT9bOKwQ-gAdLaBZOyx4%b#Rvb zllj=Fy&DE!k;uA+*8$X5ez2VNTFS&yHbugcX@jVG=wv2epS@MQ-bUH|s| z)9fqekg$A;;fvy75&o0F2UXSoU$4J@iNAw~>mI)8qf5+WQ`8;r4#SY26ZjTE)}4kl z;!BDM4aeoo;ua2|TWgF-HCam)NP$7Ux;cS3dd-vJ!Z{|&s=S2<~k@+a3h z@xu#fxqs8gu8}j-C^}(?e6I*U0vt?Qf4SD=nJX(?TrFQehN~Np98|F_lY^55PVAdz zGM8>YkFC`C<&uc}iY2_Oqc=XdnMh=B^f+N#K zvbNRM^EZFM@I63%;L`#>pR{`Pc1Rg^6tg z0@NIr^DAYZTP(3!Zv;!^1&_Jw4BCW0nM-ri%p`}V3S?8(`a6xPv>7e|zhjF0Ue)oh z+*C06-3P7@m)>FF%VrsW`zKaW)Bt%vM1BnT1|S_*r#qDc1m1)fS1UZXxW-DYiSPS2 zjD@=mZSTd&T$*Du6C9i>kg=^0qSv@4nj-wA0q;l|@;O)WUue316nt5&-mm-M0(1E^ zuJ0Fp<)0w(!vZe?B&i>O01e0Ga;e7oykdydu zM_q4K;7Q<}rtn@LDW519{PO)TJY7EuzATsRKl=D8|LfspzU^f*w+MVMBL4z799KuX z{wE>`;CMdEwFVcK%Umv1sWqJ-O3V$ZV0%CV+-FlZ2PX3zo+>bv%U~JCy76vv`Pd*P z7ex3U0{`t_-aB>n4<9e_zSq96#r+68eEGt+ePR^@4272f{|1q7Rn=(`>Do!6E*t}G z*J4$uG+SI+t8!_z!fLG%NCog^j^Qyt8}*iDFqKVnaH_z8i9ETq9awg{g6=1j;#Pn^ zF@@huo5rKAs((G)+uts@u{?qA`qUZ%sg|d&QTTVj*8;h%CHtex@qAWl4KA-$xmv1H zZnSRL3WkBUZePfx*jLPPU?R_CE{$z=LPP4H0ZgjE2Mm$lFocJI)^2Cz*$Z%ERkq>l zKeR|8ZQ(0?+t7yr94*>5n3_kArQnL-0_q}dzw`aYl;LCL7 z^@s2O^fK34f+b`Xc@4t%pnNT`=>|wtII^t|W&I9))Ad-ZH(9FGS*kWDHJUUXuPq($ z!et0;N;V9Gv}IDvrkKfPnaO9E$fikKW}E0qlq4hq_bb5Pg1*ZV9;&*2{iV}s-hN+k zw`ad6;RU+V#s}a1*(GW%3)3Lo^7Q4vw_k;_nU}|WwqX9xl(7P)}Y#QXt^GquN&TL*V~us7*RzqL`YjE`LxYsF2hVd!(=8+CS_q6 zM!R@_D_+cNU*LJ*&qerSTjcXiUz@iV)BMW)Ic`8STyM*2SK-?~v5Fy>j?b;Yw*cP; z+^dipwe8^KL2-SbhT~CbwpgpTSgSWFH=5KP7uWN8*74PU@0YPve^A)%<#S!aw?DLkX(+B| z%qzUfSH2B+H9*07luCMsM-{tcj z+Uk~HqX!q#Mq7iZ zX(5Qo=Xf4X*Q4op)E$?m>$ac!ZRQY24ywVA->$zY`#KT?{glAB!8D9@BDYNgyRElp zQWj~;B4wGR%;3E#27x3$c4F^_vcQ)V{?Ziw#t?bhQ<^Em<-M=HVb~iMZZLo`!9V%? z75@0OQ+(Se)+QBR2K)ns2Wz~|@m2FiLyDoMl7rdb&4I4Wd!s~cu9h~vx4EsNxkb|9 z`^i>CN4ODLFzKR2DgCdYD*;m*>%$2;#%=tz2W^!LNgyv_E97rgB;T=riolSy`vX}cjD z-y$h>OvYg=Ym8Wh%#S4;^2QH3frqwb|629Fj`9A{``iW1jBgbFEbiA$ID3Af_i9Og z%QdUMGp%Q;WcX5+(B~^TA->=UpX0MTu8orqq&N6WA6h)^#L@Ft)TP&-)BWgfUh+$} zS(+z7{Gohpj-;?F@8%2VN-Nss4O*)ktxb0`ey{qlZu#{GY|J;DKj_OxC0{%jT%uKW`tr zb~f?XO*W_R96V*-2UHt3AIM~4zNaa8E>$P;-XwD^AAdQg}ge<)vH z!+8Hha>nHkQ}-OwDd4bJb8tUg(5bKl=RFYLfsmOtMi zC12>pE8fP?1jZds0#2_taZKkZ`{~W56X~>dUFQCTu*&n<&&s7+%@x>yN#wPJ-Np+K zOp7!3y?yN6rk}>9p%o;uaK$RuP%b5(tb%$)_8Y5~s3dT0+xC`MNMPsU2OSrbe(Yvj zvtW&42ymzb?J=cUteiy9YV$8jD|g9hA&lFZ!H;*!MN0xWurA(kleWq$?gVeoYIb6Mw<&;$TV{;&H0 literal 0 HcmV?d00001 diff --git a/docs/docs/assets/images/logo_dark.svg b/docs/docs/assets/images/logo_dark.svg new file mode 100644 index 00000000..0be819d0 --- /dev/null +++ b/docs/docs/assets/images/logo_dark.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + Logo + + Main circle + + + Inner circles + + + + + + + + + + + Text + + easy + + + diffraction + + + + \ No newline at end of file diff --git a/docs/docs/assets/images/logo_light.svg b/docs/docs/assets/images/logo_light.svg new file mode 100644 index 00000000..84103655 --- /dev/null +++ b/docs/docs/assets/images/logo_light.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + Logo + + Main circle + + + Inner circles + + + + + + + + + + + Text + + easy + + + diffraction + + + + \ No newline at end of file diff --git a/docs/assets/images/user-guide/data-acquisition_2d-raw-data.jpg b/docs/docs/assets/images/user-guide/data-acquisition_2d-raw-data.jpg similarity index 100% rename from docs/assets/images/user-guide/data-acquisition_2d-raw-data.jpg rename to docs/docs/assets/images/user-guide/data-acquisition_2d-raw-data.jpg diff --git a/docs/assets/images/user-guide/data-acquisition_instrument.png b/docs/docs/assets/images/user-guide/data-acquisition_instrument.png similarity index 100% rename from docs/assets/images/user-guide/data-acquisition_instrument.png rename to docs/docs/assets/images/user-guide/data-acquisition_instrument.png diff --git a/docs/assets/images/user-guide/data-analysis_model.png b/docs/docs/assets/images/user-guide/data-analysis_model.png similarity index 100% rename from docs/assets/images/user-guide/data-analysis_model.png rename to docs/docs/assets/images/user-guide/data-analysis_model.png diff --git a/docs/assets/images/user-guide/data-analysis_refinement.png b/docs/docs/assets/images/user-guide/data-analysis_refinement.png similarity index 100% rename from docs/assets/images/user-guide/data-analysis_refinement.png rename to docs/docs/assets/images/user-guide/data-analysis_refinement.png diff --git a/docs/assets/images/user-guide/data-reduction_1d-pattern.png b/docs/docs/assets/images/user-guide/data-reduction_1d-pattern.png similarity index 100% rename from docs/assets/images/user-guide/data-reduction_1d-pattern.png rename to docs/docs/assets/images/user-guide/data-reduction_1d-pattern.png diff --git a/docs/docs/assets/javascripts/extra.js b/docs/docs/assets/javascripts/extra.js new file mode 100644 index 00000000..9596ee07 --- /dev/null +++ b/docs/docs/assets/javascripts/extra.js @@ -0,0 +1,27 @@ +;(function () { + 'use strict' + + // Variables + const header = document.getElementsByTagName('header')[0] + + console.log(window.pageYOffset) + + // Hide-show header shadow + function toggleHeaderShadow() { + if (window.pageYOffset <= 0) { + header.classList.remove('md-header--shadow') + } else { + header.classList.add('md-header--shadow') + } + } + + // Onload + window.onload = function () { + toggleHeaderShadow() + } + + // Onscroll + window.onscroll = function () { + toggleHeaderShadow() + } +})() diff --git a/docs/docs/assets/javascripts/mathjax.js b/docs/docs/assets/javascripts/mathjax.js new file mode 100644 index 00000000..333524ec --- /dev/null +++ b/docs/docs/assets/javascripts/mathjax.js @@ -0,0 +1,33 @@ +window.MathJax = { + tex: { + //inlineMath: [['\\(', '\\)']], + //displayMath: [['\\[', '\\]']], + // Add support for $...$ and \(...\) delimiters + inlineMath: [ + ['$', '$'], + ['\\(', '\\)'], + ], + // Add support for $$...$$ and \[...]\ delimiters + displayMath: [ + ['$$', '$$'], + ['\\[', '\\]'], + ], + processEscapes: true, + processEnvironments: true, + }, + options: { + //ignoreHtmlClass: ".*|", + //processHtmlClass: "arithmatex" + // Skip code blocks only + skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'], + // Only ignore explicit opt-out + ignoreHtmlClass: 'no-mathjax|tex2jax_ignore', + }, +} + +document$.subscribe(() => { + MathJax.startup.output.clearCache() + MathJax.typesetClear() + MathJax.texReset() + MathJax.typesetPromise() +}) diff --git a/docs/docs/assets/stylesheets/extra.css b/docs/docs/assets/stylesheets/extra.css new file mode 100644 index 00000000..a625be80 --- /dev/null +++ b/docs/docs/assets/stylesheets/extra.css @@ -0,0 +1,359 @@ +/*************/ +/* Variables */ +/*************/ + +:root { + --sz-link-color--lightmode: #0184c7; + --sz-hovered-link-color--lightmode: #37bdf9; + --sz-body-background-color--lightmode: #fafafa; + --sz-body-text-color--lightmode: #525252; + --sz-body-heading-color--lightmode: #434343; + --sz-code-background-color--lightmode: #ececec; + + --sz-link-color--darkmode: #37bdf9; + --sz-hovered-link-color--darkmode: #2890c0; + --sz-body-background-color--darkmode: #262626; + --sz-body-text-color--darkmode: #a3a3a3; + --sz-body-heading-color--darkmode: #e5e5e5; + --sz-code-background-color--darkmode: #212121; +} + +/****************/ +/* Color styles */ +/****************/ + +/* Default styles https://github.com/squidfunk/mkdocs-material/blob/master/src/assets/stylesheets/main/_typeset.scss */ + +/* Light mode */ + +/* Default light mode https://github.com/squidfunk/mkdocs-material/blob/master/src/assets/stylesheets/main/_colors.scss */ + +[data-md-color-scheme="default"] { + + /* Primary color shades */ + --md-primary-fg-color: var(--sz-body-background-color--lightmode); /* Navigation background */ + --md-primary-bg-color: var(--sz-body-text-color--lightmode); /* E.g., Header title and icons */ + --md-primary-bg-color--light: var(--sz-body-text-color--lightmode); /* E.g., Header search */ + + /* Accent color shades */ + --md-accent-fg-color: var(--sz-hovered-link-color--lightmode); /* E.g., Hovered `a` elements & copy icon in code */ + + /* Default color shades */ + --md-default-fg-color: var(--sz-body-text-color--lightmode); + --md-default-fg-color--light: var(--sz-body-heading-color--lightmode); /* E.g., `h1` color & TOC viewed items & `$` in code */ + --md-default-fg-color--lighter: var(--sz-body-text-color--lightmode); /* E.g., `¶` sign near `h1-h8` */ + --md-default-fg-color--lightest: var(--sz-body-text-color--lightmode); /* E.g., Copy icon in code */ + --md-default-bg-color: var(--sz-body-background-color--lightmode); + + /* Code color shades */ + --md-code-bg-color: var(--sz-code-background-color--lightmode); + + /* Typeset color shades */ + --md-typeset-color: var(--sz-body-text-color--lightmode); + + /* Typeset `a` color shades */ + --md-typeset-a-color: var(--sz-link-color--lightmode); + + /* Footer color shades */ + --md-footer-fg-color: var(--sz-body-text-color--lightmode); /* E.g., Next -> */ + --md-footer-fg-color--light: var(--sz-body-text-color--lightmode); /* E.g., © 2022 EasyDiffraction, Material for MkDocs */ + --md-footer-fg-color--lighter: var(--sz-body-text-color--lightmode); /* E.g. Made with */ + --md-footer-bg-color: hsla(0, 0%, 0%, 0.0); /* Space with, e.g., Next -> */ + --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.0); /* Space with, e.g., © 2022 EasyDiffraction */ + + /* Custom colors */ + --sz-red-color: #F44336; + --sz-blue-color: #03A9F4; + --sz-green-color: #4CAF50; + --sz-orange-color: #FF9800; + + /* Logo display */ + --md-footer-logo-dark-mode: none; + --md-footer-logo-light-mode: block; +} + +/* Dark mode */ + +/* Default dark mode: https://github.com/squidfunk/mkdocs-material/blob/master/src/assets/stylesheets/palette/_scheme.scss */ + +[data-md-color-scheme="slate"] { + + /* Primary color shades */ + --md-primary-fg-color: var(--sz-body-background-color--darkmode); /* Navigation background */ + --md-primary-bg-color: var(--sz-body-text-color--darkmode); /* E.g., Header title and icons */ + --md-primary-bg-color--light: var(--sz-body-text-color--darkmode); /* E.g., Header search */ + + /* Accent color shades */ + --md-accent-fg-color: var(--sz-hovered-link-color--darkmode); /* E.g., Hovered `a` elements & copy icon in code */ + + /* Default color shades */ + --md-default-fg-color: var(--sz-body-text-color--darkmode); + --md-default-fg-color--light: var(--sz-body-heading-color--darkmode); /* E.g., `h1` color & TOC viewed items & `$` in code */ + --md-default-fg-color--lighter: var(--sz-body-text-color--darkmode); /* E.g., `¶` sign near `h1-h8` */ + --md-default-fg-color--lightest: var(--sz-body-text-color--darkmode); /* E.g., Copy icon in code */ + --md-default-bg-color: var(--sz-body-background-color--darkmode); + + /* Code color shades */ + --md-code-bg-color: var(--sz-code-background-color--darkmode); + + /* Typeset color shades */ + --md-typeset-color: var(--sz-body-text-color--darkmode); + + /* Typeset `a` color shades */ + --md-typeset-a-color: var(--sz-link-color--darkmode); + + /* Footer color shades */ + --md-footer-fg-color: var(--sz-body-text-color--darkmode); /* E.g., Next -> */ + --md-footer-fg-color--light: var(--sz-body-text-color--darkmode); /* E.g., © 2022 EasyDiffraction, Material for MkDocs */ + --md-footer-fg-color--lighter: var(--sz-body-text-color--darkmode); /* E.g. Made with */ + --md-footer-bg-color: hsla(0, 0%, 0%, 0.0); /* Space with, e.g., Next -> */ + --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.0); /* Space with, e.g., © 2022 EasyDiffraction */ + + /* Custom colors */ + --sz-red-color: #EF9A9A; + --sz-blue-color: #81D4FA; + --sz-green-color: #A5D6A7; + --sz-orange-color: #FFCC80; + + /* Logo display */ + --md-footer-logo-dark-mode: block; + --md-footer-logo-light-mode: none; +} + +/*****************/ +/* Custom styles */ +/*****************/ + +/* Logo */ + +#logo_light_mode { + display: var(--md-footer-logo-light-mode); +} + +#logo_dark_mode { + display: var(--md-footer-logo-dark-mode); +} + +/* Customize default styles of MkDocs Material */ + +/* Hide navigation title */ +label.md-nav__title[for="__drawer"] { + height: 0; +} + +/* Hide site title (first topic) while keeping page title and version selector */ +.md-header__topic:first-child .md-ellipsis { + display: none; +} + +/* Increase logo size */ +.md-logo :is(img, svg) { + height: 1.8rem !important; +} + +/* Hide GH repo with counts (top right page corner) */ +.md-header__source { + display: none; +} + +/* Hide GH repo with counts (navigation bar in mobile view) */ +.md-nav__source { + display: none; +} + +/* Ensure all horizontal lines in the navigation list are removed or hidden */ +.md-nav__item { + /* Removes any border starting from the second level */ + border: none !important; + /* Modifies the background color to hide the first horizontal line */ + background-color: var(--md-default-bg-color); +} + +/* Increase TOC (on the right) width */ +.md-nav--secondary { + margin-left: -10px; + margin-right: -4px; +} + +/* */ +.md-nav__item > .md-nav__link { + padding-left: 0.5em; /* Default */ +} + +/* Change line height of the tabel cells */ +.md-typeset td, +.md-typeset th { + line-height: 1.25 !important; +} + +/* Change vertical alignment of the icon inside the tabel cells */ +.md-typeset td .twemoji { + vertical-align: sub !important; +} + +/* Change the width of the primary sidebar */ +/* +.md-sidebar--primary { + width: 240px; +} +*/ + +/* Change the overall width of the page */ +.md-grid { + max-width: 1280px; +} + +/* Needed for mkdocs-jupyter to show download and other buttons on top of the notebook */ +.md-content__button { + position: relative !important; +} + +/* Background color of the search input field */ +.md-search__input { + background-color: var(--md-code-bg-color) !important; +} + +/* Customize default style of mkdocs-jupyter plugin */ + +/* Set the width of the notebook to fill 100% and not reduce by the width of .md-content__button's +Adjust the margins and paddings to fit the defaults in MkDocs Material and do not crop the label in the header +*/ +.jupyter-wrapper { + width: 100% !important; + display: flex !important; +} + +.jp-Notebook { + padding: 0 !important; + margin-top: -3em !important; + + /* Ensure notebook content stretches across the page */ + width: 100% !important; + max-width: 100% !important; + + /* mkdocs-material + some notebook HTML end up as flex */ + align-items: stretch !important; +} + +.jp-Notebook .jp-Cell { + /* Key: flex children often need min-width: 0 to prevent weird shrink */ + width: 100% !important; + max-width: 100% !important; + min-width: 0 !important; + + /* Removes jupyter cell paddings */ + padding-left: 0 !important; +} + +/* Removes jupyter cell prefixes, like In[123]: */ +.prompt, +.jp-InputPrompt, +.jp-OutputPrompt { + display: none !important; +} + +/* Removes jupyter output cell padding to align with input cell text */ +.jp-RenderedText { + padding-left: 0.85em !important; +} + +/* Extra styling the panda dataframes, on top of the style included in the code */ +table.dataframe { + float: left; + margin-left: 0.75em !important; + margin-bottom: 0.5em !important; + font-size: var(--jp-code-font-size) !important; + color: var(--md-primary-bg-color) !important; + /* Allow table cell wrapping in MkDocs-Jupyter outputs */ + /* + table-layout: auto !important; + width: auto !important; + */ +} + +/* Allow wrap for the last column */ +/* +table.dataframe td:last-child, +table.dataframe th:last-child { + white-space: normal !important; + word-break: break-word !important; +} +*/ + +/* Custom styles for the CIF files */ + +.cif { + padding-left: 1em; + padding-right: 1em; + padding-top: 1px; + padding-bottom: 1px; + background-color: var(--md-code-bg-color); + font-size: small; +} +.red { + color: var(--sz-red-color); +} +.green { + color: var(--sz-green-color); +} +.blue { + color: var(--sz-blue-color); +} +.orange { + color: var(--sz-orange-color); +} +.grey { + color: grey; +} + +/**********/ +/* Labels */ +/**********/ + +.label-cif { + padding-top: 0.5ex; + padding-bottom: 0.5ex; + padding-left: 0.9ex; + padding-right: 0.9ex; + border-radius: 1ex; + color: var(--md-default-fg-color) !important; + background-color: var(--md-code-bg-color); +} + +p .label-cif, li .label-cif { + vertical-align: 5%; + font-size: 12px; +} + +.label-cif:hover { + color: white !important; +} + +.label-experiment { + padding-top: 0.25ex; + padding-bottom: 0.6ex; + padding-left: 0.9ex; + padding-right: 0.9ex; + border-radius: 1ex; + color: var(--md-default-fg-color) !important; + background-color: rgba(55, 189, 249, 0.1); +} + +p .label-experiment, li .label-experiment { + vertical-align: 5%; + font-size: 12px; +} + +h1 .label-experiment { + padding-top: 0.05ex; + padding-bottom: 0.4ex; + padding-left: 0.9ex; + padding-right: 0.9ex; + border-radius: 0.75ex; + color: var(--md-default-fg-color) !important; + background-color: rgba(55, 189, 249, 0.1); +} + +.label-experiment:hover { + color: white !important; +} diff --git a/docs/index.md b/docs/docs/index.md similarity index 53% rename from docs/index.md rename to docs/docs/index.md index a34d3023..73c35423 100644 --- a/docs/index.md +++ b/docs/docs/index.md @@ -4,17 +4,18 @@ Here is a brief overview of the main documentation sections: -- [:material-information-slab-circle: Introduction](introduction/index.md) – - Provides an overview of EasyDiffraction, including its purpose, licensing, - latest release details, and contact information. -- [:material-cog-box: Installation & Setup](installation-and-setup/index.md) – - Guides users through system requirements, environment configuration, and the - installation process. -- [:material-book-open-variant: User Guide](user-guide/index.md) – Covers core - concepts, key terminology, workflow steps, and essential parameters for - effective use of EasyDiffraction. +- [:material-information-slab-circle: Introduction](introduction/index.md) + – Provides an overview of EasyDiffraction, including its purpose, + licensing, latest release details, and contact information. +- [:material-cog-box: Installation & Setup](installation-and-setup/index.md) + – Guides users through system requirements, environment configuration, + and the installation process. +- [:material-book-open-variant: User Guide](user-guide/index.md) – + Covers core concepts, key terminology, workflow steps, and essential + parameters for effective use of EasyDiffraction. - [:material-school: Tutorials](tutorials/index.md) – Offers practical, - step-by-step examples demonstrating common workflows and data analysis tasks. -- [:material-code-braces-box: API Reference](api-reference/index.md) – An - auto-generated reference detailing the available functions and modules in - EasyDiffraction. + step-by-step examples demonstrating common workflows and data analysis + tasks. +- [:material-code-braces-box: API Reference](api-reference/index.md) – + An auto-generated reference detailing the available functions and + modules in EasyDiffraction. diff --git a/docs/installation-and-setup/index.md b/docs/docs/installation-and-setup/index.md similarity index 80% rename from docs/installation-and-setup/index.md rename to docs/docs/installation-and-setup/index.md index 80b64235..0891a55f 100644 --- a/docs/installation-and-setup/index.md +++ b/docs/docs/installation-and-setup/index.md @@ -6,16 +6,16 @@ icon: material/cog-box ## Requirements -EasyDiffraction is a cross-platform Python library compatible with **Python 3.11 -through 3.13**. +EasyDiffraction is a cross-platform Python library compatible with +**Python 3.11 through 3.13**. Make sure Python is installed on your system before proceeding with the installation. ## Environment Setup optional { #environment-setup data-toc-label="Environment Setup" } -We recommend using a **virtual environment** to isolate dependencies and avoid -conflicts with system-wide packages. If any issues arise, you can simply delete -and recreate the environment. +We recommend using a **virtual environment** to isolate dependencies and +avoid conflicts with system-wide packages. If any issues arise, you can +simply delete and recreate the environment. #### Creating and Activating a Virtual Environment: @@ -76,22 +76,23 @@ and recreate the environment. ### Installing from PyPI recommended { #from-pypi data-toc-label="Installing from PyPI" } -EasyDiffraction is available on **PyPI (Python Package Index)** and can be -installed using `pip`. We strongly recommend installing it within a virtual -environment, as described in the [Environment Setup](#environment-setup) -section. +EasyDiffraction is available on **PyPI (Python Package Index)** and can +be installed using `pip`. We strongly recommend installing it within a +virtual environment, as described in the +[Environment Setup](#environment-setup) section. We recommend installing the latest release of EasyDiffraction with the -`visualization` extras, which include optional dependencies used for simplified -visualization of charts and tables. This can be especially useful for running -the Jupyter Notebook examples. To do so, use the following command: +`visualization` extras, which include optional dependencies used for +simplified visualization of charts and tables. This can be especially +useful for running the Jupyter Notebook examples. To do so, use the +following command: ```bash pip install 'easydiffraction[visualization]' ``` -If only the core functionality is needed, the library can be installed simply -with: +If only the core functionality is needed, the library can be installed +simply with: ```bash pip install easydiffraction @@ -117,8 +118,8 @@ pip show easydiffraction ### Installing from GitHub -Installing unreleased versions is generally not recommended but may be useful -for testing. +Installing unreleased versions is generally not recommended but may be +useful for testing. To install EasyDiffraction from, e.g., the `develop` branch of GitHub: @@ -134,18 +135,20 @@ pip install 'easydiffraction[visualization] @ git+https://github.com/easyscience ## How to Run Tutorials -EasyDiffraction includes a collection of **Jupyter Notebook examples** that -demonstrate key functionality. These tutorials serve as **step-by-step guides** -to help users understand the diffraction data analysis workflow. +EasyDiffraction includes a collection of **Jupyter Notebook examples** +that demonstrate key functionality. These tutorials serve as +**step-by-step guides** to help users understand the diffraction data +analysis workflow. They are available as **static HTML pages** in the -[:material-school: Tutorials](../tutorials/index.md) section. You can also run -them interactively in two ways: +[:material-school: Tutorials](../tutorials/index.md) section. You can +also run them interactively in two ways: - **Run Locally** – Download the notebook via the :material-download: **Download** button and run it on your computer. -- **Run Online** – Use the :google-colab: **Open in Google Colab** button to run - the tutorial directly in your browser (no setup required). +- **Run Online** – Use the :google-colab: **Open in Google Colab** + button to run the tutorial directly in your browser (no setup + required). !!! note @@ -154,8 +157,9 @@ them interactively in two ways: ### Run Tutorials Locally -To run tutorials locally, install **Jupyter Notebook** or **JupyterLab**. Here -are the steps to follow in the case of **Jupyter Notebook**: +To run tutorials locally, install **Jupyter Notebook** or +**JupyterLab**. Here are the steps to follow in the case of **Jupyter +Notebook**: - Install Jupyter Notebook and IPython kernel: ```bash @@ -177,32 +181,34 @@ are the steps to follow in the case of **Jupyter Notebook**: ```bash http://localhost:8888/ ``` -- Open one of the `*.ipynb` files and select the `EasyDiffraction Python kernel` - to get started. +- Open one of the `*.ipynb` files and select the + `EasyDiffraction Python kernel` to get started. ### Run Tutorials via Google Colab -**Google Colab** lets you run Jupyter Notebooks in the cloud without any local -installation. +**Google Colab** lets you run Jupyter Notebooks in the cloud without any +local installation. To use Google Colab: - Ensure you have a **Google account**. -- Go to the **[:material-school: Tutorials](../tutorials/index.md)** section. -- Click the :google-colab: **Open in Google Colab** button on any tutorial. +- Go to the **[:material-school: Tutorials](../tutorials/index.md)** + section. +- Click the :google-colab: **Open in Google Colab** button on any + tutorial. -This is the fastest way to start experimenting with EasyDiffraction, without -setting up Python on your system. +This is the fastest way to start experimenting with EasyDiffraction, +without setting up Python on your system. ## Installing with Pixi alternative { #installing-with-pixi data-toc-label="Installing with Pixi" } -[Pixi](https://pixi.sh) is a modern package and environment manager for Python -and Conda-compatible packages. It simplifies dependency management, environment -isolation, and reproducibility. +[Pixi](https://pixi.sh) is a modern package and environment manager for +Python and Conda-compatible packages. It simplifies dependency +management, environment isolation, and reproducibility. The following simple steps provide an alternative setup method for -EasyDiffraction using Pixi, replacing the traditional virtual environment -approach. +EasyDiffraction using Pixi, replacing the traditional virtual +environment approach. diff --git a/docs/introduction/index.md b/docs/docs/introduction/index.md similarity index 90% rename from docs/introduction/index.md rename to docs/docs/introduction/index.md index 2996386b..a2b42b59 100644 --- a/docs/introduction/index.md +++ b/docs/docs/introduction/index.md @@ -8,21 +8,22 @@ icon: material/information-slab-circle **EasyDiffraction** is scientific software for calculating diffraction patterns -based on structural models and refining model parameters against experimental -data. +based on structural models and refining model parameters against +experimental data. -It is available as both a cross-platform desktop application and a Python -library. +It is available as both a cross-platform desktop application and a +Python library. -This documentation covers the usage of the EasyDiffraction Python library. +This documentation covers the usage of the EasyDiffraction Python +library. For the graphical user interface (GUI) version, refer to the [GUI documentation](https://docs.easydiffraction.org/app). ## EasyScience EasyDiffraction is developed using the -[EasyScience framework](https://easyscience.software), which provides tools -for +[EasyScience framework](https://easyscience.software), which provides +tools for building modular and flexible scientific libraries and applications. ## License @@ -35,27 +36,28 @@ EasyDiffraction is released under the The latest version of the EasyDiffraction Python library is [{{ vars.release_version }}](https://github.com/easyscience/diffraction-lib/releases/latest). -For a complete list of new features, bug fixes, and improvements, see the +For a complete list of new features, bug fixes, and improvements, see +the [GitHub Releases page](https://github.com/easyscience/diffraction-lib/releases). ## Citation -If you use EasyDiffraction in your work, please cite the specific version you -used. +If you use EasyDiffraction in your work, please cite the specific +version you used. All official releases of the EasyDiffraction library are archived on Zenodo, each with a version-specific Digital Object Identifier (DOI). -Citation details in various styles (e.g., APA, MLA) and formats (e.g., BibTeX, -JSON) +Citation details in various styles (e.g., APA, MLA) and formats (e.g., +BibTeX, JSON) are available on the [Zenodo archive page](https://doi.org/10.5281/zenodo.16806521). ## Contributing -We welcome contributions from the community! EasyDiffraction is intended to be a -community-driven, open-source project supported by a diverse group of -contributors. +We welcome contributions from the community! EasyDiffraction is intended +to be a community-driven, open-source project supported by a diverse +group of contributors. The project is maintained by the [European Spallation Source (ESS)](https://ess.eu). diff --git a/docs/docs/tutorials/ed-1.ipynb b/docs/docs/tutorials/ed-1.ipynb new file mode 100644 index 00000000..9b46deb0 --- /dev/null +++ b/docs/docs/tutorials/ed-1.ipynb @@ -0,0 +1,203 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: LBCO, HRPT\n", + "\n", + "This minimalistic example is designed to show how Rietveld refinement\n", + "can be performed when both the crystal structure and experiment\n", + "parameters are defined using CIF files.\n", + "\n", + "For this example, constant-wavelength neutron powder diffraction data\n", + "for La0.5Ba0.5CoO3 from HRPT at PSI is used.\n", + "\n", + "It does not contain any advanced features or options, and includes no\n", + "comments or explanations—these can be found in the other tutorials.\n", + "Default values are used for all parameters if not specified. Only\n", + "essential and self-explanatory code is provided.\n", + "\n", + "The example is intended for users who are already familiar with the\n", + "EasyDiffraction library and want to quickly get started with a simple\n", + "refinement. It is also useful for those who want to see what a\n", + "refinement might look like in code. For a more detailed explanation of\n", + "the code, please refer to the other tutorials." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Step 1: Define Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "# Create minimal project without name and description\n", + "project = ed.Project()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Step 2: Define Crystal Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "# Download CIF file from repository\n", + "structure_path = ed.download_data(id=1, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add_from_cif_path(structure_path)" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "## Step 3: Define Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "# Download CIF file from repository\n", + "expt_path = ed.download_data(id=2, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_cif_path(expt_path)" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Step 4: Perform Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "# Start refinement. All parameters, which have standard uncertainties\n", + "# in the input CIF files, are refined by default.\n", + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "# Show fit results summary\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.show_names()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "16", + "metadata": {}, + "source": [ + "## Step 5: Show Project Summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17", + "metadata": {}, + "outputs": [], + "source": [ + "project.summary.show_report()" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-1.py b/docs/docs/tutorials/ed-1.py similarity index 100% rename from tutorials/ed-1.py rename to docs/docs/tutorials/ed-1.py diff --git a/docs/docs/tutorials/ed-10.ipynb b/docs/docs/tutorials/ed-10.ipynb new file mode 100644 index 00000000..d6596816 --- /dev/null +++ b/docs/docs/tutorials/ed-10.ipynb @@ -0,0 +1,222 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Pair Distribution Function: Ni, NPD\n", + "\n", + "This example demonstrates a pair distribution function (PDF) analysis\n", + "of Ni, based on data collected from a constant wavelength neutron\n", + "powder diffraction experiment.\n", + "\n", + "The dataset is taken from:\n", + "https://github.com/diffpy/cmi_exchange/tree/main/cmi_scripts/fitNiPDF" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "project = ed.Project()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.create(name='ni')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['ni'].space_group.name_h_m = 'F m -3 m'\n", + "project.structures['ni'].space_group.it_coordinate_system_code = '1'\n", + "project.structures['ni'].cell.length_a = 3.52387\n", + "project.structures['ni'].atom_sites.create(\n", + " label='Ni',\n", + " type_symbol='Ni',\n", + " fract_x=0.0,\n", + " fract_y=0.0,\n", + " fract_z=0.0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.5,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "## Add Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = ed.download_data(id=6, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_data_path(\n", + " name='pdf',\n", + " data_path=data_path,\n", + " sample_form='powder',\n", + " beam_mode='constant wavelength',\n", + " radiation_probe='neutron',\n", + " scattering_type='total',\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['pdf'].linked_phases.create(id='ni', scale=1.0)\n", + "project.experiments['pdf'].peak.damp_q = 0\n", + "project.experiments['pdf'].peak.broad_q = 0.03\n", + "project.experiments['pdf'].peak.cutoff_q = 27.0\n", + "project.experiments['pdf'].peak.sharp_delta_1 = 0.0\n", + "project.experiments['pdf'].peak.sharp_delta_2 = 2.0\n", + "project.experiments['pdf'].peak.damp_particle_diameter = 0" + ] + }, + { + "cell_type": "markdown", + "id": "12", + "metadata": {}, + "source": [ + "## Select Fitting Parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['ni'].cell.length_a.free = True\n", + "project.structures['ni'].atom_sites['Ni'].b_iso.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['pdf'].linked_phases['ni'].scale.free = True\n", + "project.experiments['pdf'].peak.broad_q.free = True\n", + "project.experiments['pdf'].peak.sharp_delta_2.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "## Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "## Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='pdf', show_residual=True)" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-10.py b/docs/docs/tutorials/ed-10.py similarity index 100% rename from tutorials/ed-10.py rename to docs/docs/tutorials/ed-10.py diff --git a/docs/docs/tutorials/ed-11.ipynb b/docs/docs/tutorials/ed-11.ipynb new file mode 100644 index 00000000..ddacaac7 --- /dev/null +++ b/docs/docs/tutorials/ed-11.ipynb @@ -0,0 +1,254 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Pair Distribution Function: Si, NPD\n", + "\n", + "This example demonstrates a pair distribution function (PDF) analysis\n", + "of Si, based on data collected from a time-of-flight neutron powder\n", + "diffraction experiment at NOMAD at SNS." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "project = ed.Project()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Set Plotting Engine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "# Keep the auto-selected engine. Alternatively, you can uncomment the\n", + "# line below to explicitly set the engine to the required one.\n", + "# project.plotter.engine = 'plotly'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "# Set global plot range for plots\n", + "project.plotter.x_max = 40" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "## Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.create(name='si')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure = project.structures['si']\n", + "structure.space_group.name_h_m.value = 'F d -3 m'\n", + "structure.space_group.it_coordinate_system_code = '1'\n", + "structure.cell.length_a = 5.43146\n", + "structure.atom_sites.create(\n", + " label='Si',\n", + " type_symbol='Si',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.5,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Add Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = ed.download_data(id=5, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_data_path(\n", + " name='nomad',\n", + " data_path=data_path,\n", + " sample_form='powder',\n", + " beam_mode='time-of-flight',\n", + " radiation_probe='neutron',\n", + " scattering_type='total',\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "experiment = project.experiments['nomad']\n", + "experiment.linked_phases.create(id='si', scale=1.0)\n", + "experiment.peak.damp_q = 0.02\n", + "experiment.peak.broad_q = 0.03\n", + "experiment.peak.cutoff_q = 35.0\n", + "experiment.peak.sharp_delta_1 = 0.0\n", + "experiment.peak.sharp_delta_2 = 4.0\n", + "experiment.peak.damp_particle_diameter = 0" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "## Select Fitting Parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['si'].cell.length_a.free = True\n", + "project.structures['si'].atom_sites['Si'].b_iso.free = True\n", + "experiment.linked_phases['si'].scale.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.peak.damp_q.free = True\n", + "experiment.peak.broad_q.free = True\n", + "experiment.peak.sharp_delta_1.free = True\n", + "experiment.peak.sharp_delta_2.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "18", + "metadata": {}, + "source": [ + "## Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "20", + "metadata": {}, + "source": [ + "## Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='nomad', show_residual=False)" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-11.py b/docs/docs/tutorials/ed-11.py similarity index 100% rename from tutorials/ed-11.py rename to docs/docs/tutorials/ed-11.py diff --git a/docs/docs/tutorials/ed-12.ipynb b/docs/docs/tutorials/ed-12.ipynb new file mode 100644 index 00000000..a6394390 --- /dev/null +++ b/docs/docs/tutorials/ed-12.ipynb @@ -0,0 +1,303 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Pair Distribution Function: NaCl, XRD\n", + "\n", + "This example demonstrates a pair distribution function (PDF) analysis\n", + "of NaCl, based on data collected from an X-ray powder diffraction\n", + "experiment.\n", + "\n", + "The dataset is taken from:\n", + "https://github.com/diffpy/add2019-diffpy-cmi/tree/master" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "project = ed.Project()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Set Plotting Engine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "# Keep the auto-selected engine. Alternatively, you can uncomment the\n", + "# line below to explicitly set the engine to the required one.\n", + "# project.plotter.engine = 'plotly'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "# Set global plot range for plots\n", + "project.plotter.x_min = 2.0\n", + "project.plotter.x_max = 30.0" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "## Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.create(name='nacl')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['nacl'].space_group.name_h_m = 'F m -3 m'\n", + "project.structures['nacl'].space_group.it_coordinate_system_code = '1'\n", + "project.structures['nacl'].cell.length_a = 5.62\n", + "project.structures['nacl'].atom_sites.create(\n", + " label='Na',\n", + " type_symbol='Na',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=1.0,\n", + ")\n", + "project.structures['nacl'].atom_sites.create(\n", + " label='Cl',\n", + " type_symbol='Cl',\n", + " fract_x=0.5,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='b',\n", + " b_iso=1.0,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Add Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = ed.download_data(id=4, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_data_path(\n", + " name='xray_pdf',\n", + " data_path=data_path,\n", + " sample_form='powder',\n", + " beam_mode='constant wavelength',\n", + " radiation_probe='xray',\n", + " scattering_type='total',\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['xray_pdf'].show_supported_peak_profile_types()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['xray_pdf'].show_current_peak_profile_type()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['xray_pdf'].peak_profile_type = 'gaussian-damped-sinc'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['xray_pdf'].peak.damp_q = 0.03\n", + "project.experiments['xray_pdf'].peak.broad_q = 0\n", + "project.experiments['xray_pdf'].peak.cutoff_q = 21\n", + "project.experiments['xray_pdf'].peak.sharp_delta_1 = 0\n", + "project.experiments['xray_pdf'].peak.sharp_delta_2 = 5\n", + "project.experiments['xray_pdf'].peak.damp_particle_diameter = 0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['xray_pdf'].linked_phases.create(id='nacl', scale=0.5)" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "## Select Fitting Parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['nacl'].cell.length_a.free = True\n", + "project.structures['nacl'].atom_sites['Na'].b_iso.free = True\n", + "project.structures['nacl'].atom_sites['Cl'].b_iso.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['xray_pdf'].linked_phases['nacl'].scale.free = True\n", + "project.experiments['xray_pdf'].peak.damp_q.free = True\n", + "project.experiments['xray_pdf'].peak.sharp_delta_2.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "22", + "metadata": {}, + "source": [ + "## Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "24", + "metadata": {}, + "source": [ + "## Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='xray_pdf')" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-12.py b/docs/docs/tutorials/ed-12.py similarity index 100% rename from tutorials/ed-12.py rename to docs/docs/tutorials/ed-12.py diff --git a/docs/docs/tutorials/ed-13.ipynb b/docs/docs/tutorials/ed-13.ipynb new file mode 100644 index 00000000..d35f3628 --- /dev/null +++ b/docs/docs/tutorials/ed-13.ipynb @@ -0,0 +1,2923 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Fitting Powder Diffraction data\n", + "\n", + "This notebook guides you through the Rietveld refinement of crystal\n", + "structures using simulated powder diffraction data. It consists of two\n", + "parts:\n", + "- Introduction: A simple reference fit using silicon (Si) crystal\n", + " structure.\n", + "- Exercise: A more complex fit using La₀.₅Ba₀.₅CoO₃ (LBCO) crystal\n", + " structure.\n", + "\n", + "## 🛠️ Import Library\n", + "\n", + "We start by importing the necessary library for the analysis. In this\n", + "notebook, we use the EasyDiffraction library. As mentioned in the\n", + "introduction to EasyScience, EasyDiffraction is built on that\n", + "framework and offers a high-level interface focused specifically for\n", + "diffraction analysis.\n", + "\n", + "This notebook is self-contained and designed for hands-on learning.\n", + "However, if you're interested in exploring more advanced features or\n", + "learning about additional capabilities of the EasyDiffraction library,\n", + "please refer to the official documentation:\n", + "https://docs.easydiffraction.org/lib\n", + "\n", + "Depending on your requirements, you may choose to import only specific\n", + "classes. However, for the sake of simplicity in this notebook, we will\n", + "import the entire library." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/first-steps/#importing-easydiffraction)\n", + "for more details about importing the EasyDiffraction library and its\n", + "components." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## 📘 Introduction: Simple Reference Fit – Si\n", + "\n", + "Before diving into the more complex fitting exercise with the\n", + "La₀.₅Ba₀.₅CoO₃ (LBCO) crystal structure, let's start with a simpler\n", + "example using the silicon (Si) crystal structure. This will help us\n", + "understand the basic concepts and steps involved in fitting a crystal\n", + "structure using powder diffraction data.\n", + "\n", + "For this part of the notebook, we will use the powder diffraction data\n", + "previously simulated using the Si crystal structure.\n", + "\n", + "### 📦 Create a Project – 'reference'\n", + "\n", + "In EasyDiffraction, a project serves as a container for all\n", + "information related to the analysis of a specific experiment or set of\n", + "experiments. It enables you to organize your data, experiments,\n", + "crystal structures, and fitting parameters in an organized manner. You\n", + "can think of it as a folder containing all the essential details about\n", + "your analysis. The project also allows us to visualize both the\n", + "measured and calculated diffraction patterns, among other things." + ] + }, + { + "cell_type": "markdown", + "id": "4", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/project/)\n", + "for more details about creating a project and its purpose in the\n", + "analysis workflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "project_1 = ed.Project(name='reference')" + ] + }, + { + "cell_type": "markdown", + "id": "6", + "metadata": {}, + "source": [ + "You can set the title and description of the project to provide\n", + "context and information about the analysis being performed. This is\n", + "useful for documentation purposes and helps others (or yourself in the\n", + "future) understand the purpose of the project at a glance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.info.title = 'Reference Silicon Fit'\n", + "project_1.info.description = 'Fitting simulated powder diffraction pattern of Si.'" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "### 🔬 Create an Experiment\n", + "\n", + "An experiment represents a specific diffraction measurement performed\n", + "on a specific sample using a particular instrument. It contains\n", + "details about the measured data, instrument parameters, and other\n", + "relevant information." + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/)\n", + "for more details about experiments and their purpose in the analysis\n", + "workflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "data_dir = 'data'\n", + "file_name = 'reduced_Si.xye'\n", + "si_xye_path = f'{data_dir}/{file_name}'" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "Uncomment the following cell if your data reduction failed and the\n", + "reduced data file is missing. In this case, you can download our\n", + "pre-generated reduced data file from the EasyDiffraction repository.\n", + "The `download_data` function will not overwrite an existing file\n", + "unless you set `overwrite=True`, so it's safe to run even if the\n", + "file is already present." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "si_xye_path = ed.download_data(id=17, destination=data_dir)" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "Now we can create the experiment and load the measured data. In this\n", + "case, the experiment is defined as a powder diffraction measurement\n", + "using time-of-flight neutrons. The measured data is loaded from a file\n", + "containing the reduced diffraction pattern of Si from the data\n", + "reduction notebook." + ] + }, + { + "cell_type": "markdown", + "id": "14", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#defining-an-experiment-manually)\n", + "for more details about different types of experiments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.experiments.add_from_data_path(\n", + " name='sim_si',\n", + " data_path=si_xye_path,\n", + " sample_form='powder',\n", + " beam_mode='time-of-flight',\n", + " radiation_probe='neutron',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "16", + "metadata": {}, + "source": [ + "#### Inspect Measured Data\n", + "\n", + "After creating the experiment, we can examine the measured data. The\n", + "measured data consists of a diffraction pattern having time-of-flight\n", + "(TOF) values and corresponding intensities. The TOF values are given\n", + "in microseconds (μs), and the intensities are in arbitrary units.\n", + "\n", + "The data is stored in XYE format, a simple text format containing\n", + "three columns: TOF, intensity, and intensity error (if available)." + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#measured-data-category)\n", + "for more details about the measured data and its format.\n", + "\n", + "To visualize the measured data, we can use the `plot_meas` method of\n", + "the project. Before plotting, we need to set the plotting engine to\n", + "'plotly', which provides interactive visualizations." + ] + }, + { + "cell_type": "markdown", + "id": "18", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://easyscience.github.io/diffraction-lib/user-guide/first-steps/#supported-plotters)\n", + "for more details about setting the plotting engine." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19", + "metadata": {}, + "outputs": [], + "source": [ + "# Keep the auto-selected engine. Alternatively, you can uncomment the\n", + "# line below to explicitly set the engine to the required one.\n", + "# project.plotter.engine = 'plotly'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.plot_meas(expt_name='sim_si')" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "If you zoom in on the highest TOF peak (around 120,000 μs), you will\n", + "notice that it has a broad and unusual shape. This distortion, along\n", + "with additional effects on the low TOF peaks, is most likely an\n", + "artifact related to the simplifications made during the simulation\n", + "and/or reduction process and is currently under investigation.\n", + "However, this is outside the scope of this school. Therefore, we will\n", + "simply exclude both the low and high TOF regions from the analysis by\n", + "adding an excluded regions to the experiment.\n", + "\n", + "In real experiments, it is often necessary to exclude certain regions\n", + "from the measured data. For example, the direct beam can significantly\n", + "increase the background at very low angles, making those parts of the\n", + "diffractogram unreliable. Additionally, sample environment components\n", + "may introduce unwanted peaks. In such cases, excluding specific\n", + "regions is often simpler and more effective than modeling them with an\n", + "additional sample phase." + ] + }, + { + "cell_type": "markdown", + "id": "22", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#excluded-regions-category)\n", + "for more details about excluding regions from the measured data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.experiments['sim_si'].excluded_regions.create(id='1', start=0, end=55000)\n", + "project_1.experiments['sim_si'].excluded_regions.create(id='2', start=105500, end=200000)" + ] + }, + { + "cell_type": "markdown", + "id": "24", + "metadata": {}, + "source": [ + "To visualize the effect of excluding the high TOF region, we can plot\n", + "the measured data again. The excluded region will be omitted from the\n", + "plot and is not used in the fitting process." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.plot_meas(expt_name='sim_si')" + ] + }, + { + "cell_type": "markdown", + "id": "26", + "metadata": {}, + "source": [ + "#### Set Instrument Parameters\n", + "\n", + "After the experiment is created and measured data is loaded, we need\n", + "to set the instrument parameters.\n", + "\n", + "In this type of experiment, the instrument parameters define how the\n", + "measured data is converted between d-spacing and time-of-flight (TOF)\n", + "during the data reduction process as well as the angular position of\n", + "the detector. So, we put values based on those from the reduction.\n", + "These values can be found in the header of the corresponding .XYE\n", + "file. Their names are `two_theta` and `DIFC`, which stand for the\n", + "two-theta angle and the linear conversion factor from d-spacing to\n", + "TOF, respectively.\n", + "\n", + "You can set them manually, but it is more convenient to use the\n", + "`get_value_from_xye_header` function from the EasyDiffraction library." + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#instrument-category)\n", + "for more details about the instrument parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.experiments['sim_si'].instrument.setup_twotheta_bank = ed.get_value_from_xye_header(\n", + " si_xye_path, 'two_theta'\n", + ")\n", + "project_1.experiments['sim_si'].instrument.calib_d_to_tof_linear = ed.get_value_from_xye_header(\n", + " si_xye_path, 'DIFC'\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "Before proceeding, let's take a quick look at the concept of\n", + "parameters in EasyDiffraction, which is similar to the parameter\n", + "concept in EasyScience. The current version of EasyDiffraction is\n", + "transitioning to reuse the parameter system from EasyScience.\n", + "\n", + "That is, every parameter is an object, which has different attributes,\n", + "such as `value`, `units`, etc. To display the parameter of interest,\n", + "you can simply print the parameter object.\n", + "\n", + "For example, to display the linear conversion factor from d-spacing to\n", + "TOF, which is the `calib_d_to_tof_linear` parameter, you can do the\n", + "following:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "print(project_1.experiments['sim_si'].instrument.calib_d_to_tof_linear)" + ] + }, + { + "cell_type": "markdown", + "id": "31", + "metadata": {}, + "source": [ + "The `value` attribute represents the current value of the parameter as\n", + "a float. You can access it directly by using the `value` attribute of\n", + "the parameter. This is useful when you want to use the parameter value\n", + "in calculations or when you want to assign it to another parameter.\n", + "For example, to get only the value of the same parameter as floating\n", + "point number, but not the whole object, you can do the following:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "print(project_1.experiments['sim_si'].instrument.calib_d_to_tof_linear.value)" + ] + }, + { + "cell_type": "markdown", + "id": "33", + "metadata": {}, + "source": [ + "Note that to set the value of the parameter, you can simply assign a\n", + "new value to the parameter object without using the `value` attribute,\n", + "as we did above." + ] + }, + { + "cell_type": "markdown", + "id": "34", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/parameters/)\n", + "for more details about parameters in EasyDiffraction and their\n", + "attributes." + ] + }, + { + "cell_type": "markdown", + "id": "35", + "metadata": {}, + "source": [ + "#### Set Peak Profile Parameters\n", + "\n", + "The next set of parameters is needed to define the peak profile used\n", + "in the fitting process. The peak profile describes the shape of the\n", + "diffraction peaks. They include parameters for the broadening and\n", + "asymmetry of the peaks.\n", + "\n", + "There are several commonly used peak profile functions:\n", + "- **Gaussian**: Describes peaks with a symmetric bell-shaped curve,\n", + " often used when instrumental broadening dominates. [Click for more\n", + " details.](https://mantidproject.github.io/docs-versioned/v6.1.0/fitting/fitfunctions/Gaussian.html)\n", + "- **Lorentzian**: Produces narrower central peaks with longer tails,\n", + " frequently used to model size broadening effects. [Click for more\n", + " details.](https://mantidproject.github.io/docs-versioned/v6.1.0/fitting/fitfunctions/Lorentzian.html)\n", + "- **Pseudo-Voigt**: A linear combination of Gaussian and Lorentzian\n", + " components, providing flexibility to represent real diffraction\n", + " peaks. [Click for more\n", + " details.](https://mantidproject.github.io/docs-versioned/v6.1.0/fitting/fitfunctions/PseudoVoigt.html)\n", + "- **Pseudo-Voigt convoluted with Ikeda-Carpenter**: Incorporates the\n", + " asymmetry introduced by the neutron pulse shape in time-of-flight\n", + " instruments. This is a common choice for TOF neutron powder\n", + " diffraction data. [Click for more\n", + " details.](https://docs.mantidproject.org/v6.1.0/fitting/fitfunctions/IkedaCarpenterPV.html)\n", + "\n", + "Here, we use a pseudo-Voigt peak profile function with Ikeda-Carpenter\n", + "asymmetry.\n", + "\n", + "The parameter values are typically determined experimentally on the\n", + "same instrument and under the same configuration as the data being\n", + "analyzed, using measurements of a standard sample. In our case, the Si\n", + "sample serves as this standard reference. We will refine the peak\n", + "profile parameters here, and these refined values will be used as\n", + "starting points for the more complex fit in the next part of the\n", + "notebook. For this initial fit, we will provide reasonable physical\n", + "guesses as starting values." + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#peak-category)\n", + "for more details about the peak profile types." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.experiments['sim_si'].peak_profile_type = 'pseudo-voigt * ikeda-carpenter'\n", + "project_1.experiments['sim_si'].peak.broad_gauss_sigma_0 = 69498\n", + "project_1.experiments['sim_si'].peak.broad_gauss_sigma_1 = -55578\n", + "project_1.experiments['sim_si'].peak.broad_gauss_sigma_2 = 14560\n", + "project_1.experiments['sim_si'].peak.broad_mix_beta_0 = 0.0019\n", + "project_1.experiments['sim_si'].peak.broad_mix_beta_1 = 0.0137\n", + "project_1.experiments['sim_si'].peak.asym_alpha_0 = -0.0055\n", + "project_1.experiments['sim_si'].peak.asym_alpha_1 = 0.0147" + ] + }, + { + "cell_type": "markdown", + "id": "38", + "metadata": {}, + "source": [ + "#### Set Background\n", + "\n", + "The background of the diffraction pattern represents the portion of\n", + "the pattern that is not related to the crystal structure of the\n", + "sample. It's rather represents noise and other sources of scattering\n", + "that can affect the measured intensities. This includes contributions\n", + "from the instrument, the sample holder, the sample environment, and\n", + "other sources of incoherent scattering.\n", + "\n", + "The background can be modeled in various ways. In this example, we\n", + "will use a simple line segment background, which is a common approach\n", + "for powder diffraction data. The background intensity at any point is\n", + "defined by linear interpolation between neighboring points. The\n", + "background points are selected to span the range of the diffraction\n", + "pattern while avoiding the peaks.\n", + "\n", + "We will add several background points at specific TOF values (in μs)\n", + "and corresponding intensity values. These points are chosen to\n", + "represent the background level in the diffraction pattern free from\n", + "any peaks.\n", + "\n", + "The background points are added using the `add` method of the\n", + "`background` object. The `x` parameter represents the TOF value, and\n", + "the `y` parameter represents the intensity value at that TOF.\n", + "\n", + "Let's set all the background points at a constant value of 0.01, which\n", + "can be roughly estimated by the eye, and we will refine them later\n", + "during the fitting process." + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#background-category)\n", + "for more details about the background and its types." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.experiments['sim_si'].background_type = 'line-segment'\n", + "project_1.experiments['sim_si'].background.create(id='1', x=50000, y=0.01)\n", + "project_1.experiments['sim_si'].background.create(id='2', x=60000, y=0.01)\n", + "project_1.experiments['sim_si'].background.create(id='3', x=70000, y=0.01)\n", + "project_1.experiments['sim_si'].background.create(id='4', x=80000, y=0.01)\n", + "project_1.experiments['sim_si'].background.create(id='5', x=90000, y=0.01)\n", + "project_1.experiments['sim_si'].background.create(id='6', x=100000, y=0.01)\n", + "project_1.experiments['sim_si'].background.create(id='7', x=110000, y=0.01)" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "### 🧩 Create a Structure – Si\n", + "\n", + "After setting up the experiment, we need to create a structure that\n", + "describes the crystal structure of the sample being analyzed.\n", + "\n", + "In this case, we will create a structure for silicon (Si) with a\n", + "cubic crystal structure. The structure contains information about\n", + "the space group, lattice parameters, atomic positions of the atoms in\n", + "the unit cell, atom types, occupancies and atomic displacement\n", + "parameters. The structure is essential for the fitting process, as\n", + "it is used to calculate the expected diffraction pattern.\n", + "\n", + "EasyDiffraction refines the crystal structure of the sample, but does\n", + "not solve it. Therefore, we need a good starting point with reasonable\n", + "structural parameters.\n", + "\n", + "Here, we define the Si structure as a cubic structure. As this is a\n", + "cubic structure, we only need to define the single lattice parameter,\n", + "which is the length of the unit cell edge. The Si crystal structure\n", + "has a single atom in the unit cell, which is located at the origin (0,\n", + "0, 0) of the unit cell. The symmetry of this site is defined by the\n", + "Wyckoff letter 'a'. The atomic displacement parameter defines the\n", + "thermal vibrations of the atoms in the unit cell and is presented as\n", + "an isotropic parameter (B_iso).\n", + "\n", + "Sometimes, the initial crystal structure parameters can be obtained\n", + "from one of the crystallographic databases, like for example the\n", + "Crystallography Open Database (COD). In this case, we use the COD\n", + "entry for silicon as a reference for the initial crystal structure\n", + "model: https://www.crystallography.net/cod/4507226.html\n", + "\n", + "Usually, the crystal structure parameters are provided in a CIF file\n", + "format, which is a standard format for crystallographic data. An\n", + "example of a CIF file for silicon is shown below. The CIF file\n", + "contains the space group information, unit cell parameters, and atomic\n", + "positions." + ] + }, + { + "cell_type": "markdown", + "id": "42", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/data-format/)\n", + "for more details about the CIF format and its use in EasyDiffraction." + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "```\n", + "data_si\n", + "\n", + "_space_group.name_H-M_alt \"F d -3 m\"\n", + "_space_group.IT_coordinate_system_code 2\n", + "\n", + "_cell.length_a 5.43\n", + "_cell.length_b 5.43\n", + "_cell.length_c 5.43\n", + "_cell.angle_alpha 90.0\n", + "_cell.angle_beta 90.0\n", + "_cell.angle_gamma 90.0\n", + "\n", + "loop_\n", + "_atom_site.label\n", + "_atom_site.type_symbol\n", + "_atom_site.fract_x\n", + "_atom_site.fract_y\n", + "_atom_site.fract_z\n", + "_atom_site.wyckoff_letter\n", + "_atom_site.occupancy\n", + "_atom_site.ADP_type\n", + "_atom_site.B_iso_or_equiv\n", + "Si Si 0 0 0 a 1.0 Biso 0.89\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "44", + "metadata": {}, + "source": [ + "As with adding the experiment in the previous step, we will create a\n", + "default structure and then modify its parameters to match the Si\n", + "structure." + ] + }, + { + "cell_type": "markdown", + "id": "45", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/structure/)\n", + "for more details about structures and their purpose in the data\n", + "analysis workflow." + ] + }, + { + "cell_type": "markdown", + "id": "46", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.structures.create(name='si')" + ] + }, + { + "cell_type": "markdown", + "id": "48", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "markdown", + "id": "49", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/structure/#space-group-category)\n", + "for more details about the space group." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.structures['si'].space_group.name_h_m = 'F d -3 m'\n", + "project_1.structures['si'].space_group.it_coordinate_system_code = '2'" + ] + }, + { + "cell_type": "markdown", + "id": "51", + "metadata": {}, + "source": [ + "#### Set Lattice Parameters" + ] + }, + { + "cell_type": "markdown", + "id": "52", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/structure/#cell-category)\n", + "for more details about the unit cell parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.structures['si'].cell.length_a = 5.43" + ] + }, + { + "cell_type": "markdown", + "id": "54", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "markdown", + "id": "55", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/structure/#atom-sites-category)\n", + "for more details about the atom sites category." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.structures['si'].atom_sites.create(\n", + " label='Si',\n", + " type_symbol='Si',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.89,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "57", + "metadata": {}, + "source": [ + "### 🔗 Assign Structure to Experiment\n", + "\n", + "Now we need to assign, or link, this structure to the experiment\n", + "created above. This linked crystallographic phase will be used to\n", + "calculate the expected diffraction pattern based on the crystal\n", + "structure defined in the structure." + ] + }, + { + "cell_type": "markdown", + "id": "58", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/experiment/#linked-phases-category)\n", + "for more details about linking a structure to an experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.experiments['sim_si'].linked_phases.create(id='si', scale=1.0)" + ] + }, + { + "cell_type": "markdown", + "id": "60", + "metadata": {}, + "source": [ + "### 🚀 Analyze and Fit the Data\n", + "\n", + "After setting up the experiment and structure, we can now analyze\n", + "the measured diffraction pattern and perform the fit. Building on the\n", + "analogies from the EasyScience library and the previous notebooks, we\n", + "can say that all the parameters we introduced earlier — those defining\n", + "the structure (crystal structure parameters) and the experiment\n", + "(instrument, background, and peak profile parameters) — together form\n", + "the complete set of parameters that can be refined during the fitting\n", + "process.\n", + "\n", + "Unlike in the previous analysis notebooks, we will not create a\n", + "**math_model** object here. The mathematical model used to calculate\n", + "the expected diffraction pattern is already defined in the library and\n", + "will be applied automatically during the fitting process." + ] + }, + { + "cell_type": "markdown", + "id": "61", + "metadata": { + "title": "**Reminder:**" + }, + "source": [ + "\n", + "The fitting process involves comparing the measured diffraction\n", + "pattern with the calculated diffraction pattern based on the crystal\n", + "structure and instrument parameters. The goal is to adjust the\n", + "parameters of the structure and the experiment to minimize the\n", + "difference between the measured and calculated diffraction patterns.\n", + "This is done by refining the parameters of the structure and the\n", + "instrument settings to achieve a better fit." + ] + }, + { + "cell_type": "markdown", + "id": "62", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/analysis/#minimization-optimization)\n", + "for more details about the fitting process in EasyDiffraction." + ] + }, + { + "cell_type": "markdown", + "id": "63", + "metadata": {}, + "source": [ + "#### Set Fit Parameters\n", + "\n", + "To perform the fit, we need to specify the refinement parameters.\n", + "These are the parameters that will be adjusted during the fitting\n", + "process to minimize the difference between the measured and calculated\n", + "diffraction patterns. This is done by setting the `free` attribute of\n", + "the corresponding parameters to `True`.\n", + "\n", + "Note: setting `param.free = True` is equivalent to using `param.fixed\n", + "= False` in the EasyScience library.\n", + "\n", + "We will refine the scale factor of the Si phase, the intensities of\n", + "the background points as well as the peak profile parameters. The\n", + "structure parameters of the Si phase will not be refined, as this\n", + "sample is considered a reference sample with known parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.experiments['sim_si'].linked_phases['si'].scale.free = True\n", + "\n", + "for line_segment in project_1.experiments['sim_si'].background:\n", + " line_segment.y.free = True\n", + "\n", + "project_1.experiments['sim_si'].peak.broad_gauss_sigma_0.free = True\n", + "project_1.experiments['sim_si'].peak.broad_gauss_sigma_1.free = True\n", + "project_1.experiments['sim_si'].peak.broad_gauss_sigma_2.free = True\n", + "project_1.experiments['sim_si'].peak.broad_mix_beta_0.free = True\n", + "project_1.experiments['sim_si'].peak.broad_mix_beta_1.free = True\n", + "project_1.experiments['sim_si'].peak.asym_alpha_0.free = True\n", + "project_1.experiments['sim_si'].peak.asym_alpha_1.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "65", + "metadata": {}, + "source": [ + "#### Show Free Parameters\n", + "\n", + "We can check which parameters are free to be refined by calling the\n", + "`show_free_params` method of the `analysis` object of the project." + ] + }, + { + "cell_type": "markdown", + "id": "66", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://easyscience.github.io/diffraction-lib/user-guide/first-steps/#available-parameters)\n", + "for more details on how to\n", + "- show all parameters of the project,\n", + "- show all fittable parameters, and\n", + "- show only free parameters of the project." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "68", + "metadata": {}, + "source": [ + "#### Visualize Diffraction Patterns\n", + "\n", + "Before performing the fit, we can visually compare the measured\n", + "diffraction pattern with the calculated diffraction pattern based on\n", + "the initial parameters of the structure and the instrument. This\n", + "provides an indication of how well the initial parameters match the\n", + "measured data. The `plot_meas_vs_calc` method of the project allows\n", + "this comparison." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.plot_meas_vs_calc(expt_name='sim_si')" + ] + }, + { + "cell_type": "markdown", + "id": "70", + "metadata": {}, + "source": [ + "#### Run Fitting\n", + "\n", + "We can now perform the fit using the `fit` method of the `analysis`\n", + "object of the project." + ] + }, + { + "cell_type": "markdown", + "id": "71", + "metadata": { + "tags": [ + "doc-link" + ] + }, + "source": [ + "📖 See\n", + "[documentation](https://docs.easydiffraction.org/lib/user-guide/analysis-workflow/analysis/#perform-fit)\n", + "for more details about the fitting process." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.analysis.fit()\n", + "project_1.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "73", + "metadata": {}, + "source": [ + "#### Check Fit Results\n", + "\n", + "You can see that the agreement between the measured and calculated\n", + "diffraction patterns is now much improved and that the intensities of\n", + "the calculated peaks align much better with the measured peaks. To\n", + "check the quality of the fit numerically, we can look at the\n", + "goodness-of-fit χ² value and the reliability R-factors. The χ² value\n", + "is a measure of how well the calculated diffraction pattern matches\n", + "the measured pattern, and it is calculated as the sum of the squared\n", + "differences between the measured and calculated intensities, divided\n", + "by the number of data points. Ideally, the χ² value should be close to\n", + "1, indicating a good fit." + ] + }, + { + "cell_type": "markdown", + "id": "74", + "metadata": {}, + "source": [ + "#### Visualize Fit Results\n", + "\n", + "After the fit is completed, we can plot the comparison between the\n", + "measured and calculated diffraction patterns again to see how well the\n", + "fit improved the agreement between the two. The calculated diffraction\n", + "pattern is now based on the refined parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.plot_meas_vs_calc(expt_name='sim_si')" + ] + }, + { + "cell_type": "markdown", + "id": "76", + "metadata": {}, + "source": [ + "#### TOF vs d-spacing\n", + "\n", + "The diffraction pattern is typically analyzed and plotted in the\n", + "time-of-flight (TOF) axis, which represents the time it takes for\n", + "neutrons to travel from the sample to the detector. However, it is\n", + "sometimes more convenient to visualize the diffraction pattern in the\n", + "d-spacing axis, which represents the distance between planes in the\n", + "crystal lattice.\n", + "\n", + "The conversion from d-spacing to TOF was already introduced in the\n", + "data reduction notebook. As a reminder, the two are related through\n", + "the instrument calibration parameters according to the equation:\n", + "\n", + "$$ \\text{TOF} = \\text{offset} + \\text{linear} \\cdot d + \\text{quad}\n", + "\\cdot d^{2}, $$\n", + "\n", + "where `offset`, `linear`, and `quad` are calibration parameters.\n", + "\n", + "In our case, only the `linear` term is used (the\n", + "`calib_d_to_tof_linear` parameter we set earlier). The `offset` and\n", + "`quad` terms were not part of the data reduction and are therefore set\n", + "to 0 by default.\n", + "\n", + "The `plot_meas_vs_calc` method of the project allows us to plot the\n", + "measured and calculated diffraction patterns in the d-spacing axis by\n", + "setting the `d_spacing` parameter to `True`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.plot_meas_vs_calc(expt_name='sim_si', x='d_spacing')" + ] + }, + { + "cell_type": "markdown", + "id": "78", + "metadata": {}, + "source": [ + "As you can see, the calculated diffraction pattern now matches the\n", + "measured pattern much more closely. Typically, additional experimental\n", + "parameters are included in the refinement process to further improve\n", + "the fit. In this example, the structural parameters are not refined\n", + "because the Si crystal structure is a well-known standard reference\n", + "used to calibrate both the instrument and the experimental setup. The\n", + "refined experimental parameters obtained here will then be applied\n", + "when fitting the crystal structures of other materials.\n", + "\n", + "In the next part of the notebook, we will move to a more advanced case\n", + "and fit a more complex crystal structure: La₀.₅Ba₀.₅CoO₃ (LBCO).\n", + "\n", + "#### Save Project\n", + "\n", + "Before moving on, we can save the project to disk for later use. This\n", + "will preserve the entire project structure, including experiments,\n", + "structures, and fitting results. The project is saved into a\n", + "directory specified by the `dir_path` attribute of the project object." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79", + "metadata": {}, + "outputs": [], + "source": [ + "project_1.save_as(dir_path='powder_diffraction_Si')" + ] + }, + { + "cell_type": "markdown", + "id": "80", + "metadata": {}, + "source": [ + "## 💪 Exercise: Complex Fit – LBCO\n", + "\n", + "Now that you have a basic understanding of the fitting process, we\n", + "will undertake a more complex fit of the La₀.₅Ba₀.₅CoO₃ (LBCO) crystal\n", + "structure using simulated powder diffraction data from the data\n", + "reduction notebook.\n", + "\n", + "You can use the same approach as in the previous part of the notebook,\n", + "but this time we will refine a more complex crystal structure LBCO\n", + "with multiple atoms in the unit cell.\n", + "\n", + "### 📦 Exercise 1: Create a Project\n", + "\n", + "Create a new project for the LBCO fit." + ] + }, + { + "cell_type": "markdown", + "id": "81", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "82", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can use the same approach as in the previous part of the notebook,\n", + "but this time we will create a new project for the LBCO fit." + ] + }, + { + "cell_type": "markdown", + "id": "83", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2 = ed.Project(name='main')\n", + "project_2.info.title = 'La0.5Ba0.5CoO3 Fit'\n", + "project_2.info.description = 'Fitting simulated powder diffraction pattern of La0.5Ba0.5CoO3.'" + ] + }, + { + "cell_type": "markdown", + "id": "85", + "metadata": {}, + "source": [ + "### 🔬 Exercise 2: Define an Experiment\n", + "\n", + "#### Exercise 2.1: Create an Experiment\n", + "\n", + "Create an experiment within the new project and load the reduced\n", + "diffraction pattern for LBCO." + ] + }, + { + "cell_type": "markdown", + "id": "86", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "87", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can use the same approach as in the previous part of the notebook,\n", + "but this time you need to use the data file for LBCO." + ] + }, + { + "cell_type": "markdown", + "id": "88", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "data_dir = 'data'\n", + "file_name = 'reduced_LBCO.xye'\n", + "lbco_xye_path = f'{data_dir}/{file_name}'\n", + "\n", + "# Uncomment the following line if your data reduction failed and the\n", + "# reduced data file is missing.\n", + "lbco_xye_path = ed.download_data(id=18, destination=data_dir)\n", + "\n", + "project_2.experiments.add_from_data_path(\n", + " name='sim_lbco',\n", + " data_path=lbco_xye_path,\n", + " sample_form='powder',\n", + " beam_mode='time-of-flight',\n", + " radiation_probe='neutron',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "90", + "metadata": {}, + "source": [ + "#### Exercise 2.1: Inspect Measured Data\n", + "\n", + "Check the measured data of the LBCO experiment. Are there any peaks\n", + "with the shape similar to those excluded in the Si fit? If so, exclude\n", + "them from this analysis as well." + ] + }, + { + "cell_type": "markdown", + "id": "91", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "92", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can use the `plot_meas` method of the project to visualize the\n", + "measured diffraction pattern. You can also use the `excluded_regions`\n", + "attribute of the experiment to exclude specific regions from the\n", + "analysis as we did in the previous part of the notebook." + ] + }, + { + "cell_type": "markdown", + "id": "93", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.plot_meas(expt_name='sim_lbco')\n", + "\n", + "project_2.experiments['sim_lbco'].excluded_regions.create(id='1', start=0, end=55000)\n", + "project_2.experiments['sim_lbco'].excluded_regions.create(id='2', start=105500, end=200000)\n", + "\n", + "project_2.plot_meas(expt_name='sim_lbco')" + ] + }, + { + "cell_type": "markdown", + "id": "95", + "metadata": {}, + "source": [ + "#### Exercise 2.2: Set Instrument Parameters\n", + "\n", + "Set the instrument parameters for the LBCO experiment." + ] + }, + { + "cell_type": "markdown", + "id": "96", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "97", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the values from the data reduction process for the LBCO and\n", + "follow the same approach as in the previous part of the notebook." + ] + }, + { + "cell_type": "markdown", + "id": "98", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "99", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.experiments['sim_lbco'].instrument.setup_twotheta_bank = ed.get_value_from_xye_header(\n", + " lbco_xye_path, 'two_theta'\n", + ")\n", + "project_2.experiments['sim_lbco'].instrument.calib_d_to_tof_linear = ed.get_value_from_xye_header(\n", + " lbco_xye_path, 'DIFC'\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "100", + "metadata": {}, + "source": [ + "#### Exercise 2.3: Set Peak Profile Parameters\n", + "\n", + "Set the peak profile parameters for the LBCO experiment." + ] + }, + { + "cell_type": "markdown", + "id": "101", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "102", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the values from the\n", + "previous part of the notebook. You can either manually copy the values\n", + "from the Si fit or use the `value` attribute of the parameters from\n", + "the Si experiment to set the initial values for the LBCO experiment.\n", + "This will help us to have a good starting point for the fit." + ] + }, + { + "cell_type": "markdown", + "id": "103", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "104", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "# # Create a reference to the peak profile parameters from the Si\n", + "sim_si_peak = project_1.experiments['sim_si'].peak\n", + "project_2.experiments['sim_lbco'].peak_profile_type = 'pseudo-voigt * ikeda-carpenter'\n", + "project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_0 = sim_si_peak.broad_gauss_sigma_0.value\n", + "project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_1 = sim_si_peak.broad_gauss_sigma_1.value\n", + "project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_2 = sim_si_peak.broad_gauss_sigma_2.value\n", + "project_2.experiments['sim_lbco'].peak.broad_mix_beta_0 = sim_si_peak.broad_mix_beta_0.value\n", + "project_2.experiments['sim_lbco'].peak.broad_mix_beta_1 = sim_si_peak.broad_mix_beta_1.value\n", + "project_2.experiments['sim_lbco'].peak.asym_alpha_0 = sim_si_peak.asym_alpha_0.value\n", + "project_2.experiments['sim_lbco'].peak.asym_alpha_1 = sim_si_peak.asym_alpha_1.value" + ] + }, + { + "cell_type": "markdown", + "id": "105", + "metadata": {}, + "source": [ + "#### Exercise 2.4: Set Background\n", + "\n", + "Set the background points for the LBCO experiment. What would you\n", + "suggest as the initial intensity value for the background points?" + ] + }, + { + "cell_type": "markdown", + "id": "106", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "107", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the same approach as in the previous part of the notebook, but\n", + "this time you need to set the background points for the LBCO\n", + "experiment. You can zoom in on the measured diffraction pattern to\n", + "determine the approximate background level." + ] + }, + { + "cell_type": "markdown", + "id": "108", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "109", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.experiments['sim_lbco'].background_type = 'line-segment'\n", + "project_2.experiments['sim_lbco'].background.create(id='1', x=50000, y=0.2)\n", + "project_2.experiments['sim_lbco'].background.create(id='2', x=60000, y=0.2)\n", + "project_2.experiments['sim_lbco'].background.create(id='3', x=70000, y=0.2)\n", + "project_2.experiments['sim_lbco'].background.create(id='4', x=80000, y=0.2)\n", + "project_2.experiments['sim_lbco'].background.create(id='5', x=90000, y=0.2)\n", + "project_2.experiments['sim_lbco'].background.create(id='6', x=100000, y=0.2)\n", + "project_2.experiments['sim_lbco'].background.create(id='7', x=110000, y=0.2)" + ] + }, + { + "cell_type": "markdown", + "id": "110", + "metadata": {}, + "source": [ + "### 🧩 Exercise 3: Define a Structure – LBCO\n", + "\n", + "The LBSO structure is not as simple as the Si one, as it contains\n", + "multiple atoms in the unit cell. It is not in COD, so we give you the\n", + "structural parameters in CIF format to create the structure.\n", + "\n", + "Note that those parameters are not necessarily the most accurate ones,\n", + "but they are a good starting point for the fit. The aim of the study\n", + "is to refine the LBCO lattice parameters." + ] + }, + { + "cell_type": "markdown", + "id": "111", + "metadata": {}, + "source": [ + "```\n", + "data_lbco\n", + "\n", + "_space_group.name_H-M_alt \"P m -3 m\"\n", + "_space_group.IT_coordinate_system_code 1\n", + "\n", + "_cell.length_a 3.89\n", + "_cell.length_b 3.89\n", + "_cell.length_c 3.89\n", + "_cell.angle_alpha 90.0\n", + "_cell.angle_beta 90.0\n", + "_cell.angle_gamma 90.0\n", + "\n", + "loop_\n", + "_atom_site.label\n", + "_atom_site.type_symbol\n", + "_atom_site.fract_x\n", + "_atom_site.fract_y\n", + "_atom_site.fract_z\n", + "_atom_site.wyckoff_letter\n", + "_atom_site.occupancy\n", + "_atom_site.ADP_type\n", + "_atom_site.B_iso_or_equiv\n", + "La La 0.0 0.0 0.0 a 0.5 Biso 0.95\n", + "Ba Ba 0.0 0.0 0.0 a 0.5 Biso 0.95\n", + "Co Co 0.5 0.5 0.5 b 1.0 Biso 0.80\n", + "O O 0.0 0.5 0.5 c 1.0 Biso 1.66\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "112", + "metadata": {}, + "source": [ + "Note that the `occupancy` of the La and Ba atoms is 0.5\n", + "and those atoms are located in the same position (0, 0, 0) in the unit\n", + "cell. This means that an extra attribute `occupancy` needs to be set\n", + "for those atoms later in the structure.\n", + "\n", + "We model the La/Ba site using the virtual crystal approximation. In\n", + "this approach, the scattering is taken as a weighted average of La and\n", + "Ba. This reproduces the average diffraction pattern well but does not\n", + "capture certain real-world effects.\n", + "\n", + "The edge cases are:\n", + "- **Random distribution**. La and Ba atoms are placed randomly. The\n", + " Bragg peaks still match the average structure, but the pattern also\n", + " shows extra background (diffuse scattering) between the peaks, but\n", + " this is usually neglected in the analysis.\n", + "- **Perfect ordering**. La and Ba arrange themselves in a regular\n", + " pattern, creating a larger repeating unit. This gives rise to extra\n", + " peaks (\"superlattice reflections\") and changes the intensity of\n", + " some existing peaks.\n", + "- **Virtual crystal approximation (our model)**. We replace the site\n", + " with a single \"virtual atom\" that averages La and Ba. This gives\n", + " the correct average Bragg peaks but leaves out the extra background\n", + " of the random case and the extra peaks of the ordered case." + ] + }, + { + "cell_type": "markdown", + "id": "113", + "metadata": {}, + "source": [ + "#### Exercise 3.1: Create Structure\n", + "\n", + "Add a structure for LBCO to the project. The structure\n", + "parameters will be set in the next exercises." + ] + }, + { + "cell_type": "markdown", + "id": "114", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "115", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can use the same approach as in the previous part of the notebook,\n", + "but this time you need to use the name corresponding to the LBCO\n", + "structure, e.g. 'lbco'." + ] + }, + { + "cell_type": "markdown", + "id": "116", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "117", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.structures.create(name='lbco')" + ] + }, + { + "cell_type": "markdown", + "id": "118", + "metadata": {}, + "source": [ + "#### Exercise 3.2: Set Space Group\n", + "\n", + "Set the space group for the LBCO structure." + ] + }, + { + "cell_type": "markdown", + "id": "119", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "120", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the space group name and IT coordinate system code from the CIF\n", + "data." + ] + }, + { + "cell_type": "markdown", + "id": "121", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "122", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.structures['lbco'].space_group.name_h_m = 'P m -3 m'\n", + "project_2.structures['lbco'].space_group.it_coordinate_system_code = '1'" + ] + }, + { + "cell_type": "markdown", + "id": "123", + "metadata": {}, + "source": [ + "#### Exercise 3.3: Set Lattice Parameters\n", + "\n", + "Set the lattice parameters for the LBCO structure." + ] + }, + { + "cell_type": "markdown", + "id": "124", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "125", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the lattice parameters from the CIF data." + ] + }, + { + "cell_type": "markdown", + "id": "126", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "127", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.structures['lbco'].cell.length_a = 3.88" + ] + }, + { + "cell_type": "markdown", + "id": "128", + "metadata": {}, + "source": [ + "#### Exercise 3.4: Set Atom Sites\n", + "\n", + "Set the atom sites for the LBCO structure." + ] + }, + { + "cell_type": "markdown", + "id": "129", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "130", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the atom sites from the CIF data. You can use the `add` method of\n", + "the `atom_sites` attribute of the structure to add the atom sites." + ] + }, + { + "cell_type": "markdown", + "id": "131", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "132", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.structures['lbco'].atom_sites.create(\n", + " label='La',\n", + " type_symbol='La',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.95,\n", + " occupancy=0.5,\n", + ")\n", + "project_2.structures['lbco'].atom_sites.create(\n", + " label='Ba',\n", + " type_symbol='Ba',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.95,\n", + " occupancy=0.5,\n", + ")\n", + "project_2.structures['lbco'].atom_sites.create(\n", + " label='Co',\n", + " type_symbol='Co',\n", + " fract_x=0.5,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='b',\n", + " b_iso=0.80,\n", + ")\n", + "project_2.structures['lbco'].atom_sites.create(\n", + " label='O',\n", + " type_symbol='O',\n", + " fract_x=0,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='c',\n", + " b_iso=1.66,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "133", + "metadata": {}, + "source": [ + "### 🔗 Exercise 4: Assign Structure to Experiment\n", + "\n", + "Now assign the LBCO structure to the experiment created above." + ] + }, + { + "cell_type": "markdown", + "id": "134", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "135", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the `linked_phases` attribute of the experiment to link the\n", + "crystal structure." + ] + }, + { + "cell_type": "markdown", + "id": "136", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "137", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.experiments['sim_lbco'].linked_phases.create(id='lbco', scale=1.0)" + ] + }, + { + "cell_type": "markdown", + "id": "138", + "metadata": {}, + "source": [ + "### 🚀 Exercise 5: Analyze and Fit the Data\n", + "\n", + "#### Exercise 5.1: Set Fit Parameters\n", + "\n", + "Select the initial set of parameters to be refined during the fitting\n", + "process." + ] + }, + { + "cell_type": "markdown", + "id": "139", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "140", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can start with the scale factor and the background points, as in\n", + "the Si fit." + ] + }, + { + "cell_type": "markdown", + "id": "141", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "142", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.experiments['sim_lbco'].linked_phases['lbco'].scale.free = True\n", + "\n", + "for line_segment in project_2.experiments['sim_lbco'].background:\n", + " line_segment.y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "143", + "metadata": {}, + "source": [ + "#### Exercise 5.2: Run Fitting\n", + "\n", + "Visualize the measured and calculated diffraction patterns before\n", + "fitting and then run the fitting process." + ] + }, + { + "cell_type": "markdown", + "id": "144", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "145", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the `plot_meas_vs_calc` method of the project to visualize the\n", + "measured and calculated diffraction patterns before fitting. Then, use\n", + "the `fit` method of the `analysis` object of the project to perform\n", + "the fitting process." + ] + }, + { + "cell_type": "markdown", + "id": "146", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "147", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.plot_meas_vs_calc(expt_name='sim_lbco')\n", + "\n", + "project_2.analysis.fit()\n", + "project_2.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "148", + "metadata": {}, + "source": [ + "#### Exercise 5.3: Find the Misfit in the Fit\n", + "\n", + "Visualize the measured and calculated diffraction patterns after the\n", + "fit. As you can see, the fit shows noticeable discrepancies. If you\n", + "zoom in on different regions of the pattern, you will observe that all\n", + "the calculated peaks are shifted to the left.\n", + "\n", + "What could be the reason for the misfit?" + ] + }, + { + "cell_type": "markdown", + "id": "149", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "150", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Consider the following options:\n", + "1. The conversion parameters from TOF to d-spacing are not correct.\n", + "2. The lattice parameters of the LBCO phase are not correct.\n", + "3. The peak profile parameters are not correct.\n", + "4. The background points are not correct." + ] + }, + { + "cell_type": "markdown", + "id": "151", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "markdown", + "id": "152", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "\n", + "1. ❌ The conversion parameters from TOF to d-spacing were set based\n", + "on the data reduction step. While they are specific to each dataset\n", + "and thus differ from those used for the Si data, the full reduction\n", + "workflow has already been validated with the Si fit. Therefore, they\n", + "are not the cause of the misfit in this case.\n", + "2. ✅ The lattice parameters of the LBCO phase were set based on the\n", + "CIF data, which is a good starting point, but they are not necessarily\n", + "as accurate as needed for the fit. The lattice parameters may need to\n", + "be refined.\n", + "3. ❌ The peak profile parameters do not change the position of the\n", + "peaks, but rather their shape.\n", + "4. ❌ The background points affect the background level, but not the\n", + "peak positions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "153", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.plot_meas_vs_calc(expt_name='sim_lbco')" + ] + }, + { + "cell_type": "markdown", + "id": "154", + "metadata": {}, + "source": [ + "#### Exercise 5.4: Refine the LBCO Lattice Parameter\n", + "\n", + "To improve the fit, refine the lattice parameters of the LBCO phase." + ] + }, + { + "cell_type": "markdown", + "id": "155", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "156", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "To achieve this, we will set the `free` attribute of the `length_a`\n", + "parameter of the LBCO cell to `True`.\n", + "\n", + "LBCO has a cubic crystal structure (space group `P m -3 m`), which\n", + "means that `length_b` and `length_c` are constrained to be equal to\n", + "`length_a`. Therefore, only `length_a` needs to be refined; the other\n", + "two will be updated automatically. All cell angles are fixed at 90°,\n", + "so they do not require refinement." + ] + }, + { + "cell_type": "markdown", + "id": "157", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "158", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.structures['lbco'].cell.length_a.free = True\n", + "\n", + "project_2.analysis.fit()\n", + "project_2.analysis.show_fit_results()\n", + "\n", + "project_2.plot_meas_vs_calc(expt_name='sim_lbco')" + ] + }, + { + "cell_type": "markdown", + "id": "159", + "metadata": {}, + "source": [ + "One of the main goals of this study was to refine the lattice\n", + "parameter of the LBCO phase. As shown in the updated fit results, the\n", + "overall fit has improved significantly, even though the change in cell\n", + "length is less than 1% of the initial value. This demonstrates how\n", + "even a small adjustment to the lattice parameter can have a\n", + "substantial impact on the quality of the fit." + ] + }, + { + "cell_type": "markdown", + "id": "160", + "metadata": {}, + "source": [ + "#### Exercise 5.5: Visualize the Fit Results in d-spacing\n", + "\n", + "Plot measured vs calculated diffraction patterns in d-spacing instead\n", + "of TOF." + ] + }, + { + "cell_type": "markdown", + "id": "161", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "162", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Use the `plot_meas_vs_calc` method of the project and set the\n", + "`d_spacing` parameter to `True`." + ] + }, + { + "cell_type": "markdown", + "id": "163", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "164", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing')" + ] + }, + { + "cell_type": "markdown", + "id": "165", + "metadata": {}, + "source": [ + "#### Exercise 5.6: Refine the Peak Profile Parameters\n", + "\n", + "As you can see, the fit is now relatively good and the peak positions\n", + "are much closer to the measured data.\n", + "\n", + "The peak profile parameters were not refined, and their starting\n", + "values were set based on the previous fit of the Si standard sample.\n", + "Although these starting values are reasonable and provide a good\n", + "starting point for the fit, they are not necessarily optimal for the\n", + "LBCO phase. This can be seen while inspecting the individual peaks in\n", + "the diffraction pattern. For example, the calculated curve does not\n", + "perfectly describe the peak at about 1.38 Å, as can be seen below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "166", + "metadata": {}, + "outputs": [], + "source": [ + "project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing', x_min=1.35, x_max=1.40)" + ] + }, + { + "cell_type": "markdown", + "id": "167", + "metadata": {}, + "source": [ + "The peak profile parameters are determined based on both the\n", + "instrument and the sample characteristics, so they can vary when\n", + "analyzing different samples on the same instrument. Therefore, it is\n", + "better to refine them as well.\n", + "\n", + "Select the peak profile parameters to be refined during the fitting\n", + "process." + ] + }, + { + "cell_type": "markdown", + "id": "168", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "169", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can set the `free` attribute of the peak profile parameters to\n", + "`True` to allow the fitting process to adjust them. You can use the\n", + "same approach as in the previous part of the notebook, but this time\n", + "you will refine the peak profile parameters of the LBCO phase." + ] + }, + { + "cell_type": "markdown", + "id": "170", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "171", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_0.free = True\n", + "project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_1.free = True\n", + "project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_2.free = True\n", + "project_2.experiments['sim_lbco'].peak.broad_mix_beta_0.free = True\n", + "project_2.experiments['sim_lbco'].peak.broad_mix_beta_1.free = True\n", + "project_2.experiments['sim_lbco'].peak.asym_alpha_0.free = True\n", + "project_2.experiments['sim_lbco'].peak.asym_alpha_1.free = True\n", + "\n", + "project_2.analysis.fit()\n", + "project_2.analysis.show_fit_results()\n", + "\n", + "project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing', x_min=1.35, x_max=1.40)" + ] + }, + { + "cell_type": "markdown", + "id": "172", + "metadata": {}, + "source": [ + "#### Exercise 5.7: Find Undefined Features\n", + "\n", + "After refining the lattice parameter and the peak profile parameters,\n", + "the fit is significantly improved, but inspect the diffraction pattern\n", + "again. Are you noticing anything undefined?" + ] + }, + { + "cell_type": "markdown", + "id": "173", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "174", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "While the fit is now significantly better, there are still some\n", + "unexplained peaks in the diffraction pattern. These peaks are not\n", + "accounted for by the LBCO phase. For example, if you zoom in on the\n", + "region around 1.6 Å (or 95,000 μs), you will notice that the rightmost\n", + "peak is not explained by the LBCO phase at all." + ] + }, + { + "cell_type": "markdown", + "id": "175", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "176", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing', x_min=1.53, x_max=1.7)" + ] + }, + { + "cell_type": "markdown", + "id": "177", + "metadata": {}, + "source": [ + "#### Exercise 5.8: Identify the Cause of the Unexplained Peaks\n", + "\n", + "Analyze the residual peaks that remain after refining the LBCO phase\n", + "and the peak-profile parameters. Based on their positions and\n", + "characteristics, decide which potential cause best explains the\n", + "misfit." + ] + }, + { + "cell_type": "markdown", + "id": "178", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "179", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Consider the following options:\n", + "1. The LBCO phase is not correctly modeled.\n", + "2. The LBCO phase is not the only phase present in the sample.\n", + "3. The data reduction process introduced artifacts.\n", + "4. The studied sample is not LBCO, but rather a different phase." + ] + }, + { + "cell_type": "markdown", + "id": "180", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "markdown", + "id": "181", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "1. ❌ In principle, this could be the case, as sometimes the presence\n", + "of extra peaks in the diffraction pattern can indicate lower symmetry\n", + "than the one used in the model, or that the model is not complete.\n", + "However, in this case, the LBCO phase is correctly modeled based on\n", + "the CIF data.\n", + "2. ✅ The unexplained peaks are due to the presence of an impurity\n", + "phase in the sample, which is not included in the current model.\n", + "3. ❌ The data reduction process is not likely to introduce such\n", + "specific peaks, as it is tested and verified in the previous part of\n", + "the notebook.\n", + "4. ❌ This could also be the case in real experiments, but in this\n", + "case, we know that the sample is LBCO, as it was simulated based on\n", + "the CIF data." + ] + }, + { + "cell_type": "markdown", + "id": "182", + "metadata": {}, + "source": [ + "#### Exercise 5.9: Identify the impurity phase\n", + "\n", + "Use the positions of the unexplained peaks to identify the most likely\n", + "secondary phase present in the sample." + ] + }, + { + "cell_type": "markdown", + "id": "183", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "184", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "Check the positions of the unexplained peaks in the diffraction\n", + "pattern. Compare them with the known diffraction patterns in the\n", + "previous part of the notebook." + ] + }, + { + "cell_type": "markdown", + "id": "185", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "markdown", + "id": "186", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "The unexplained peaks are likely due to the presence of a small amount\n", + "of Si in the LBCO sample. In real experiments, it might happen, e.g.,\n", + "because the sample holder was not cleaned properly after the Si\n", + "experiment.\n", + "\n", + "You can visalize both the patterns of the Si and LBCO phases to\n", + "confirm this hypothesis." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "187", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "project_1.plot_meas_vs_calc(expt_name='sim_si', x='d_spacing', x_min=1, x_max=1.7)\n", + "project_2.plot_meas_vs_calc(expt_name='sim_lbco', x='d_spacing', x_min=1, x_max=1.7)" + ] + }, + { + "cell_type": "markdown", + "id": "188", + "metadata": {}, + "source": [ + "#### Exercise 5.10: Create a Second Structure – Si as Impurity\n", + "\n", + "Create a second structure for the Si phase, which is the impurity\n", + "phase identified in the previous step. Link this structure to the\n", + "LBCO experiment." + ] + }, + { + "cell_type": "markdown", + "id": "189", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "190", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can use the same approach as in the previous part of the notebook,\n", + "but this time you need to create a structure for Si and link it to\n", + "the LBCO experiment." + ] + }, + { + "cell_type": "markdown", + "id": "191", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "192", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "# Set Space Group\n", + "project_2.structures.create(name='si')\n", + "project_2.structures['si'].space_group.name_h_m = 'F d -3 m'\n", + "project_2.structures['si'].space_group.it_coordinate_system_code = '2'\n", + "\n", + "# Set Lattice Parameters\n", + "project_2.structures['si'].cell.length_a = 5.43\n", + "\n", + "# Set Atom Sites\n", + "project_2.structures['si'].atom_sites.create(\n", + " label='Si',\n", + " type_symbol='Si',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.89,\n", + ")\n", + "\n", + "# Assign Structure to Experiment\n", + "project_2.experiments['sim_lbco'].linked_phases.create(id='si', scale=1.0)" + ] + }, + { + "cell_type": "markdown", + "id": "193", + "metadata": {}, + "source": [ + "#### Exercise 5.11: Refine the Scale of the Si Phase\n", + "\n", + "Visualize the measured diffraction pattern and the calculated\n", + "diffraction pattern. Check if the Si phase is contributing to the\n", + "calculated diffraction pattern. Refine the scale factor of the Si\n", + "phase to improve the fit." + ] + }, + { + "cell_type": "markdown", + "id": "194", + "metadata": {}, + "source": [ + "**Hint:**" + ] + }, + { + "cell_type": "markdown", + "id": "195", + "metadata": { + "tags": [ + "dmsc-school-hint" + ] + }, + "source": [ + "You can use the `plot_meas_vs_calc` method of the project to visualize\n", + "the patterns. Then, set the `free` attribute of the `scale` parameter\n", + "of the Si phase to `True` to allow the fitting process to adjust the\n", + "scale factor." + ] + }, + { + "cell_type": "markdown", + "id": "196", + "metadata": {}, + "source": [ + "**Solution:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "197", + "metadata": { + "tags": [ + "solution", + "hide-input" + ] + }, + "outputs": [], + "source": [ + "# Before optimizing the parameters, we can visualize the measured\n", + "# diffraction pattern and the calculated diffraction pattern based on\n", + "# the two phases: LBCO and Si.\n", + "project_2.plot_meas_vs_calc(expt_name='sim_lbco')\n", + "\n", + "# As you can see, the calculated pattern is now the sum of both phases,\n", + "# and Si peaks are visible in the calculated pattern. However, their\n", + "# intensities are much too high. Therefore, we need to refine the scale\n", + "# factor of the Si phase.\n", + "project_2.experiments['sim_lbco'].linked_phases['si'].scale.free = True\n", + "\n", + "# Now we can perform the fit with both phases included.\n", + "project_2.analysis.fit()\n", + "project_2.analysis.show_fit_results()\n", + "\n", + "# Let's plot the measured diffraction pattern and the calculated\n", + "# diffraction pattern both for the full range and for a zoomed-in region\n", + "# around the previously unexplained peak near 95,000 μs. The calculated\n", + "# pattern will be the sum of the two phases.\n", + "project_2.plot_meas_vs_calc(expt_name='sim_lbco')\n", + "project_2.plot_meas_vs_calc(expt_name='sim_lbco', x_min=88000, x_max=101000)" + ] + }, + { + "cell_type": "markdown", + "id": "198", + "metadata": {}, + "source": [ + "All previously unexplained peaks are now accounted for in the pattern,\n", + "and the fit is improved. Some discrepancies in the peak intensities\n", + "remain, but further improvements would require more advanced data\n", + "reduction and analysis, which are beyond the scope of this school.\n", + "\n", + "To review the analysis results, you can generate and print a summary\n", + "report using the `show_report()` method, as demonstrated in the cell\n", + "below. The report includes parameters related to the structure and\n", + "the experiment, such as the refined unit cell parameter `a` of LBCO.\n", + "\n", + "Information about the crystal or magnetic structure, along with\n", + "experimental details, fitting quality, and other relevant data, is\n", + "often submitted to crystallographic journals as part of a scientific\n", + "publication. It can also be deposited in crystallographic databases\n", + "when relevant." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "199", + "metadata": {}, + "outputs": [], + "source": [ + "project_2.summary.show_report()" + ] + }, + { + "cell_type": "markdown", + "id": "200", + "metadata": {}, + "source": [ + "Finally, we save the project to disk to preserve the current state of\n", + "the analysis." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "201", + "metadata": {}, + "outputs": [], + "source": [ + "project_2.save_as(dir_path='powder_diffraction_LBCO_Si')" + ] + }, + { + "cell_type": "markdown", + "id": "202", + "metadata": {}, + "source": [ + "#### Final Remarks\n", + "\n", + "In this part of the notebook, you learned how to use EasyDiffraction\n", + "to refine lattice parameters of a more complex crystal structure,\n", + "La₀.₅Ba₀.₅CoO₃ (LBCO).\n", + "\n", + "In real experiments, you might also refine\n", + "additional parameters, such as atomic positions, occupancies, and\n", + "atomic displacement factors, to achieve an even better fit. For our\n", + "purposes, we'll stop here, as the goal was to give you a starting\n", + "point for analyzing more complex crystal structures with\n", + "EasyDiffraction." + ] + }, + { + "cell_type": "markdown", + "id": "203", + "metadata": {}, + "source": [ + "## 🎁 Bonus\n", + "\n", + "Congratulations — you've now completed the diffraction data analysis\n", + "part of the DMSC Summer School!\n", + "\n", + "If you'd like to keep exploring, the EasyDiffraction library offers\n", + "many additional tutorials and examples on the official documentation\n", + "site: 👉 https://docs.easydiffraction.org/lib/tutorials/\n", + "\n", + "Besides the Python package, EasyDiffraction also comes with a\n", + "graphical user interface (GUI) that lets you perform similar analyses\n", + "without writing code. To be fair, it's not *quite* feature-complete\n", + "compared to the Python library yet — but we're working on it! 🚧\n", + "\n", + "If you prefer a point-and-click interface over coding, the GUI\n", + "provides a user-friendly way to analyze diffraction data. You can\n", + "download it as a standalone application here: 👉\n", + "https://easydiffraction.org\n", + "\n", + "We'd love to hear your feedback on EasyDiffraction — both the library\n", + "and the GUI! 💬" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "tags,title,-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-13.py b/docs/docs/tutorials/ed-13.py similarity index 100% rename from tutorials/ed-13.py rename to docs/docs/tutorials/ed-13.py diff --git a/docs/docs/tutorials/ed-14.ipynb b/docs/docs/tutorials/ed-14.ipynb new file mode 100644 index 00000000..25927d6a --- /dev/null +++ b/docs/docs/tutorials/ed-14.ipynb @@ -0,0 +1,319 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: Tb2TiO7, HEiDi\n", + "\n", + "Crystal structure refinement of Tb2TiO7 using single crystal neutron\n", + "diffraction data from HEiDi at FRM II." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Step 1: Define Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "# Create minimal project without name and description\n", + "project = ed.Project()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Step 2: Define Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "# Download CIF file from repository\n", + "structure_path = ed.download_data(id=20, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add_from_cif_path(structure_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.show_names()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "structure = project.structures['tbti']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites['Tb'].b_iso = 0.0\n", + "structure.atom_sites['Ti'].b_iso = 0.0\n", + "structure.atom_sites['O1'].b_iso = 0.0\n", + "structure.atom_sites['O2'].b_iso = 0.0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11", + "metadata": {}, + "outputs": [], + "source": [ + "structure.show_as_cif()" + ] + }, + { + "cell_type": "markdown", + "id": "12", + "metadata": {}, + "source": [ + "## Step 3: Define Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = ed.download_data(id=19, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_data_path(\n", + " name='heidi',\n", + " data_path=data_path,\n", + " sample_form='single crystal',\n", + " beam_mode='constant wavelength',\n", + " radiation_probe='neutron',\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "experiment = project.experiments['heidi'] # TODO: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.linked_crystal.id = 'tbti'\n", + "experiment.linked_crystal.scale = 1.0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.instrument.setup_wavelength = 0.793" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.extinction.mosaicity = 29820\n", + "experiment.extinction.radius = 30" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "## Step 4: Perform Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='heidi')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.linked_crystal.scale.free = True\n", + "experiment.extinction.radius.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.show_as_cif()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "# Start refinement. All parameters, which have standard uncertainties\n", + "# in the input CIF files, are refined by default.\n", + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "# Show fit results summary\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.show_as_cif()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.show_names()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='heidi')" + ] + }, + { + "cell_type": "markdown", + "id": "28", + "metadata": {}, + "source": [ + "## Step 5: Show Project Summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29", + "metadata": {}, + "outputs": [], + "source": [ + "project.summary.show_report()" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-14.py b/docs/docs/tutorials/ed-14.py similarity index 100% rename from tutorials/ed-14.py rename to docs/docs/tutorials/ed-14.py diff --git a/docs/docs/tutorials/ed-15.ipynb b/docs/docs/tutorials/ed-15.ipynb new file mode 100644 index 00000000..a426f2ee --- /dev/null +++ b/docs/docs/tutorials/ed-15.ipynb @@ -0,0 +1,296 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: Taurine, SENJU\n", + "\n", + "Crystal structure refinement of Taurine using time-of-flight single\n", + "crystal neutron diffraction data from SENJU at J-PARC." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Step 1: Define Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "# Create minimal project without name and description\n", + "project = ed.Project()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Step 2: Define Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "# Download CIF file from repository\n", + "structure_path = ed.download_data(id=21, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add_from_cif_path(structure_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.show_names()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "structure = project.structures['taurine']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "# structure.show_as_cif()" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Step 3: Define Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = ed.download_data(id=22, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_data_path(\n", + " name='senju',\n", + " data_path=data_path,\n", + " sample_form='single crystal',\n", + " beam_mode='time-of-flight',\n", + " radiation_probe='neutron',\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "experiment = project.experiments['senju']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.linked_crystal.id = 'taurine'\n", + "experiment.linked_crystal.scale = 1.0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.extinction.mosaicity = 1000.0\n", + "experiment.extinction.radius = 100.0" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "## Step 4: Perform Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='senju')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.linked_crystal.scale.free = True\n", + "experiment.extinction.radius.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "# experiment.show_as_cif()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "# Start refinement. All parameters, which have standard uncertainties\n", + "# in the input CIF files, are refined by default.\n", + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "# Show fit results summary\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "# experiment.show_as_cif()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.show_names()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='senju')" + ] + }, + { + "cell_type": "markdown", + "id": "26", + "metadata": {}, + "source": [ + "## Step 5: Show Project Summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27", + "metadata": {}, + "outputs": [], + "source": [ + "project.summary.show_report()" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-15.py b/docs/docs/tutorials/ed-15.py similarity index 100% rename from tutorials/ed-15.py rename to docs/docs/tutorials/ed-15.py diff --git a/docs/docs/tutorials/ed-16.ipynb b/docs/docs/tutorials/ed-16.ipynb new file mode 100644 index 00000000..688297e6 --- /dev/null +++ b/docs/docs/tutorials/ed-16.ipynb @@ -0,0 +1,623 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Joint Refinement: Si, Bragg + PDF\n", + "\n", + "This example demonstrates a joint refinement of the Si crystal\n", + "structure combining Bragg diffraction and pair distribution function\n", + "(PDF) analysis. The Bragg experiment uses time-of-flight neutron\n", + "powder diffraction data from SEPD at Argonne, while the PDF\n", + "experiment uses data from NOMAD at SNS. A single shared Si structure\n", + "is refined simultaneously against both datasets." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import ExperimentFactory\n", + "from easydiffraction import Project\n", + "from easydiffraction import StructureFactory\n", + "from easydiffraction import download_data" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Define Structure\n", + "\n", + "A single Si structure is shared between the Bragg and PDF\n", + "experiments. Structural parameters refined against both datasets\n", + "simultaneously.\n", + "\n", + "#### Create Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "structure = StructureFactory.from_scratch(name='si')" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "structure.space_group.name_h_m = 'F d -3 m'\n", + "structure.space_group.it_coordinate_system_code = '1'" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a = 5.42" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites.create(\n", + " label='Si',\n", + " type_symbol='Si',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.2,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Define Experiments\n", + "\n", + "Two experiments are defined: one for Bragg diffraction and one for\n", + "PDF analysis. Both are linked to the same Si structure.\n", + "\n", + "### Experiment 1: Bragg (SEPD, TOF)\n", + "\n", + "#### Download Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "bragg_data_path = download_data(id=7, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "bragg_expt = ExperimentFactory.from_data_path(\n", + " name='sepd', data_path=bragg_data_path, beam_mode='time-of-flight'\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "bragg_expt.instrument.setup_twotheta_bank = 144.845\n", + "bragg_expt.instrument.calib_d_to_tof_offset = -9.2\n", + "bragg_expt.instrument.calib_d_to_tof_linear = 7476.91\n", + "bragg_expt.instrument.calib_d_to_tof_quad = -1.54" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "bragg_expt.peak_profile_type = 'pseudo-voigt * ikeda-carpenter'\n", + "bragg_expt.peak.broad_gauss_sigma_0 = 5.0\n", + "bragg_expt.peak.broad_gauss_sigma_1 = 45.0\n", + "bragg_expt.peak.broad_gauss_sigma_2 = 1.0\n", + "bragg_expt.peak.broad_mix_beta_0 = 0.04221\n", + "bragg_expt.peak.broad_mix_beta_1 = 0.00946\n", + "bragg_expt.peak.asym_alpha_0 = 0.0\n", + "bragg_expt.peak.asym_alpha_1 = 0.5971" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "bragg_expt.background_type = 'line-segment'\n", + "for x in range(0, 35000, 5000):\n", + " bragg_expt.background.create(id=str(x), x=x, y=200)" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "bragg_expt.linked_phases.create(id='si', scale=13.0)" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "### Experiment 2: PDF (NOMAD, TOF)\n", + "\n", + "#### Download Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "pdf_data_path = download_data(id=5, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "25", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "pdf_expt = ExperimentFactory.from_data_path(\n", + " name='nomad',\n", + " data_path=pdf_data_path,\n", + " beam_mode='time-of-flight',\n", + " scattering_type='total',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "#### Set Peak Profile (PDF Parameters)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], + "source": [ + "pdf_expt.peak.damp_q = 0.02\n", + "pdf_expt.peak.broad_q = 0.02\n", + "pdf_expt.peak.cutoff_q = 35.0\n", + "pdf_expt.peak.sharp_delta_1 = 0.001\n", + "pdf_expt.peak.sharp_delta_2 = 4.0\n", + "pdf_expt.peak.damp_particle_diameter = 0" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "pdf_expt.linked_phases.create(id='si', scale=1.0)" + ] + }, + { + "cell_type": "markdown", + "id": "31", + "metadata": {}, + "source": [ + "## Define Project\n", + "\n", + "The project object manages the shared structure, both experiments,\n", + "and the analysis.\n", + "\n", + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()" + ] + }, + { + "cell_type": "markdown", + "id": "33", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add(structure)" + ] + }, + { + "cell_type": "markdown", + "id": "35", + "metadata": {}, + "source": [ + "#### Add Experiments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add(bragg_expt)\n", + "project.experiments.add(pdf_expt)" + ] + }, + { + "cell_type": "markdown", + "id": "37", + "metadata": {}, + "source": [ + "## Perform Analysis\n", + "\n", + "This section shows the joint analysis process. The calculator is\n", + "auto-resolved per experiment: CrysPy for Bragg, PDFfit for PDF.\n", + "\n", + "#### Set Fit Mode and Weights" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit_mode.mode = 'joint'\n", + "project.analysis.joint_fit_experiments.create(id='sepd', weight=0.7)\n", + "project.analysis.joint_fit_experiments.create(id='nomad', weight=0.3)" + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": {}, + "source": [ + "#### Set Minimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated (Before Fit)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', show_residual=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='nomad', show_residual=False)" + ] + }, + { + "cell_type": "markdown", + "id": "44", + "metadata": {}, + "source": [ + "#### Set Fitting Parameters\n", + "\n", + "Shared structural parameters are refined against both datasets\n", + "simultaneously." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a.free = True\n", + "structure.atom_sites['Si'].b_iso.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "46", + "metadata": {}, + "source": [ + "Bragg experiment parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], + "source": [ + "bragg_expt.linked_phases['si'].scale.free = True\n", + "bragg_expt.instrument.calib_d_to_tof_offset.free = True\n", + "bragg_expt.peak.broad_gauss_sigma_0.free = True\n", + "bragg_expt.peak.broad_gauss_sigma_1.free = True\n", + "bragg_expt.peak.broad_gauss_sigma_2.free = True\n", + "for point in bragg_expt.background:\n", + " point.y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "48", + "metadata": {}, + "source": [ + "PDF experiment parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "pdf_expt.linked_phases['si'].scale.free = True\n", + "pdf_expt.peak.damp_q.free = True\n", + "pdf_expt.peak.broad_q.free = True\n", + "pdf_expt.peak.sharp_delta_1.free = True\n", + "pdf_expt.peak.sharp_delta_2.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "50", + "metadata": {}, + "source": [ + "#### Show Free Parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "52", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "54", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated (After Fit)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', show_residual=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='nomad', show_residual=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-16.py b/docs/docs/tutorials/ed-16.py similarity index 100% rename from tutorials/ed-16.py rename to docs/docs/tutorials/ed-16.py diff --git a/docs/docs/tutorials/ed-2.ipynb b/docs/docs/tutorials/ed-2.ipynb new file mode 100644 index 00000000..0a4af0f3 --- /dev/null +++ b/docs/docs/tutorials/ed-2.ipynb @@ -0,0 +1,344 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: LBCO, HRPT\n", + "\n", + "This minimalistic example is designed to show how Rietveld refinement\n", + "can be performed when both the crystal structure and experiment are\n", + "defined directly in code. Only the experimentally measured data is\n", + "loaded from an external file.\n", + "\n", + "For this example, constant-wavelength neutron powder diffraction data\n", + "for La0.5Ba0.5CoO3 from HRPT at PSI is used.\n", + "\n", + "It does not contain any advanced features or options, and includes no\n", + "comments or explanations—these can be found in the other tutorials.\n", + "Default values are used for all parameters if not specified. Only\n", + "essential and self-explanatory code is provided.\n", + "\n", + "The example is intended for users who are already familiar with the\n", + "EasyDiffraction library and want to quickly get started with a simple\n", + "refinement. It is also useful for those who want to see what a\n", + "refinement might look like in code. For a more detailed explanation of\n", + "the code, please refer to the other tutorials." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Step 1: Define Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "project = ed.Project()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Step 2: Define Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.create(name='lbco')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "structure = project.structures['lbco']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure.space_group.name_h_m = 'P m -3 m'\n", + "structure.space_group.it_coordinate_system_code = '1'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a = 3.88" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites.create(\n", + " label='La',\n", + " type_symbol='La',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.5,\n", + " occupancy=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Ba',\n", + " type_symbol='Ba',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.5,\n", + " occupancy=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Co',\n", + " type_symbol='Co',\n", + " fract_x=0.5,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='b',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O',\n", + " type_symbol='O',\n", + " fract_x=0,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='c',\n", + " b_iso=0.5,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Step 3: Define Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = ed.download_data(id=3, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_data_path(\n", + " name='hrpt',\n", + " data_path=data_path,\n", + " sample_form='powder',\n", + " beam_mode='constant wavelength',\n", + " radiation_probe='neutron',\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "experiment = project.experiments['hrpt']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.instrument.setup_wavelength = 1.494\n", + "experiment.instrument.calib_twotheta_offset = 0.6" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.peak.broad_gauss_u = 0.1\n", + "experiment.peak.broad_gauss_v = -0.1\n", + "experiment.peak.broad_gauss_w = 0.1\n", + "experiment.peak.broad_lorentz_y = 0.1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.background.create(id='1', x=10, y=170)\n", + "experiment.background.create(id='2', x=30, y=170)\n", + "experiment.background.create(id='3', x=50, y=170)\n", + "experiment.background.create(id='4', x=110, y=170)\n", + "experiment.background.create(id='5', x=165, y=170)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.excluded_regions.create(id='1', start=0, end=5)\n", + "experiment.excluded_regions.create(id='2', start=165, end=180)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.linked_phases.create(id='lbco', scale=10.0)" + ] + }, + { + "cell_type": "markdown", + "id": "20", + "metadata": {}, + "source": [ + "## Step 4: Perform Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a.free = True\n", + "\n", + "structure.atom_sites['La'].b_iso.free = True\n", + "structure.atom_sites['Ba'].b_iso.free = True\n", + "structure.atom_sites['Co'].b_iso.free = True\n", + "structure.atom_sites['O'].b_iso.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "experiment.instrument.calib_twotheta_offset.free = True\n", + "\n", + "experiment.peak.broad_gauss_u.free = True\n", + "experiment.peak.broad_gauss_v.free = True\n", + "experiment.peak.broad_gauss_w.free = True\n", + "experiment.peak.broad_lorentz_y.free = True\n", + "\n", + "experiment.background['1'].y.free = True\n", + "experiment.background['2'].y.free = True\n", + "experiment.background['3'].y.free = True\n", + "experiment.background['4'].y.free = True\n", + "experiment.background['5'].y.free = True\n", + "\n", + "experiment.linked_phases['lbco'].scale.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-2.py b/docs/docs/tutorials/ed-2.py similarity index 100% rename from tutorials/ed-2.py rename to docs/docs/tutorials/ed-2.py diff --git a/docs/docs/tutorials/ed-3.ipynb b/docs/docs/tutorials/ed-3.ipynb new file mode 100644 index 00000000..0fadb404 --- /dev/null +++ b/docs/docs/tutorials/ed-3.ipynb @@ -0,0 +1,1805 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: LBCO, HRPT\n", + "\n", + "This example demonstrates how to use the EasyDiffraction API in a\n", + "simplified, user-friendly manner that closely follows the GUI workflow\n", + "for a Rietveld refinement of La0.5Ba0.5CoO3 crystal structure using\n", + "constant wavelength neutron powder diffraction data from HRPT at PSI.\n", + "\n", + "It is intended for users with minimal programming experience who want\n", + "to learn how to perform standard crystal structure fitting using\n", + "diffraction data. This script covers creating a project, adding\n", + "crystal structures and experiments, performing analysis, and refining\n", + "parameters.\n", + "\n", + "Only a single import of `easydiffraction` is required, and all\n", + "operations are performed through high-level components of the\n", + "`project` object, such as `project.structures`,\n", + "`project.experiments`, and `project.analysis`. The `project` object is\n", + "the main container for all information." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Step 1: Create a Project\n", + "\n", + "This section explains how to create a project and define its metadata." + ] + }, + { + "cell_type": "markdown", + "id": "4", + "metadata": {}, + "source": [ + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "project = ed.Project(name='lbco_hrpt')" + ] + }, + { + "cell_type": "markdown", + "id": "6", + "metadata": {}, + "source": [ + "#### Set Project Metadata" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "project.info.title = 'La0.5Ba0.5CoO3 at HRPT@PSI'\n", + "project.info.description = \"\"\"This project demonstrates a standard\n", + "refinement of La0.5Ba0.5CoO3, which crystallizes in a perovskite-type\n", + "structure, using neutron powder diffraction data collected in constant\n", + "wavelength mode at the HRPT diffractometer (PSI).\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "#### Show Project Metadata as CIF" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "project.info.show_as_cif()" + ] + }, + { + "cell_type": "markdown", + "id": "10", + "metadata": {}, + "source": [ + "#### Save Project\n", + "\n", + "When saving the project for the first time, you need to specify the\n", + "directory path. In the example below, the project is saved to a\n", + "temporary location defined by the system." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11", + "metadata": {}, + "outputs": [], + "source": [ + "project.save_as(dir_path='lbco_hrpt', temporary=True)" + ] + }, + { + "cell_type": "markdown", + "id": "12", + "metadata": {}, + "source": [ + "#### Set Up Data Plotter" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "Show supported plotting engines." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "project.plotter.show_supported_engines()" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "Show current plotting configuration." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "project.plotter.show_config()" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "Set plotting engine." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "# Keep the auto-selected engine. Alternatively, you can uncomment the\n", + "# line below to explicitly set the engine to the required one.\n", + "# project.plotter.engine = 'plotly'" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "## Step 2: Define Structure\n", + "\n", + "This section shows how to add structures and modify their\n", + "parameters." + ] + }, + { + "cell_type": "markdown", + "id": "20", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.create(name='lbco')" + ] + }, + { + "cell_type": "markdown", + "id": "22", + "metadata": {}, + "source": [ + "#### Show Defined Structures\n", + "\n", + "Show the names of the crystal structures added. These names are used\n", + "to access the structure using the syntax:\n", + "`project.structures[name]`. All structure parameters can be accessed\n", + "via the `project` object." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.show_names()" + ] + }, + { + "cell_type": "markdown", + "id": "24", + "metadata": {}, + "source": [ + "#### Set Space Group\n", + "\n", + "Modify the default space group parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].space_group.name_h_m = 'P m -3 m'\n", + "project.structures['lbco'].space_group.it_coordinate_system_code = '1'" + ] + }, + { + "cell_type": "markdown", + "id": "26", + "metadata": {}, + "source": [ + "#### Set Unit Cell\n", + "\n", + "Modify the default unit cell parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].cell.length_a = 3.88" + ] + }, + { + "cell_type": "markdown", + "id": "28", + "metadata": {}, + "source": [ + "#### Set Atom Sites\n", + "\n", + "Add atom sites to the structure." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].atom_sites.create(\n", + " label='La',\n", + " type_symbol='La',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.5,\n", + " occupancy=0.5,\n", + ")\n", + "project.structures['lbco'].atom_sites.create(\n", + " label='Ba',\n", + " type_symbol='Ba',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.5,\n", + " occupancy=0.5,\n", + ")\n", + "project.structures['lbco'].atom_sites.create(\n", + " label='Co',\n", + " type_symbol='Co',\n", + " fract_x=0.5,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='b',\n", + " b_iso=0.5,\n", + ")\n", + "project.structures['lbco'].atom_sites.create(\n", + " label='O',\n", + " type_symbol='O',\n", + " fract_x=0,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='c',\n", + " b_iso=0.5,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "30", + "metadata": {}, + "source": [ + "#### Show Structure as CIF" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].show_as_cif()" + ] + }, + { + "cell_type": "markdown", + "id": "32", + "metadata": {}, + "source": [ + "#### Show Structure Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].show()" + ] + }, + { + "cell_type": "markdown", + "id": "34", + "metadata": {}, + "source": [ + "#### Save Project State\n", + "\n", + "Save the project state after adding the structure. This ensures\n", + "that all changes are stored and can be accessed later. The project\n", + "state is saved in the directory specified during project creation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35", + "metadata": {}, + "outputs": [], + "source": [ + "project.save()" + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "## Step 3: Define Experiment\n", + "\n", + "This section shows how to add experiments, configure their parameters,\n", + "and link the structures defined in the previous step." + ] + }, + { + "cell_type": "markdown", + "id": "37", + "metadata": {}, + "source": [ + "#### Download Measured Data\n", + "\n", + "Download the data file from the EasyDiffraction repository on GitHub." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = ed.download_data(id=3, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": {}, + "source": [ + "#### Add Diffraction Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_data_path(\n", + " name='hrpt',\n", + " data_path=data_path,\n", + " sample_form='powder',\n", + " beam_mode='constant wavelength',\n", + " radiation_probe='neutron',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "#### Show Defined Experiments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.show_names()" + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "#### Show Measured Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas(expt_name='hrpt')" + ] + }, + { + "cell_type": "markdown", + "id": "45", + "metadata": {}, + "source": [ + "#### Set Instrument\n", + "\n", + "Modify the default instrument parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].instrument.setup_wavelength = 1.494\n", + "project.experiments['hrpt'].instrument.calib_twotheta_offset = 0.6" + ] + }, + { + "cell_type": "markdown", + "id": "47", + "metadata": {}, + "source": [ + "#### Set Peak Profile\n", + "\n", + "Show supported peak profile types." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].show_supported_peak_profile_types()" + ] + }, + { + "cell_type": "markdown", + "id": "49", + "metadata": {}, + "source": [ + "Show the current peak profile type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].show_current_peak_profile_type()" + ] + }, + { + "cell_type": "markdown", + "id": "51", + "metadata": {}, + "source": [ + "Select the desired peak profile type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].peak_profile_type = 'pseudo-voigt'" + ] + }, + { + "cell_type": "markdown", + "id": "53", + "metadata": {}, + "source": [ + "Modify default peak profile parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].peak.broad_gauss_u = 0.1\n", + "project.experiments['hrpt'].peak.broad_gauss_v = -0.1\n", + "project.experiments['hrpt'].peak.broad_gauss_w = 0.1\n", + "project.experiments['hrpt'].peak.broad_lorentz_x = 0\n", + "project.experiments['hrpt'].peak.broad_lorentz_y = 0.1" + ] + }, + { + "cell_type": "markdown", + "id": "55", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "markdown", + "id": "56", + "metadata": {}, + "source": [ + "Show supported background types." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].show_supported_background_types()" + ] + }, + { + "cell_type": "markdown", + "id": "58", + "metadata": {}, + "source": [ + "Show current background type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].show_current_background_type()" + ] + }, + { + "cell_type": "markdown", + "id": "60", + "metadata": {}, + "source": [ + "Select the desired background type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].background_type = 'line-segment'" + ] + }, + { + "cell_type": "markdown", + "id": "62", + "metadata": {}, + "source": [ + "Add background points." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].background.create(id='10', x=10, y=170)\n", + "project.experiments['hrpt'].background.create(id='30', x=30, y=170)\n", + "project.experiments['hrpt'].background.create(id='50', x=50, y=170)\n", + "project.experiments['hrpt'].background.create(id='110', x=110, y=170)\n", + "project.experiments['hrpt'].background.create(id='165', x=165, y=170)" + ] + }, + { + "cell_type": "markdown", + "id": "64", + "metadata": {}, + "source": [ + "Show current background points." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].background.show()" + ] + }, + { + "cell_type": "markdown", + "id": "66", + "metadata": {}, + "source": [ + "#### Set Linked Phases\n", + "\n", + "Link the structure defined in the previous step to the experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].linked_phases.create(id='lbco', scale=10.0)" + ] + }, + { + "cell_type": "markdown", + "id": "68", + "metadata": {}, + "source": [ + "#### Show Experiment as CIF" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].show_as_cif()" + ] + }, + { + "cell_type": "markdown", + "id": "70", + "metadata": {}, + "source": [ + "#### Save Project State" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71", + "metadata": {}, + "outputs": [], + "source": [ + "project.save()" + ] + }, + { + "cell_type": "markdown", + "id": "72", + "metadata": {}, + "source": [ + "## Step 4: Perform Analysis\n", + "\n", + "This section explains the analysis process, including how to set up\n", + "calculation and fitting engines.\n", + "\n", + "#### Set Calculator\n", + "\n", + "Show supported calculation engines for this experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "73", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].show_supported_calculator_types()" + ] + }, + { + "cell_type": "markdown", + "id": "74", + "metadata": {}, + "source": [ + "Show current calculation engine for this experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].show_current_calculator_type()" + ] + }, + { + "cell_type": "markdown", + "id": "76", + "metadata": {}, + "source": [ + "Select the desired calculation engine." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].calculator_type = 'cryspy'" + ] + }, + { + "cell_type": "markdown", + "id": "78", + "metadata": {}, + "source": [ + "#### Show Calculated Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_calc(expt_name='hrpt')" + ] + }, + { + "cell_type": "markdown", + "id": "80", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "81", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "83", + "metadata": {}, + "source": [ + "#### Show Parameters\n", + "\n", + "Show all parameters of the project." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84", + "metadata": {}, + "outputs": [], + "source": [ + "# project.analysis.show_all_params()" + ] + }, + { + "cell_type": "markdown", + "id": "85", + "metadata": {}, + "source": [ + "Show all fittable parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_fittable_params()" + ] + }, + { + "cell_type": "markdown", + "id": "87", + "metadata": {}, + "source": [ + "Show only free parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "89", + "metadata": {}, + "source": [ + "Show how to access parameters in the code." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90", + "metadata": {}, + "outputs": [], + "source": [ + "# project.analysis.how_to_access_parameters()" + ] + }, + { + "cell_type": "markdown", + "id": "91", + "metadata": {}, + "source": [ + "#### Set Fit Mode\n", + "\n", + "Show supported fit modes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_supported_fit_mode_types()" + ] + }, + { + "cell_type": "markdown", + "id": "93", + "metadata": {}, + "source": [ + "Show current fit mode." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_current_fit_mode_type()" + ] + }, + { + "cell_type": "markdown", + "id": "95", + "metadata": {}, + "source": [ + "Select desired fit mode." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit_mode.mode = 'single'" + ] + }, + { + "cell_type": "markdown", + "id": "97", + "metadata": {}, + "source": [ + "#### Set Minimizer\n", + "\n", + "Show supported fitting engines." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_available_minimizers()" + ] + }, + { + "cell_type": "markdown", + "id": "99", + "metadata": {}, + "source": [ + "Show current fitting engine." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "100", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_current_minimizer()" + ] + }, + { + "cell_type": "markdown", + "id": "101", + "metadata": {}, + "source": [ + "Select desired fitting engine." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "102", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "103", + "metadata": {}, + "source": [ + "### Perform Fit 1/5\n", + "\n", + "Set structure parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "104", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].cell.length_a.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "105", + "metadata": {}, + "source": [ + "Set experiment parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "106", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].linked_phases['lbco'].scale.free = True\n", + "project.experiments['hrpt'].instrument.calib_twotheta_offset.free = True\n", + "project.experiments['hrpt'].background['10'].y.free = True\n", + "project.experiments['hrpt'].background['30'].y.free = True\n", + "project.experiments['hrpt'].background['50'].y.free = True\n", + "project.experiments['hrpt'].background['110'].y.free = True\n", + "project.experiments['hrpt'].background['165'].y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "107", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "108", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "109", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "110", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "111", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "112", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "113", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "114", + "metadata": {}, + "source": [ + "#### Save Project State" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "115", + "metadata": {}, + "outputs": [], + "source": [ + "project.save_as(dir_path='lbco_hrpt', temporary=True)" + ] + }, + { + "cell_type": "markdown", + "id": "116", + "metadata": {}, + "source": [ + "### Perform Fit 2/5\n", + "\n", + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "117", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['hrpt'].peak.broad_gauss_u.free = True\n", + "project.experiments['hrpt'].peak.broad_gauss_v.free = True\n", + "project.experiments['hrpt'].peak.broad_gauss_w.free = True\n", + "project.experiments['hrpt'].peak.broad_lorentz_y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "118", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "119", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "120", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "121", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "122", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "123", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "124", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "125", + "metadata": {}, + "source": [ + "#### Save Project State" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "126", + "metadata": {}, + "outputs": [], + "source": [ + "project.save_as(dir_path='lbco_hrpt', temporary=True)" + ] + }, + { + "cell_type": "markdown", + "id": "127", + "metadata": {}, + "source": [ + "### Perform Fit 3/5\n", + "\n", + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "128", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].atom_sites['La'].b_iso.free = True\n", + "project.structures['lbco'].atom_sites['Ba'].b_iso.free = True\n", + "project.structures['lbco'].atom_sites['Co'].b_iso.free = True\n", + "project.structures['lbco'].atom_sites['O'].b_iso.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "129", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "130", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "131", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "132", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "133", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "134", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "135", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "136", + "metadata": {}, + "source": [ + "#### Save Project State" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "137", + "metadata": {}, + "outputs": [], + "source": [ + "project.save_as(dir_path='lbco_hrpt', temporary=True)" + ] + }, + { + "cell_type": "markdown", + "id": "138", + "metadata": {}, + "source": [ + "### Perform Fit 4/5\n", + "\n", + "#### Set Constraints\n", + "\n", + "Set aliases for parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "139", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.aliases.create(\n", + " label='biso_La',\n", + " param_uid=project.structures['lbco'].atom_sites['La'].b_iso.uid,\n", + ")\n", + "project.analysis.aliases.create(\n", + " label='biso_Ba',\n", + " param_uid=project.structures['lbco'].atom_sites['Ba'].b_iso.uid,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "140", + "metadata": {}, + "source": [ + "Set constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "141", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.constraints.create(lhs_alias='biso_Ba', rhs_expr='biso_La')" + ] + }, + { + "cell_type": "markdown", + "id": "142", + "metadata": {}, + "source": [ + "Show defined constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "143", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_constraints()" + ] + }, + { + "cell_type": "markdown", + "id": "144", + "metadata": {}, + "source": [ + "Show free parameters before applying constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "145", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "146", + "metadata": {}, + "source": [ + "Apply constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "147", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.apply_constraints()" + ] + }, + { + "cell_type": "markdown", + "id": "148", + "metadata": {}, + "source": [ + "Show free parameters after applying constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "149", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "150", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "151", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "152", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "153", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "154", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "155", + "metadata": {}, + "source": [ + "#### Save Project State" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "156", + "metadata": {}, + "outputs": [], + "source": [ + "project.save_as(dir_path='lbco_hrpt', temporary=True)" + ] + }, + { + "cell_type": "markdown", + "id": "157", + "metadata": {}, + "source": [ + "### Perform Fit 5/5\n", + "\n", + "#### Set Constraints\n", + "\n", + "Set more aliases for parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "158", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.aliases.create(\n", + " label='occ_La',\n", + " param_uid=project.structures['lbco'].atom_sites['La'].occupancy.uid,\n", + ")\n", + "project.analysis.aliases.create(\n", + " label='occ_Ba',\n", + " param_uid=project.structures['lbco'].atom_sites['Ba'].occupancy.uid,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "159", + "metadata": {}, + "source": [ + "Set more constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "160", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.constraints.create(\n", + " lhs_alias='occ_Ba',\n", + " rhs_expr='1 - occ_La',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "161", + "metadata": {}, + "source": [ + "Show defined constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "162", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_constraints()" + ] + }, + { + "cell_type": "markdown", + "id": "163", + "metadata": {}, + "source": [ + "Apply constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "164", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.apply_constraints()" + ] + }, + { + "cell_type": "markdown", + "id": "165", + "metadata": {}, + "source": [ + "Set structure parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "166", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures['lbco'].atom_sites['La'].occupancy.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "167", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "168", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "169", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "170", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "171", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "172", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "173", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "174", + "metadata": {}, + "source": [ + "#### Save Project State" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "175", + "metadata": {}, + "outputs": [], + "source": [ + "project.save_as(dir_path='lbco_hrpt', temporary=True)" + ] + }, + { + "cell_type": "markdown", + "id": "176", + "metadata": {}, + "source": [ + "## Step 5: Summary\n", + "\n", + "This final section shows how to review the results of the analysis." + ] + }, + { + "cell_type": "markdown", + "id": "177", + "metadata": {}, + "source": [ + "#### Show Project Summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "178", + "metadata": {}, + "outputs": [], + "source": [ + "project.summary.show_report()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "179", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-3.py b/docs/docs/tutorials/ed-3.py similarity index 100% rename from tutorials/ed-3.py rename to docs/docs/tutorials/ed-3.py diff --git a/docs/docs/tutorials/ed-4.ipynb b/docs/docs/tutorials/ed-4.ipynb new file mode 100644 index 00000000..0b784054 --- /dev/null +++ b/docs/docs/tutorials/ed-4.ipynb @@ -0,0 +1,705 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: PbSO4, NPD + XRD\n", + "\n", + "This example demonstrates a more advanced use of the EasyDiffraction\n", + "library by explicitly creating and configuring structures and\n", + "experiments before adding them to a project. It could be more suitable\n", + "for users who are interested in creating custom workflows. This\n", + "tutorial provides minimal explanation and is intended for users\n", + "already familiar with EasyDiffraction.\n", + "\n", + "The tutorial covers a Rietveld refinement of PbSO4 crystal structure\n", + "based on the joint fit of both X-ray and neutron diffraction data." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import ExperimentFactory\n", + "from easydiffraction import Project\n", + "from easydiffraction import StructureFactory\n", + "from easydiffraction import download_data" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Define Structure\n", + "\n", + "This section shows how to add structures and modify their\n", + "parameters.\n", + "\n", + "#### Create Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "structure = StructureFactory.from_scratch(name='pbso4')" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "structure.space_group.name_h_m = 'P n m a'" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a = 8.47\n", + "structure.cell.length_b = 5.39\n", + "structure.cell.length_c = 6.95" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "structure.atom_sites.create(\n", + " label='Pb',\n", + " type_symbol='Pb',\n", + " fract_x=0.1876,\n", + " fract_y=0.25,\n", + " fract_z=0.167,\n", + " wyckoff_letter='c',\n", + " b_iso=1.37,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='S',\n", + " type_symbol='S',\n", + " fract_x=0.0654,\n", + " fract_y=0.25,\n", + " fract_z=0.684,\n", + " wyckoff_letter='c',\n", + " b_iso=0.3777,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O1',\n", + " type_symbol='O',\n", + " fract_x=0.9082,\n", + " fract_y=0.25,\n", + " fract_z=0.5954,\n", + " wyckoff_letter='c',\n", + " b_iso=1.9764,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O2',\n", + " type_symbol='O',\n", + " fract_x=0.1935,\n", + " fract_y=0.25,\n", + " fract_z=0.5432,\n", + " wyckoff_letter='c',\n", + " b_iso=1.4456,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O3',\n", + " type_symbol='O',\n", + " fract_x=0.0811,\n", + " fract_y=0.0272,\n", + " fract_z=0.8086,\n", + " wyckoff_letter='d',\n", + " b_iso=1.2822,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Define Experiments\n", + "\n", + "This section shows how to add experiments, configure their parameters,\n", + "and link the structures defined in the previous step.\n", + "\n", + "### Experiment 1: npd\n", + "\n", + "#### Download Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path1 = download_data(id=13, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "expt1 = ExperimentFactory.from_data_path(\n", + " name='npd',\n", + " data_path=data_path1,\n", + " radiation_probe='neutron',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "expt1.instrument.setup_wavelength = 1.91\n", + "expt1.instrument.calib_twotheta_offset = -0.1406" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "expt1.peak.broad_gauss_u = 0.139\n", + "expt1.peak.broad_gauss_v = -0.412\n", + "expt1.peak.broad_gauss_w = 0.386\n", + "expt1.peak.broad_lorentz_x = 0\n", + "expt1.peak.broad_lorentz_y = 0.088" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "markdown", + "id": "20", + "metadata": {}, + "source": [ + "Select the background type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "expt1.background_type = 'line-segment'" + ] + }, + { + "cell_type": "markdown", + "id": "22", + "metadata": {}, + "source": [ + "Add background points." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "for id, x, y in [\n", + " ('1', 11.0, 206.1624),\n", + " ('2', 15.0, 194.75),\n", + " ('3', 20.0, 194.505),\n", + " ('4', 30.0, 188.4375),\n", + " ('5', 50.0, 207.7633),\n", + " ('6', 70.0, 201.7002),\n", + " ('7', 120.0, 244.4525),\n", + " ('8', 153.0, 226.0595),\n", + "]:\n", + " expt1.background.create(id=id, x=x, y=y)" + ] + }, + { + "cell_type": "markdown", + "id": "24", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "expt1.linked_phases.create(id='pbso4', scale=1.5)" + ] + }, + { + "cell_type": "markdown", + "id": "26", + "metadata": {}, + "source": [ + "### Experiment 2: xrd\n", + "\n", + "#### Download Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27", + "metadata": {}, + "outputs": [], + "source": [ + "data_path2 = download_data(id=16, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "28", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29", + "metadata": {}, + "outputs": [], + "source": [ + "expt2 = ExperimentFactory.from_data_path(\n", + " name='xrd',\n", + " data_path=data_path2,\n", + " radiation_probe='xray',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "30", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31", + "metadata": {}, + "outputs": [], + "source": [ + "expt2.instrument.setup_wavelength = 1.540567\n", + "expt2.instrument.calib_twotheta_offset = -0.05181" + ] + }, + { + "cell_type": "markdown", + "id": "32", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33", + "metadata": {}, + "outputs": [], + "source": [ + "expt2.peak.broad_gauss_u = 0.304138\n", + "expt2.peak.broad_gauss_v = -0.112622\n", + "expt2.peak.broad_gauss_w = 0.021272\n", + "expt2.peak.broad_lorentz_x = 0\n", + "expt2.peak.broad_lorentz_y = 0.057691" + ] + }, + { + "cell_type": "markdown", + "id": "34", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "markdown", + "id": "35", + "metadata": {}, + "source": [ + "Select background type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36", + "metadata": {}, + "outputs": [], + "source": [ + "expt2.background_type = 'chebyshev'" + ] + }, + { + "cell_type": "markdown", + "id": "37", + "metadata": {}, + "source": [ + "Add background points." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "for id, x, y in [\n", + " ('1', 0, 119.195),\n", + " ('2', 1, 6.221),\n", + " ('3', 2, -45.725),\n", + " ('4', 3, 8.119),\n", + " ('5', 4, 54.552),\n", + " ('6', 5, -20.661),\n", + "]:\n", + " expt2.background.create(id=id, order=x, coef=y)" + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "expt2.linked_phases.create(id='pbso4', scale=0.001)" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "## Define Project\n", + "\n", + "The project object is used to manage structures, experiments, and\n", + "analysis.\n", + "\n", + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()" + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add(structure)" + ] + }, + { + "cell_type": "markdown", + "id": "45", + "metadata": {}, + "source": [ + "#### Add Experiments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add(expt1)\n", + "project.experiments.add(expt2)" + ] + }, + { + "cell_type": "markdown", + "id": "47", + "metadata": {}, + "source": [ + "## Perform Analysis\n", + "\n", + "This section outlines the analysis process, including how to configure\n", + "calculation and fitting engines.\n", + "\n", + "#### Set Fit Mode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit_mode.mode = 'joint'" + ] + }, + { + "cell_type": "markdown", + "id": "49", + "metadata": {}, + "source": [ + "#### Set Minimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "51", + "metadata": {}, + "source": [ + "#### Set Fitting Parameters\n", + "\n", + "Set structure parameters to be optimized." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a.free = True\n", + "structure.cell.length_b.free = True\n", + "structure.cell.length_c.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "53", + "metadata": {}, + "source": [ + "Set experiment parameters to be optimized." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54", + "metadata": {}, + "outputs": [], + "source": [ + "expt1.linked_phases['pbso4'].scale.free = True\n", + "\n", + "expt1.instrument.calib_twotheta_offset.free = True\n", + "\n", + "expt1.peak.broad_gauss_u.free = True\n", + "expt1.peak.broad_gauss_v.free = True\n", + "expt1.peak.broad_gauss_w.free = True\n", + "expt1.peak.broad_lorentz_y.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55", + "metadata": {}, + "outputs": [], + "source": [ + "expt2.linked_phases['pbso4'].scale.free = True\n", + "\n", + "expt2.instrument.calib_twotheta_offset.free = True\n", + "\n", + "expt2.peak.broad_gauss_u.free = True\n", + "expt2.peak.broad_gauss_v.free = True\n", + "expt2.peak.broad_gauss_w.free = True\n", + "expt2.peak.broad_lorentz_y.free = True\n", + "\n", + "for term in expt2.background:\n", + " term.coef.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "56", + "metadata": {}, + "source": [ + "#### Perform Fit" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "58", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='npd', x_min=35.5, x_max=38.3, show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='xrd', x_min=29.0, x_max=30.4, show_residual=True)" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-4.py b/docs/docs/tutorials/ed-4.py similarity index 100% rename from tutorials/ed-4.py rename to docs/docs/tutorials/ed-4.py diff --git a/docs/docs/tutorials/ed-5.ipynb b/docs/docs/tutorials/ed-5.ipynb new file mode 100644 index 00000000..2ee5ec1a --- /dev/null +++ b/docs/docs/tutorials/ed-5.ipynb @@ -0,0 +1,638 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: Co2SiO4, D20\n", + "\n", + "This example demonstrates a Rietveld refinement of Co2SiO4 crystal\n", + "structure using constant wavelength neutron powder diffraction data\n", + "from D20 at ILL." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import ExperimentFactory\n", + "from easydiffraction import Project\n", + "from easydiffraction import StructureFactory\n", + "from easydiffraction import download_data" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Define Structure\n", + "\n", + "This section shows how to add structures and modify their\n", + "parameters.\n", + "\n", + "#### Create Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "structure = StructureFactory.from_scratch(name='cosio')" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "structure.space_group.name_h_m = 'P n m a'\n", + "structure.space_group.it_coordinate_system_code = 'abc'" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a = 10.3\n", + "structure.cell.length_b = 6.0\n", + "structure.cell.length_c = 4.8" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites.create(\n", + " label='Co1',\n", + " type_symbol='Co',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Co2',\n", + " type_symbol='Co',\n", + " fract_x=0.279,\n", + " fract_y=0.25,\n", + " fract_z=0.985,\n", + " wyckoff_letter='c',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Si',\n", + " type_symbol='Si',\n", + " fract_x=0.094,\n", + " fract_y=0.25,\n", + " fract_z=0.429,\n", + " wyckoff_letter='c',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O1',\n", + " type_symbol='O',\n", + " fract_x=0.091,\n", + " fract_y=0.25,\n", + " fract_z=0.771,\n", + " wyckoff_letter='c',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O2',\n", + " type_symbol='O',\n", + " fract_x=0.448,\n", + " fract_y=0.25,\n", + " fract_z=0.217,\n", + " wyckoff_letter='c',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O3',\n", + " type_symbol='O',\n", + " fract_x=0.164,\n", + " fract_y=0.032,\n", + " fract_z=0.28,\n", + " wyckoff_letter='d',\n", + " b_iso=0.5,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Define Experiment\n", + "\n", + "This section shows how to add experiments, configure their parameters,\n", + "and link the structures defined in the previous step.\n", + "\n", + "#### Download Measured Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = download_data(id=12, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "expt = ExperimentFactory.from_data_path(name='d20', data_path=data_path)" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "expt.instrument.setup_wavelength = 1.87\n", + "expt.instrument.calib_twotheta_offset = 0.1" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.broad_gauss_u = 0.3\n", + "expt.peak.broad_gauss_v = -0.5\n", + "expt.peak.broad_gauss_w = 0.4" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "expt.background.create(id='1', x=8, y=500)\n", + "expt.background.create(id='2', x=9, y=500)\n", + "expt.background.create(id='3', x=10, y=500)\n", + "expt.background.create(id='4', x=11, y=500)\n", + "expt.background.create(id='5', x=12, y=500)\n", + "expt.background.create(id='6', x=15, y=500)\n", + "expt.background.create(id='7', x=25, y=500)\n", + "expt.background.create(id='8', x=30, y=500)\n", + "expt.background.create(id='9', x=50, y=500)\n", + "expt.background.create(id='10', x=70, y=500)\n", + "expt.background.create(id='11', x=90, y=500)\n", + "expt.background.create(id='12', x=110, y=500)\n", + "expt.background.create(id='13', x=130, y=500)\n", + "expt.background.create(id='14', x=150, y=500)" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "expt.linked_phases.create(id='cosio', scale=1.0)" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "## Define Project\n", + "\n", + "The project object is used to manage the structure, experiment, and\n", + "analysis.\n", + "\n", + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()" + ] + }, + { + "cell_type": "markdown", + "id": "25", + "metadata": {}, + "source": [ + "#### Set Plotting Engine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "# Keep the auto-selected engine. Alternatively, you can uncomment the\n", + "# line below to explicitly set the engine to the required one.\n", + "# project.plotter.engine = 'plotly'" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add(structure)" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "#### Add Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add(expt)" + ] + }, + { + "cell_type": "markdown", + "id": "31", + "metadata": {}, + "source": [ + "## Perform Analysis\n", + "\n", + "This section shows the analysis process, including how to set up\n", + "calculation and fitting engines.\n", + "\n", + "#### Set Minimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "33", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='d20', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='d20', x_min=41, x_max=54, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "#### Set Free Parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a.free = True\n", + "structure.cell.length_b.free = True\n", + "structure.cell.length_c.free = True\n", + "\n", + "structure.atom_sites['Co2'].fract_x.free = True\n", + "structure.atom_sites['Co2'].fract_z.free = True\n", + "structure.atom_sites['Si'].fract_x.free = True\n", + "structure.atom_sites['Si'].fract_z.free = True\n", + "structure.atom_sites['O1'].fract_x.free = True\n", + "structure.atom_sites['O1'].fract_z.free = True\n", + "structure.atom_sites['O2'].fract_x.free = True\n", + "structure.atom_sites['O2'].fract_z.free = True\n", + "structure.atom_sites['O3'].fract_x.free = True\n", + "structure.atom_sites['O3'].fract_y.free = True\n", + "structure.atom_sites['O3'].fract_z.free = True\n", + "\n", + "structure.atom_sites['Co1'].b_iso.free = True\n", + "structure.atom_sites['Co2'].b_iso.free = True\n", + "structure.atom_sites['Si'].b_iso.free = True\n", + "structure.atom_sites['O1'].b_iso.free = True\n", + "structure.atom_sites['O2'].b_iso.free = True\n", + "structure.atom_sites['O3'].b_iso.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "expt.linked_phases['cosio'].scale.free = True\n", + "\n", + "expt.instrument.calib_twotheta_offset.free = True\n", + "\n", + "expt.peak.broad_gauss_u.free = True\n", + "expt.peak.broad_gauss_v.free = True\n", + "expt.peak.broad_gauss_w.free = True\n", + "expt.peak.broad_lorentz_y.free = True\n", + "\n", + "for point in expt.background:\n", + " point.y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": {}, + "source": [ + "#### Set Constraints\n", + "\n", + "Set aliases for parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.aliases.create(\n", + " label='biso_Co1',\n", + " param_uid=project.structures['cosio'].atom_sites['Co1'].b_iso.uid,\n", + ")\n", + "project.analysis.aliases.create(\n", + " label='biso_Co2',\n", + " param_uid=project.structures['cosio'].atom_sites['Co2'].b_iso.uid,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "Set constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.constraints.create(\n", + " lhs_alias='biso_Co2',\n", + " rhs_expr='biso_Co1',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "Apply constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.apply_constraints()" + ] + }, + { + "cell_type": "markdown", + "id": "45", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "47", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='d20', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='d20', x_min=41, x_max=54, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "50", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "This final section shows how to review the results of the analysis." + ] + }, + { + "cell_type": "markdown", + "id": "51", + "metadata": {}, + "source": [ + "#### Show Project Summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52", + "metadata": {}, + "outputs": [], + "source": [ + "project.summary.show_report()" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-5.py b/docs/docs/tutorials/ed-5.py similarity index 100% rename from tutorials/ed-5.py rename to docs/docs/tutorials/ed-5.py diff --git a/docs/docs/tutorials/ed-6.ipynb b/docs/docs/tutorials/ed-6.ipynb new file mode 100644 index 00000000..599c09eb --- /dev/null +++ b/docs/docs/tutorials/ed-6.ipynb @@ -0,0 +1,849 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: HS, HRPT\n", + "\n", + "This example demonstrates a Rietveld refinement of HS crystal\n", + "structure using constant wavelength neutron powder diffraction data\n", + "from HRPT at PSI." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import ExperimentFactory\n", + "from easydiffraction import Project\n", + "from easydiffraction import StructureFactory\n", + "from easydiffraction import download_data" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Define Structure\n", + "\n", + "This section shows how to add structures and modify their\n", + "parameters.\n", + "\n", + "#### Create Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "structure = StructureFactory.from_scratch(name='hs')" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "structure.space_group.name_h_m = 'R -3 m'\n", + "structure.space_group.it_coordinate_system_code = 'h'" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": { + "lines_to_next_cell": 2 + }, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a = 6.9\n", + "structure.cell.length_c = 14.1" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites.create(\n", + " label='Zn',\n", + " type_symbol='Zn',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0.5,\n", + " wyckoff_letter='b',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Cu',\n", + " type_symbol='Cu',\n", + " fract_x=0.5,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='e',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O',\n", + " type_symbol='O',\n", + " fract_x=0.21,\n", + " fract_y=-0.21,\n", + " fract_z=0.06,\n", + " wyckoff_letter='h',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Cl',\n", + " type_symbol='Cl',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0.197,\n", + " wyckoff_letter='c',\n", + " b_iso=0.5,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='H',\n", + " type_symbol='2H',\n", + " fract_x=0.13,\n", + " fract_y=-0.13,\n", + " fract_z=0.08,\n", + " wyckoff_letter='h',\n", + " b_iso=0.5,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Define Experiment\n", + "\n", + "This section shows how to add experiments, configure their parameters,\n", + "and link the structures defined in the previous step.\n", + "\n", + "#### Download Measured Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = download_data(id=11, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "expt = ExperimentFactory.from_data_path(name='hrpt', data_path=data_path)" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "expt.instrument.setup_wavelength = 1.89\n", + "expt.instrument.calib_twotheta_offset = 0.0" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.broad_gauss_u = 0.1\n", + "expt.peak.broad_gauss_v = -0.2\n", + "expt.peak.broad_gauss_w = 0.2\n", + "expt.peak.broad_lorentz_x = 0.0\n", + "expt.peak.broad_lorentz_y = 0" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "expt.background.create(id='1', x=4.4196, y=500)\n", + "expt.background.create(id='2', x=6.6207, y=500)\n", + "expt.background.create(id='3', x=10.4918, y=500)\n", + "expt.background.create(id='4', x=15.4634, y=500)\n", + "expt.background.create(id='5', x=45.6041, y=500)\n", + "expt.background.create(id='6', x=74.6844, y=500)\n", + "expt.background.create(id='7', x=103.4187, y=500)\n", + "expt.background.create(id='8', x=121.6311, y=500)\n", + "expt.background.create(id='9', x=159.4116, y=500)" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "expt.linked_phases.create(id='hs', scale=0.5)" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "## Define Project\n", + "\n", + "The project object is used to manage the structure, experiment, and\n", + "analysis.\n", + "\n", + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()" + ] + }, + { + "cell_type": "markdown", + "id": "25", + "metadata": {}, + "source": [ + "#### Set Plotting Engine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "# Keep the auto-selected engine. Alternatively, you can uncomment the\n", + "# line below to explicitly set the engine to the required one.\n", + "# project.plotter.engine = 'plotly'" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add(structure)" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "#### Add Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add(expt)" + ] + }, + { + "cell_type": "markdown", + "id": "31", + "metadata": {}, + "source": [ + "## Perform Analysis\n", + "\n", + "This section shows the analysis process, including how to set up\n", + "calculation and fitting engines.\n", + "\n", + "#### Set Minimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "33", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "### Perform Fit 1/5\n", + "\n", + "Set parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a.free = True\n", + "structure.cell.length_c.free = True\n", + "\n", + "expt.linked_phases['hs'].scale.free = True\n", + "expt.instrument.calib_twotheta_offset.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "38", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "40", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "46", + "metadata": {}, + "source": [ + "### Perform Fit 2/5\n", + "\n", + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.broad_gauss_u.free = True\n", + "expt.peak.broad_gauss_v.free = True\n", + "expt.peak.broad_gauss_w.free = True\n", + "expt.peak.broad_lorentz_x.free = True\n", + "\n", + "for point in expt.background:\n", + " point.y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "48", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "50", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "53", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "56", + "metadata": {}, + "source": [ + "### Perform Fit 3/5\n", + "\n", + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites['O'].fract_x.free = True\n", + "structure.atom_sites['O'].fract_z.free = True\n", + "structure.atom_sites['Cl'].fract_z.free = True\n", + "structure.atom_sites['H'].fract_x.free = True\n", + "structure.atom_sites['H'].fract_z.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "58", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "60", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "63", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "66", + "metadata": {}, + "source": [ + "### Perform Fit 4/5\n", + "\n", + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites['Zn'].b_iso.free = True\n", + "structure.atom_sites['Cu'].b_iso.free = True\n", + "structure.atom_sites['O'].b_iso.free = True\n", + "structure.atom_sites['Cl'].b_iso.free = True\n", + "structure.atom_sites['H'].b_iso.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "68", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "70", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "73", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='hrpt', x_min=48, x_max=51, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "76", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "This final section shows how to review the results of the analysis." + ] + }, + { + "cell_type": "markdown", + "id": "77", + "metadata": {}, + "source": [ + "#### Show Project Summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78", + "metadata": {}, + "outputs": [], + "source": [ + "project.summary.show_report()" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-6.py b/docs/docs/tutorials/ed-6.py similarity index 100% rename from tutorials/ed-6.py rename to docs/docs/tutorials/ed-6.py diff --git a/docs/docs/tutorials/ed-7.ipynb b/docs/docs/tutorials/ed-7.ipynb new file mode 100644 index 00000000..869dc723 --- /dev/null +++ b/docs/docs/tutorials/ed-7.ipynb @@ -0,0 +1,741 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: Si, SEPD\n", + "\n", + "This example demonstrates a Rietveld refinement of Si crystal\n", + "structure using time-of-flight neutron powder diffraction data from\n", + "SEPD at Argonne." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import ExperimentFactory\n", + "from easydiffraction import Project\n", + "from easydiffraction import StructureFactory\n", + "from easydiffraction import download_data" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Define Structure\n", + "\n", + "This section shows how to add structures and modify their\n", + "parameters.\n", + "\n", + "#### Create Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "structure = StructureFactory.from_scratch(name='si')" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "structure.space_group.name_h_m = 'F d -3 m'\n", + "structure.space_group.it_coordinate_system_code = '2'" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a = 5.431" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites.create(\n", + " label='Si',\n", + " type_symbol='Si',\n", + " fract_x=0.125,\n", + " fract_y=0.125,\n", + " fract_z=0.125,\n", + " b_iso=0.5,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Define Experiment\n", + "\n", + "This section shows how to add experiments, configure their\n", + "parameters, and link the structures defined in the previous step.\n", + "\n", + "#### Download Measured Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = download_data(id=7, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "expt = ExperimentFactory.from_data_path(\n", + " name='sepd', data_path=data_path, beam_mode='time-of-flight'\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "expt.instrument.setup_twotheta_bank = 144.845\n", + "expt.instrument.calib_d_to_tof_offset = 0.0\n", + "expt.instrument.calib_d_to_tof_linear = 7476.91\n", + "expt.instrument.calib_d_to_tof_quad = -1.54" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak_profile_type = 'pseudo-voigt * ikeda-carpenter'\n", + "expt.peak.broad_gauss_sigma_0 = 3.0\n", + "expt.peak.broad_gauss_sigma_1 = 40.0\n", + "expt.peak.broad_gauss_sigma_2 = 2.0\n", + "expt.peak.broad_mix_beta_0 = 0.04221\n", + "expt.peak.broad_mix_beta_1 = 0.00946" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "#### Set Peak Asymmetry" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.asym_alpha_0 = 0.0\n", + "expt.peak.asym_alpha_1 = 0.5971" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "expt.background_type = 'line-segment'\n", + "for x in range(0, 35000, 5000):\n", + " expt.background.create(id=str(x), x=x, y=200)" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "expt.linked_phases.create(id='si', scale=10.0)" + ] + }, + { + "cell_type": "markdown", + "id": "25", + "metadata": {}, + "source": [ + "## Define Project\n", + "\n", + "The project object is used to manage the structure, experiment, and\n", + "analysis.\n", + "\n", + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add(structure)" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "#### Add Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add(expt)" + ] + }, + { + "cell_type": "markdown", + "id": "31", + "metadata": {}, + "source": [ + "## Perform Analysis\n", + "\n", + "This section shows the analysis process, including how to set up\n", + "calculation and fitting engines.\n", + "\n", + "#### Set Minimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "33", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', show_residual=True)\n", + "project.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "35", + "metadata": {}, + "source": [ + "### Perform Fit 1/5\n", + "\n", + "Set parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a.free = True\n", + "\n", + "expt.linked_phases['si'].scale.free = True\n", + "expt.instrument.calib_d_to_tof_offset.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "37", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "44", + "metadata": {}, + "source": [ + "### Perform Fit 2/5\n", + "\n", + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], + "source": [ + "for point in expt.background:\n", + " point.y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "46", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "48", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "50", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "53", + "metadata": {}, + "source": [ + "### Perform Fit 3/5\n", + "\n", + "Fix background points." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54", + "metadata": {}, + "outputs": [], + "source": [ + "for point in expt.background:\n", + " point.y.free = False" + ] + }, + { + "cell_type": "markdown", + "id": "55", + "metadata": {}, + "source": [ + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.broad_gauss_sigma_0.free = True\n", + "expt.peak.broad_gauss_sigma_1.free = True\n", + "expt.peak.broad_gauss_sigma_2.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "57", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "58", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "59", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "61", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "64", + "metadata": {}, + "source": [ + "### Perform Fit 4/5\n", + "\n", + "Set more parameters to be refined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites['Si'].b_iso.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "66", + "metadata": {}, + "source": [ + "Show free parameters after selection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.show_free_params()" + ] + }, + { + "cell_type": "markdown", + "id": "68", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "70", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='sepd', x_min=23200, x_max=23700, show_residual=True)" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-7.py b/docs/docs/tutorials/ed-7.py similarity index 100% rename from tutorials/ed-7.py rename to docs/docs/tutorials/ed-7.py diff --git a/docs/docs/tutorials/ed-8.ipynb b/docs/docs/tutorials/ed-8.ipynb new file mode 100644 index 00000000..4bc42ad1 --- /dev/null +++ b/docs/docs/tutorials/ed-8.ipynb @@ -0,0 +1,747 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: NCAF, WISH\n", + "\n", + "This example demonstrates a Rietveld refinement of Na2Ca3Al2F14\n", + "crystal structure using time-of-flight neutron powder diffraction data\n", + "from WISH at ISIS.\n", + "\n", + "Two datasets from detector banks 5+6 and 4+7 are used for joint\n", + "fitting." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import ExperimentFactory\n", + "from easydiffraction import Project\n", + "from easydiffraction import StructureFactory\n", + "from easydiffraction import download_data" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Define Structure\n", + "\n", + "This section covers how to add structures and modify their\n", + "parameters.\n", + "\n", + "#### Create Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "structure = StructureFactory.from_scratch(name='ncaf')" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "structure.space_group.name_h_m = 'I 21 3'\n", + "structure.space_group.it_coordinate_system_code = '1'" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a = 10.250256" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites.create(\n", + " label='Ca',\n", + " type_symbol='Ca',\n", + " fract_x=0.4663,\n", + " fract_y=0.0,\n", + " fract_z=0.25,\n", + " wyckoff_letter='b',\n", + " b_iso=0.92,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Al',\n", + " type_symbol='Al',\n", + " fract_x=0.2521,\n", + " fract_y=0.2521,\n", + " fract_z=0.2521,\n", + " wyckoff_letter='a',\n", + " b_iso=0.73,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Na',\n", + " type_symbol='Na',\n", + " fract_x=0.0851,\n", + " fract_y=0.0851,\n", + " fract_z=0.0851,\n", + " wyckoff_letter='a',\n", + " b_iso=2.08,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='F1',\n", + " type_symbol='F',\n", + " fract_x=0.1377,\n", + " fract_y=0.3054,\n", + " fract_z=0.1195,\n", + " wyckoff_letter='c',\n", + " b_iso=0.90,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='F2',\n", + " type_symbol='F',\n", + " fract_x=0.3625,\n", + " fract_y=0.3633,\n", + " fract_z=0.1867,\n", + " wyckoff_letter='c',\n", + " b_iso=1.37,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='F3',\n", + " type_symbol='F',\n", + " fract_x=0.4612,\n", + " fract_y=0.4612,\n", + " fract_z=0.4612,\n", + " wyckoff_letter='a',\n", + " b_iso=0.88,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## Define Experiment\n", + "\n", + "This section shows how to add experiments, configure their parameters,\n", + "and link the structures defined in the previous step.\n", + "\n", + "#### Download Measured Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "data_path56 = download_data(id=9, destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "data_path47 = download_data(id=10, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "14", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "expt56 = ExperimentFactory.from_data_path(\n", + " name='wish_5_6',\n", + " data_path=data_path56,\n", + " beam_mode='time-of-flight',\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "expt47 = ExperimentFactory.from_data_path(\n", + " name='wish_4_7',\n", + " data_path=data_path47,\n", + " beam_mode='time-of-flight',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "expt56.instrument.setup_twotheta_bank = 152.827\n", + "expt56.instrument.calib_d_to_tof_offset = -13.5\n", + "expt56.instrument.calib_d_to_tof_linear = 20773.0\n", + "expt56.instrument.calib_d_to_tof_quad = -1.08308" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19", + "metadata": {}, + "outputs": [], + "source": [ + "expt47.instrument.setup_twotheta_bank = 121.660\n", + "expt47.instrument.calib_d_to_tof_offset = -15.0\n", + "expt47.instrument.calib_d_to_tof_linear = 18660.0\n", + "expt47.instrument.calib_d_to_tof_quad = -0.47488" + ] + }, + { + "cell_type": "markdown", + "id": "20", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "expt56.peak.broad_gauss_sigma_0 = 0.0\n", + "expt56.peak.broad_gauss_sigma_1 = 0.0\n", + "expt56.peak.broad_gauss_sigma_2 = 15.5\n", + "expt56.peak.broad_mix_beta_0 = 0.007\n", + "expt56.peak.broad_mix_beta_1 = 0.01\n", + "expt56.peak.asym_alpha_0 = -0.0094\n", + "expt56.peak.asym_alpha_1 = 0.1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "expt47.peak.broad_gauss_sigma_0 = 0.0\n", + "expt47.peak.broad_gauss_sigma_1 = 29.8\n", + "expt47.peak.broad_gauss_sigma_2 = 18.0\n", + "expt47.peak.broad_mix_beta_0 = 0.006\n", + "expt47.peak.broad_mix_beta_1 = 0.015\n", + "expt47.peak.asym_alpha_0 = -0.0115\n", + "expt47.peak.asym_alpha_1 = 0.1" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "expt56.background_type = 'line-segment'\n", + "for idx, (x, y) in enumerate(\n", + " [\n", + " (9162, 465),\n", + " (11136, 593),\n", + " (13313, 497),\n", + " (14906, 546),\n", + " (16454, 533),\n", + " (17352, 496),\n", + " (18743, 428),\n", + " (20179, 452),\n", + " (21368, 397),\n", + " (22176, 468),\n", + " (22827, 477),\n", + " (24644, 380),\n", + " (26439, 381),\n", + " (28257, 378),\n", + " (31196, 343),\n", + " (34034, 328),\n", + " (37265, 310),\n", + " (41214, 323),\n", + " (44827, 283),\n", + " (49830, 273),\n", + " (52905, 257),\n", + " (58204, 260),\n", + " (62916, 261),\n", + " (70186, 262),\n", + " (74204, 262),\n", + " (82103, 268),\n", + " (91958, 268),\n", + " (102712, 262),\n", + " ],\n", + " start=1,\n", + "):\n", + " expt56.background.create(id=str(idx), x=x, y=y)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "expt47.background_type = 'line-segment'\n", + "for idx, (x, y) in enumerate(\n", + " [\n", + " (9090, 488),\n", + " (10672, 566),\n", + " (12287, 494),\n", + " (14037, 559),\n", + " (15451, 529),\n", + " (16764, 445),\n", + " (18076, 460),\n", + " (19456, 413),\n", + " (20466, 511),\n", + " (21880, 396),\n", + " (23798, 391),\n", + " (25447, 385),\n", + " (28073, 349),\n", + " (30058, 332),\n", + " (32583, 309),\n", + " (34804, 355),\n", + " (37160, 318),\n", + " (40324, 290),\n", + " (46895, 260),\n", + " (50631, 256),\n", + " (54602, 246),\n", + " (58439, 264),\n", + " (66520, 250),\n", + " (75002, 258),\n", + " (83649, 257),\n", + " (92770, 255),\n", + " (101524, 260),\n", + " ],\n", + " start=1,\n", + "):\n", + " expt47.background.create(id=str(idx), x=x, y=y)" + ] + }, + { + "cell_type": "markdown", + "id": "26", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27", + "metadata": {}, + "outputs": [], + "source": [ + "expt56.linked_phases.create(id='ncaf', scale=1.0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], + "source": [ + "expt47.linked_phases.create(id='ncaf', scale=2.0)" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "#### Set Excluded Regions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "expt56.excluded_regions.create(id='1', start=0, end=10010)\n", + "expt56.excluded_regions.create(id='2', start=100010, end=200000)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31", + "metadata": {}, + "outputs": [], + "source": [ + "expt47.excluded_regions.create(id='1', start=0, end=10006)\n", + "expt47.excluded_regions.create(id='2', start=100004, end=200000)" + ] + }, + { + "cell_type": "markdown", + "id": "32", + "metadata": {}, + "source": [ + "## Define Project\n", + "\n", + "The project object is used to manage the structure, experiments,\n", + "and analysis\n", + "\n", + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()" + ] + }, + { + "cell_type": "markdown", + "id": "34", + "metadata": {}, + "source": [ + "#### Set Plotting Engine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35", + "metadata": {}, + "outputs": [], + "source": [ + "# Keep the auto-selected engine. Alternatively, you can uncomment the\n", + "# line below to explicitly set the engine to the required one.\n", + "# project.plotter.engine = 'plotly'" + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "#### Add Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add(structure)" + ] + }, + { + "cell_type": "markdown", + "id": "38", + "metadata": {}, + "source": [ + "#### Add Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add(expt56)\n", + "project.experiments.add(expt47)" + ] + }, + { + "cell_type": "markdown", + "id": "40", + "metadata": {}, + "source": [ + "## Perform Analysis\n", + "\n", + "This section shows the analysis process, including how to set up\n", + "calculation and fitting engines.\n", + "\n", + "#### Set Minimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "42", + "metadata": {}, + "source": [ + "#### Set Fit Mode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit_mode.mode = 'joint'" + ] + }, + { + "cell_type": "markdown", + "id": "44", + "metadata": {}, + "source": [ + "#### Set Free Parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites['Ca'].b_iso.free = True\n", + "structure.atom_sites['Al'].b_iso.free = True\n", + "structure.atom_sites['Na'].b_iso.free = True\n", + "structure.atom_sites['F1'].b_iso.free = True\n", + "structure.atom_sites['F2'].b_iso.free = True\n", + "structure.atom_sites['F3'].b_iso.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46", + "metadata": {}, + "outputs": [], + "source": [ + "expt56.linked_phases['ncaf'].scale.free = True\n", + "expt56.instrument.calib_d_to_tof_offset.free = True\n", + "expt56.instrument.calib_d_to_tof_linear.free = True\n", + "expt56.peak.broad_gauss_sigma_2.free = True\n", + "expt56.peak.broad_mix_beta_0.free = True\n", + "expt56.peak.broad_mix_beta_1.free = True\n", + "expt56.peak.asym_alpha_1.free = True\n", + "\n", + "expt47.linked_phases['ncaf'].scale.free = True\n", + "expt47.instrument.calib_d_to_tof_linear.free = True\n", + "expt47.instrument.calib_d_to_tof_offset.free = True\n", + "expt47.peak.broad_gauss_sigma_2.free = True\n", + "expt47.peak.broad_mix_beta_0.free = True\n", + "expt47.peak.broad_mix_beta_1.free = True\n", + "expt47.peak.asym_alpha_1.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "47", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='wish_5_6', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='wish_4_7', show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "50", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "52", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='wish_5_6', show_residual=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='wish_4_7', show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "55", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "This final section shows how to review the results of the analysis." + ] + }, + { + "cell_type": "markdown", + "id": "56", + "metadata": {}, + "source": [ + "#### Show Project Summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57", + "metadata": {}, + "outputs": [], + "source": [ + "project.summary.show_report()" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-8.py b/docs/docs/tutorials/ed-8.py similarity index 100% rename from tutorials/ed-8.py rename to docs/docs/tutorials/ed-8.py diff --git a/docs/docs/tutorials/ed-9.ipynb b/docs/docs/tutorials/ed-9.ipynb new file mode 100644 index 00000000..735964ae --- /dev/null +++ b/docs/docs/tutorials/ed-9.ipynb @@ -0,0 +1,704 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: LBCO+Si, McStas\n", + "\n", + "This example demonstrates a Rietveld refinement of La0.5Ba0.5CoO3\n", + "crystal structure with a small amount of Si phase using time-of-flight\n", + "neutron powder diffraction data simulated with McStas." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import ExperimentFactory\n", + "from easydiffraction import Project\n", + "from easydiffraction import StructureFactory\n", + "from easydiffraction import download_data" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Define Structures\n", + "\n", + "This section shows how to add structures and modify their\n", + "parameters.\n", + "\n", + "### Create Structure 1: LBCO" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "structure_1 = StructureFactory.from_scratch(name='lbco')" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "structure_1.space_group.name_h_m = 'P m -3 m'\n", + "structure_1.space_group.it_coordinate_system_code = '1'" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure_1.cell.length_a = 3.8909" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure_1.atom_sites.create(\n", + " label='La',\n", + " type_symbol='La',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.2,\n", + " occupancy=0.5,\n", + ")\n", + "structure_1.atom_sites.create(\n", + " label='Ba',\n", + " type_symbol='Ba',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.2,\n", + " occupancy=0.5,\n", + ")\n", + "structure_1.atom_sites.create(\n", + " label='Co',\n", + " type_symbol='Co',\n", + " fract_x=0.5,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='b',\n", + " b_iso=0.2567,\n", + ")\n", + "structure_1.atom_sites.create(\n", + " label='O',\n", + " type_symbol='O',\n", + " fract_x=0,\n", + " fract_y=0.5,\n", + " fract_z=0.5,\n", + " wyckoff_letter='c',\n", + " b_iso=1.4041,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "### Create Structure 2: Si" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "structure_2 = StructureFactory.from_scratch(name='si')" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "structure_2.space_group.name_h_m = 'F d -3 m'\n", + "structure_2.space_group.it_coordinate_system_code = '2'" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "structure_2.cell.length_a = 5.43146" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "structure_2.atom_sites.create(\n", + " label='Si',\n", + " type_symbol='Si',\n", + " fract_x=0.0,\n", + " fract_y=0.0,\n", + " fract_z=0.0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.0,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "## Define Experiment\n", + "\n", + "This section shows how to add experiments, configure their parameters,\n", + "and link the structures defined in the previous step.\n", + "\n", + "#### Download Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = download_data(id=8, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "#### Create Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "experiment = ExperimentFactory.from_data_path(\n", + " name='mcstas',\n", + " data_path=data_path,\n", + " sample_form='powder',\n", + " beam_mode='time-of-flight',\n", + " radiation_probe='neutron',\n", + " scattering_type='bragg',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.instrument.setup_twotheta_bank = 94.90931761529106\n", + "experiment.instrument.calib_d_to_tof_offset = 0.0\n", + "experiment.instrument.calib_d_to_tof_linear = 58724.76869981215\n", + "experiment.instrument.calib_d_to_tof_quad = -0.00001" + ] + }, + { + "cell_type": "markdown", + "id": "25", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "# experiment.peak_profile_type = 'pseudo-voigt * ikeda-carpenter'\n", + "experiment.peak.broad_gauss_sigma_0 = 45137\n", + "experiment.peak.broad_gauss_sigma_1 = -52394\n", + "experiment.peak.broad_gauss_sigma_2 = 22998\n", + "experiment.peak.broad_mix_beta_0 = 0.0055\n", + "experiment.peak.broad_mix_beta_1 = 0.0041\n", + "experiment.peak.asym_alpha_0 = 0\n", + "experiment.peak.asym_alpha_1 = 0.0097" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "markdown", + "id": "28", + "metadata": {}, + "source": [ + "Select the background type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.background_type = 'line-segment'" + ] + }, + { + "cell_type": "markdown", + "id": "30", + "metadata": {}, + "source": [ + "Add background points." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.background.create(id='1', x=45000, y=0.2)\n", + "experiment.background.create(id='2', x=50000, y=0.2)\n", + "experiment.background.create(id='3', x=55000, y=0.2)\n", + "experiment.background.create(id='4', x=65000, y=0.2)\n", + "experiment.background.create(id='5', x=70000, y=0.2)\n", + "experiment.background.create(id='6', x=75000, y=0.2)\n", + "experiment.background.create(id='7', x=80000, y=0.2)\n", + "experiment.background.create(id='8', x=85000, y=0.2)\n", + "experiment.background.create(id='9', x=90000, y=0.2)\n", + "experiment.background.create(id='10', x=95000, y=0.2)\n", + "experiment.background.create(id='11', x=100000, y=0.2)\n", + "experiment.background.create(id='12', x=105000, y=0.2)\n", + "experiment.background.create(id='13', x=110000, y=0.2)" + ] + }, + { + "cell_type": "markdown", + "id": "32", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.linked_phases.create(id='lbco', scale=4.0)\n", + "experiment.linked_phases.create(id='si', scale=0.2)" + ] + }, + { + "cell_type": "markdown", + "id": "34", + "metadata": {}, + "source": [ + "## Define Project\n", + "\n", + "The project object is used to manage structures, experiments, and\n", + "analysis.\n", + "\n", + "#### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()" + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "#### Add Structures" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add(structure_1)\n", + "project.structures.add(structure_2)" + ] + }, + { + "cell_type": "markdown", + "id": "38", + "metadata": {}, + "source": [ + "#### Show Structures" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.show_names()" + ] + }, + { + "cell_type": "markdown", + "id": "40", + "metadata": {}, + "source": [ + "#### Add Experiments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add(experiment)" + ] + }, + { + "cell_type": "markdown", + "id": "42", + "metadata": {}, + "source": [ + "#### Set Excluded Regions\n", + "\n", + "Show measured data as loaded from the file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas(expt_name='mcstas')" + ] + }, + { + "cell_type": "markdown", + "id": "44", + "metadata": {}, + "source": [ + "Add excluded regions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.excluded_regions.create(id='1', start=0, end=40000)\n", + "experiment.excluded_regions.create(id='2', start=108000, end=200000)" + ] + }, + { + "cell_type": "markdown", + "id": "46", + "metadata": {}, + "source": [ + "Show excluded regions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.excluded_regions.show()" + ] + }, + { + "cell_type": "markdown", + "id": "48", + "metadata": {}, + "source": [ + "Show measured data after adding excluded regions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas(expt_name='mcstas')" + ] + }, + { + "cell_type": "markdown", + "id": "50", + "metadata": {}, + "source": [ + "Show experiment as CIF." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments['mcstas'].show_as_cif()" + ] + }, + { + "cell_type": "markdown", + "id": "52", + "metadata": {}, + "source": [ + "## Perform Analysis\n", + "\n", + "This section outlines the analysis process, including how to configure\n", + "calculation and fitting engines.\n", + "\n", + "#### Set Minimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.current_minimizer = 'lmfit'" + ] + }, + { + "cell_type": "markdown", + "id": "54", + "metadata": {}, + "source": [ + "#### Set Fitting Parameters\n", + "\n", + "Set structure parameters to be optimized." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55", + "metadata": {}, + "outputs": [], + "source": [ + "structure_1.cell.length_a.free = True\n", + "structure_1.atom_sites['Co'].b_iso.free = True\n", + "structure_1.atom_sites['O'].b_iso.free = True\n", + "\n", + "structure_2.cell.length_a.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "56", + "metadata": {}, + "source": [ + "Set experiment parameters to be optimized." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57", + "metadata": {}, + "outputs": [], + "source": [ + "experiment.linked_phases['lbco'].scale.free = True\n", + "experiment.linked_phases['si'].scale.free = True\n", + "\n", + "experiment.peak.broad_gauss_sigma_0.free = True\n", + "experiment.peak.broad_gauss_sigma_1.free = True\n", + "experiment.peak.broad_gauss_sigma_2.free = True\n", + "\n", + "experiment.peak.asym_alpha_1.free = True\n", + "experiment.peak.broad_mix_beta_0.free = True\n", + "experiment.peak.broad_mix_beta_1.free = True\n", + "\n", + "for point in experiment.background:\n", + " point.y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "58", + "metadata": {}, + "source": [ + "#### Perform Fit" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()\n", + "project.analysis.show_fit_results()" + ] + }, + { + "cell_type": "markdown", + "id": "60", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_meas_vs_calc(expt_name='mcstas')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/ed-9.py b/docs/docs/tutorials/ed-9.py similarity index 100% rename from tutorials/ed-9.py rename to docs/docs/tutorials/ed-9.py diff --git a/tutorials/index.json b/docs/docs/tutorials/index.json similarity index 98% rename from tutorials/index.json rename to docs/docs/tutorials/index.json index 138b0e51..d40d1896 100644 --- a/tutorials/index.json +++ b/docs/docs/tutorials/index.json @@ -86,7 +86,7 @@ "13": { "url": "https://easyscience.github.io/diffraction-lib/{version}/tutorials/ed-13/ed-13.ipynb", "original_name": "dmsc-summer-school-2025_analysis-powder-diffraction", - "title": "DMSC Summer School 2025: Powder Diffraction Analysis", + "title": "DMSC Summer School: Powder Diffraction Analysis", "description": "Comprehensive workshop tutorial covering Rietveld refinement of Si and La0.5Ba0.5CoO3 using simulated powder diffraction data", "level": "workshop" }, diff --git a/docs/docs/tutorials/index.md b/docs/docs/tutorials/index.md new file mode 100644 index 00000000..5e406e59 --- /dev/null +++ b/docs/docs/tutorials/index.md @@ -0,0 +1,95 @@ +--- +icon: material/school +--- + +# :material-school: Tutorials + +This section presents a collection of **Jupyter Notebook** tutorials +that demonstrate how to use EasyDiffraction for various tasks. These +tutorials serve as self-contained, step-by-step **guides** to help users +grasp the workflow of diffraction data analysis using EasyDiffraction. + +Instructions on how to run the tutorials are provided in the +[:material-cog-box: Installation & Setup](../installation-and-setup/index.md#how-to-run-tutorials) +section of the documentation. + +The tutorials are organized into the following categories. + +## Getting Started + +- [LBCO `quick` CIF](ed-1.ipynb) – A minimal example intended as a quick + reference for users already familiar with the EasyDiffraction API or + who want to see how Rietveld refinement of the La0.5Ba0.5CoO3 crystal + structure can be performed when both the structure and experiment are + loaded from CIF files. Data collected from constant wavelength neutron + powder diffraction at HRPT at PSI. +- [LBCO `quick` `code`](ed-2.ipynb) – A minimal example intended as a + quick reference for users already familiar with the EasyDiffraction + API or who want to see an example refinement when both the structure + and experiment are defined directly in code. This tutorial covers a + Rietveld refinement of the La0.5Ba0.5CoO3 crystal structure using + constant wavelength neutron powder diffraction data from HRPT at PSI. +- [LBCO `complete`](ed-3.ipynb) – Demonstrates the use of the + EasyDiffraction API in a simplified, user-friendly manner that closely + follows the GUI workflow for a Rietveld refinement of the + La0.5Ba0.5CoO3 crystal structure using constant wavelength neutron + powder diffraction data from HRPT at PSI. This tutorial provides a + full explanation of the workflow with detailed comments and + descriptions of every step, making it suitable for users who are new + to EasyDiffraction or those who prefer a more guided approach. + +## Powder Diffraction + +- [Co2SiO4 `pd-neut-cwl`](ed-5.ipynb) – Demonstrates a Rietveld + refinement of the Co2SiO4 crystal structure using constant wavelength + neutron powder diffraction data from D20 at ILL. +- [HS `pd-neut-cwl`](ed-6.ipynb) – Demonstrates a Rietveld refinement of + the HS crystal structure using constant wavelength neutron powder + diffraction data from HRPT at PSI. +- [Si `pd-neut-tof`](ed-7.ipynb) – Demonstrates a Rietveld refinement of + the Si crystal structure using time-of-flight neutron powder + diffraction data from SEPD at Argonne. +- [NCAF `pd-neut-tof`](ed-8.ipynb) – Demonstrates a Rietveld refinement + of the Na2Ca3Al2F14 crystal structure using two time-of-flight neutron + powder diffraction datasets (from two detector banks) of the WISH + instrument at ISIS. + +## Single Crystal Diffraction + +- [Tb2TiO7 `sg-neut-cwl`](ed-14.ipynb) – Demonstrates structure + refinement of Tb2TiO7 using constant wavelength neutron single crystal + diffraction data from HEiDi at FRM II. +- [Taurine `sg-neut-tof`](ed-15.ipynb) – Demonstrates structure + refinement of Taurine using time-of-flight neutron single crystal + diffraction data from SENJU at J-PARC. + +## Pair Distribution Function (PDF) + +- [Ni `pd-neut-cwl`](ed-10.ipynb) – Demonstrates a PDF analysis of Ni + using data collected from a constant wavelength neutron powder + diffraction experiment. +- [Si `pd-neut-tof`](ed-11.ipynb) – Demonstrates a PDF analysis of Si + using data collected from a time-of-flight neutron powder diffraction + experiment at NOMAD at SNS. +- [NaCl `pd-xray`](ed-12.ipynb) – Demonstrates a PDF analysis of NaCl + using data collected from an X-ray powder diffraction experiment. + +## Multi-Structure & Multi-Experiment Refinement + +- [PbSO4 NPD+XRD](ed-4.ipynb) – Joint fit of PbSO4 using X-ray and + neutron constant wavelength powder diffraction data. +- [LBCO+Si McStas](ed-9.ipynb) – Multi-phase Rietveld refinement of + La0.5Ba0.5CoO3 with Si impurity using time-of-flight neutron data + simulated with McStas. +- [Si Bragg+PDF](ed-16.ipynb) – Joint refinement of Si combining Bragg + diffraction (SEPD) and pair distribution function (NOMAD) analysis. A + single shared structure is refined simultaneously against both + datasets. + +## Workshops & Schools + +- [DMSC Summer School](ed-13.ipynb) – A workshop tutorial that + demonstrates a Rietveld refinement of the La0.5Ba0.5CoO3 crystal + structure using time-of-flight neutron powder diffraction data + simulated with McStas. This tutorial is designed for the ESS DMSC + Summer School. diff --git a/docs/user-guide/analysis-workflow/analysis.md b/docs/docs/user-guide/analysis-workflow/analysis.md similarity index 68% rename from docs/user-guide/analysis-workflow/analysis.md rename to docs/docs/user-guide/analysis-workflow/analysis.md index 73086b3b..f0341519 100644 --- a/docs/user-guide/analysis-workflow/analysis.md +++ b/docs/docs/user-guide/analysis-workflow/analysis.md @@ -5,57 +5,62 @@ icon: material/calculator # :material-calculator: Analysis This section provides an overview of **diffraction data analysis** in -EasyDiffraction, focusing on model-dependent analysis, calculation engines, and -minimization techniques. +EasyDiffraction, focusing on model-dependent analysis, calculation +engines, and minimization techniques. -In EasyDiffraction, we focus on **model-dependent analysis**, where a model is -constructed based on prior knowledge of the studied system, and its parameters -are optimized to achieve the best agreement between experimental and calculated -diffraction data. Model-dependent analysis is widely used in neutron and X-ray -scattering data. +In EasyDiffraction, we focus on **model-dependent analysis**, where a +model is constructed based on prior knowledge of the studied system, and +its parameters are optimized to achieve the best agreement between +experimental and calculated diffraction data. Model-dependent analysis +is widely used in neutron and X-ray scattering data. ## Calculation -EasyDiffraction relies on third-party crystallographic libraries, referred to as -**calculation engines** or just **calculators**, to perform the calculations. +EasyDiffraction relies on third-party crystallographic libraries, +referred to as **calculation engines** or just **calculators**, to +perform the calculations. -The calculation engines are used to calculate the diffraction pattern for the -defined model of the studied structure using the instrumental and other required -experiment-related parameters, such as the wavelength, resolution, etc. +The calculation engines are used to calculate the diffraction pattern +for the defined model of the studied structure using the instrumental +and other required experiment-related parameters, such as the +wavelength, resolution, etc. -You do not necessarily need the measured data to perform the calculations, but -you need a structural model and some details about the type of experiment you -want to simulate. +You do not necessarily need the measured data to perform the +calculations, but you need a structural model and some details about the +type of experiment you want to simulate. -EasyDiffraction is designed as a flexible and extensible tool that supports -different **calculation engines** for diffraction pattern calculations. -Currently, we integrate CrysPy, CrysFML, and PDFfit2 libraries as calculation -engines. +EasyDiffraction is designed as a flexible and extensible tool that +supports different **calculation engines** for diffraction pattern +calculations. Currently, we integrate CrysPy, CrysFML, and PDFfit2 +libraries as calculation engines. ### CrysPy Calculator -[CrysPy](https://www.cryspy.fr) is a Python library originally developed for -analysing polarised neutron diffraction data. It is now evolving into a more -general purpose library and covers powders and single crystals, nuclear and -(commensurate) magnetic structures, unpolarised neutron and X-ray diffraction. +[CrysPy](https://www.cryspy.fr) is a Python library originally developed +for analysing polarised neutron diffraction data. It is now evolving +into a more general purpose library and covers powders and single +crystals, nuclear and (commensurate) magnetic structures, unpolarised +neutron and X-ray diffraction. ### CrysFML Calculator -[CrysFML](https://code.ill.fr/scientific-software/CrysFML2008) library is a -collection of Fortran modules for crystallographic computations. It is used in -the software package [FullProf](https://www.ill.eu/sites/fullprof/), and we are -currently working on its integration into EasyDiffraction. +[CrysFML](https://code.ill.fr/scientific-software/CrysFML2008) library +is a collection of Fortran modules for crystallographic computations. It +is used in the software package +[FullProf](https://www.ill.eu/sites/fullprof/), and we are currently +working on its integration into EasyDiffraction. ### PDFfit2 Calculator -[PDFfit2](https://github.com/diffpy/diffpy.pdffit2/) is a Python library for -calculating the pair distribution function (PDF) from crystallographic models. +[PDFfit2](https://github.com/diffpy/diffpy.pdffit2/) is a Python library +for calculating the pair distribution function (PDF) from +crystallographic models. ### Set Calculator -The calculator is automatically selected based on the experiment type (e.g., -`cryspy` for Bragg diffraction, `pdffit` for total scattering). To show the -supported calculation engines for a specific experiment: +The calculator is automatically selected based on the experiment type +(e.g., `cryspy` for Bragg diffraction, `pdffit` for total scattering). +To show the supported calculation engines for a specific experiment: ```python project.experiments['hrpt'].show_supported_calculator_types() @@ -77,9 +82,9 @@ project.experiments['hrpt'].calculator_type = 'cryspy' ## Minimization / Optimization -The process of refining model parameters involves iterating through multiple -steps until the calculated data sufficiently matches the experimental data. This -process is illustrated in the following diagram: +The process of refining model parameters involves iterating through +multiple steps until the calculated data sufficiently matches the +experimental data. This process is illustrated in the following diagram: ```mermaid flowchart LR @@ -95,31 +100,34 @@ flowchart LR d-- Threshold
reached -->e ``` -Like the calculation engines, EasyDiffraction is designed to utilize various -third-party libraries for model refinement and parameter optimization. These -libraries provide robust curve fitting and uncertainty estimation tools. +Like the calculation engines, EasyDiffraction is designed to utilize +various third-party libraries for model refinement and parameter +optimization. These libraries provide robust curve fitting and +uncertainty estimation tools. ### Lmfit Minimizer Most of the examples in this section will use the -[lmfit](https://lmfit.github.io/lmfit-py/) package, which provides a high-level -interface to non-linear optimisation and curve fitting problems for Python. It -is one of the tools that can be used to fit models to the experimental data. +[lmfit](https://lmfit.github.io/lmfit-py/) package, which provides a +high-level interface to non-linear optimisation and curve fitting +problems for Python. It is one of the tools that can be used to fit +models to the experimental data. ### Bumps Minimizer Another package that can be used for the same purpose is -[bumps](https://bumps.readthedocs.io/en/latest/). In addition to traditional -optimizers which search for the best minimum they can find in the search space, -bumps provides Bayesian uncertainty analysis which explores all viable minima -and finds confidence intervals on the parameters based on uncertainty in the -measured values. +[bumps](https://bumps.readthedocs.io/en/latest/). In addition to +traditional optimizers which search for the best minimum they can find +in the search space, bumps provides Bayesian uncertainty analysis which +explores all viable minima and finds confidence intervals on the +parameters based on uncertainty in the measured values. ### DFO-LS Minimizer -[DFO-LS](https://github.com/numericalalgorithmsgroup/dfols) (Derivative-Free -Optimizer for Least-Squares) is a Python library for solving nonlinear -least-squares minimization, without requiring derivatives of the objective. +[DFO-LS](https://github.com/numericalalgorithmsgroup/dfols) +(Derivative-Free Optimizer for Least-Squares) is a Python library for +solving nonlinear least-squares minimization, without requiring +derivatives of the objective. ### Set Minimizer @@ -148,9 +156,10 @@ project.analysis.current_minimizer = 'lmfit' ### Fit Mode -In EasyDiffraction, you can set the **fit mode** to control how the refinement -process is performed. The fit mode determines whether the refinement is -performed independently for each experiment or jointly across all experiments. +In EasyDiffraction, you can set the **fit mode** to control how the +refinement process is performed. The fit mode determines whether the +refinement is performed independently for each experiment or jointly +across all experiments. The supported fit modes are: @@ -173,14 +182,14 @@ print(project.analysis.fit_mode.mode.value) ### Perform Fit -Refining the structure and experiment parameters against measured data is -usually divided into several steps, where each step involves adding or removing -parameters to be refined, calculating the model data, and comparing it to the -experimental data as shown in the diagram above. +Refining the structure and experiment parameters against measured data +is usually divided into several steps, where each step involves adding +or removing parameters to be refined, calculating the model data, and +comparing it to the experimental data as shown in the diagram above. -To select the parameters to be refined, you can set the attribute `free` of the -parameters to `True`. This indicates that the parameter is free to be optimized -during the refinement process. +To select the parameters to be refined, you can set the attribute `free` +of the parameters to `True`. This indicates that the parameter is free +to be optimized during the refinement process. Here is an example of how to set parameters to be refined: @@ -195,15 +204,16 @@ project.experiments['hrpt'].background['10'].y.free = True project.experiments['hrpt'].background['165'].y.free = True ``` -After setting the parameters to be refined, you can perform the fit using the -`fit` method of the `analysis` object: +After setting the parameters to be refined, you can perform the fit +using the `fit` method of the `analysis` object: ```python project.analysis.fit() ``` -This method will iterate through the defined steps, adjusting the parameters -until the calculated data sufficiently matches the experimental data. +This method will iterate through the defined steps, adjusting the +parameters until the calculated data sufficiently matches the +experimental data. An example of the output after performing the fit is: @@ -233,9 +243,9 @@ Fit results 📈 Fitted parameters: ``` -Now, you can inspect the fitted parameters to see how they have changed during -the refinement process, select more parameters to be refined, and perform -additional fits as needed. +Now, you can inspect the fitted parameters to see how they have changed +during the refinement process, select more parameters to be refined, and +perform additional fits as needed. To plot the measured vs calculated data after the fit, you can use the `plot_meas_vs_calc` method of the `analysis` object: @@ -246,16 +256,16 @@ project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) ## Constraints -In EasyDiffraction, you can define **constraints** on the model parameters to -ensure that they remain within a specific range or follow a certain relationship -during the refinement process. +In EasyDiffraction, you can define **constraints** on the model +parameters to ensure that they remain within a specific range or follow +a certain relationship during the refinement process. ### Setting Aliases -Before setting constraints, you need to set aliases for the parameters you want -to constrain. This can be done using the `add` method of the `aliases` object. -Aliases are used to reference parameters in a more readable way, making it -easier to manage constraints. +Before setting constraints, you need to set aliases for the parameters +you want to constrain. This can be done using the `add` method of the +`aliases` object. Aliases are used to reference parameters in a more +readable way, making it easier to manage constraints. An example of setting aliases for parameters in a structure: @@ -283,11 +293,11 @@ project.analysis.aliases.create( ### Setting Constraints -Now that you have set the aliases, you can define constraints using the `add` -method of the `constraints` object. Constraints are defined by specifying the -**left-hand side (lhs) alias** and the **right-hand side (rhs) expression**. The -rhs expression can be a simple alias or a more complex expression involving -other aliases. +Now that you have set the aliases, you can define constraints using the +`add` method of the `constraints` object. Constraints are defined by +specifying the **left-hand side (lhs) alias** and the **right-hand side +(rhs) expression**. The rhs expression can be a simple alias or a more +complex expression involving other aliases. An example of setting constraints for the aliases defined above: @@ -303,15 +313,16 @@ project.analysis.constraints.create( ) ``` -These constraints ensure that the `biso_Ba` parameter is equal to `biso_La`, and -the `occ_Ba` parameter is equal to `1 - occ_La`. This means that the occupancy -of the Ba atom will always be adjusted based on the occupancy of the La atom, -and the isotropic displacement parameter for Ba will be equal to that of La -during the refinement process. +These constraints ensure that the `biso_Ba` parameter is equal to +`biso_La`, and the `occ_Ba` parameter is equal to `1 - occ_La`. This +means that the occupancy of the Ba atom will always be adjusted based on +the occupancy of the La atom, and the isotropic displacement parameter +for Ba will be equal to that of La during the refinement process. ### Viewing Constraints -To view the defined constraints, you can use the `show_constraints` method: +To view the defined constraints, you can use the `show_constraints` +method: ```python project.analysis.show_constraints() @@ -361,8 +372,9 @@ Example output: ## Saving an Analysis -Saving the project, as described in the [Project](project.md) section, will also -save the analysis settings to the `analysis.cif` inside the project directory. +Saving the project, as described in the [Project](project.md) section, +will also save the analysis settings to the `analysis.cif` inside the +project directory.
diff --git a/docs/user-guide/analysis-workflow/experiment.md b/docs/docs/user-guide/analysis-workflow/experiment.md similarity index 84% rename from docs/user-guide/analysis-workflow/experiment.md rename to docs/docs/user-guide/analysis-workflow/experiment.md index a62d9822..c121d6a9 100644 --- a/docs/user-guide/analysis-workflow/experiment.md +++ b/docs/docs/user-guide/analysis-workflow/experiment.md @@ -4,10 +4,10 @@ icon: material/microscope # :material-microscope: Experiment -An **Experiment** in EasyDiffraction includes the measured diffraction data -along with all relevant parameters that describe the experimental setup and -associated conditions. This can include information about the instrumental -resolution, peak shape, background, etc. +An **Experiment** in EasyDiffraction includes the measured diffraction +data along with all relevant parameters that describe the experimental +setup and associated conditions. This can include information about the +instrumental resolution, peak shape, background, etc. ## Defining an Experiment @@ -15,23 +15,26 @@ EasyDiffraction allows you to: - **Load an existing experiment** from a file (**CIF** format). Both the metadata and measured data are expected to be in CIF format. -- **Manually define** a new experiment by specifying its type, other necessary - experimental parameters, as well as load measured data. This is useful when - you want to create an experiment from scratch or when you have a measured data - file in a non-CIF format (e.g., `.xye`, `.xy`). - -Below, you will find instructions on how to define and manage experiments in -EasyDiffraction. It is assumed that you have already created a `project` object, -as described in the [Project](project.md) section as well as defined its -`structures`, as described in the [Structure](model.md) section. +- **Manually define** a new experiment by specifying its type, other + necessary experimental parameters, as well as load measured data. This + is useful when you want to create an experiment from scratch or when + you have a measured data file in a non-CIF format (e.g., `.xye`, + `.xy`). + +Below, you will find instructions on how to define and manage +experiments in EasyDiffraction. It is assumed that you have already +created a `project` object, as described in the [Project](project.md) +section as well as defined its `structures`, as described in the +[Structure](model.md) section. ### Adding from CIF -This is the most straightforward way to define an experiment in EasyDiffraction. -If you have a crystallographic information file (CIF) for your experiment, that -contains both the necessary information (metadata) about the experiment as well -as the measured data, you can add it to your `project.experiments` collection -using the `add_from_cif_path` method. In this case, the name of the experiment +This is the most straightforward way to define an experiment in +EasyDiffraction. If you have a crystallographic information file (CIF) +for your experiment, that contains both the necessary information +(metadata) about the experiment as well as the measured data, you can +add it to your `project.experiments` collection using the +`add_from_cif_path` method. In this case, the name of the experiment will be taken from CIF. ```python @@ -51,9 +54,9 @@ project.experiments.add_from_cif_str(cif_string) ``` Accessing the experiment after adding it will also be done through the -`experiments` object of the `project` instance. The name of the experiment will -be the same as the data block id in the CIF file. For example, if the CIF file -contains a data block with the id `hrpt`, +`experiments` object of the `project` instance. The name of the +experiment will be the same as the data block id in the CIF file. For +example, if the CIF file contains a data block with the id `hrpt`, @@ -77,20 +80,22 @@ project.experiments['hrpt'] ### Defining Manually -If you do not have a CIF file or prefer to define the experiment manually, you -can use the `add_from_data_path` method of the `experiments` object of the -`project` instance. In this case, you will need to specify the **name** of the -experiment, which will be used to reference it later, as well as **data_path** -to the measured data file (e.g., `.xye`, `.xy`). Supported formats are described -in the [Measured Data Category](#5-measured-data-category) section. +If you do not have a CIF file or prefer to define the experiment +manually, you can use the `add_from_data_path` method of the +`experiments` object of the `project` instance. In this case, you will +need to specify the **name** of the experiment, which will be used to +reference it later, as well as **data_path** to the measured data file +(e.g., `.xye`, `.xy`). Supported formats are described in the +[Measured Data Category](#measured-data-category) section. -Optionally, you can also specify the additional parameters that define the -**type of experiment** you want to create. If you do not specify any of these -parameters, the default values will be used, which are the first in the list of -supported options for each parameter: +Optionally, you can also specify the additional parameters that define +the **type of experiment** you want to create. If you do not specify any +of these parameters, the default values will be used, which are the +first in the list of supported options for each parameter: - **sample_form**: The form of the sample (powder, single crystal). -- **beam_mode**: The mode of the beam (constant wavelength, time-of-flight). +- **beam_mode**: The mode of the beam (constant wavelength, + time-of-flight). - **radiation_probe**: The type of radiation used (neutron, X-ray). - **scattering_type**: The type of scattering (bragg, total). @@ -100,8 +105,8 @@ supported options for each parameter: these parameters. If you need to change them, you must create a new experiment or redefine the existing one. -Here is an example of how to add an experiment with all relevant components -explicitly defined: +Here is an example of how to add an experiment with all relevant +components explicitly defined: ```python # Add an experiment with default parameters, based on the specified type. @@ -125,9 +130,9 @@ project.experiments.add_from_data_path( ) ``` -If you do not have measured data for fitting and only want to view the simulated -pattern, you can define an experiment without measured data using the `create` -method: +If you do not have measured data for fitting and only want to view the +simulated pattern, you can define an experiment without measured data +using the `create` method: ```python # Add an experiment without measured data @@ -159,23 +164,24 @@ project.experiments.add(experiment) ## Modifying Parameters -When an experiment is added, it is created with a set of default parameters that -you can modify to match your specific experimental setup. All parameters are -grouped into categories based on their function, making it easier to manage and -understand the different aspects of the experiment: - -1. **Instrument Category**: Defines the instrument configuration, including - wavelength, two-theta offset, and resolution parameters. -2. **Peak Category**: Specifies the peak profile type and its parameters, such - as broadening and asymmetry. -3. **Background Category**: Defines the background type and allows you to add - background points. -4. **Linked Phases Category**: Links the structure defined in the previous step - to the experiment, allowing you to specify the scale factor for the linked - phase. -5. **Measured Data Category**: Contains the measured data. The expected format - depends on the experiment type, but generally includes columns for 2θ angle - or TOF and intensity. +When an experiment is added, it is created with a set of default +parameters that you can modify to match your specific experimental +setup. All parameters are grouped into categories based on their +function, making it easier to manage and understand the different +aspects of the experiment: + +1. **Instrument Category**: Defines the instrument configuration, + including wavelength, two-theta offset, and resolution parameters. +2. **Peak Category**: Specifies the peak profile type and its + parameters, such as broadening and asymmetry. +3. **Background Category**: Defines the background type and allows you + to add background points. +4. **Linked Phases Category**: Links the structure defined in the + previous step to the experiment, allowing you to specify the scale + factor for the linked phase. +5. **Measured Data Category**: Contains the measured data. The expected + format depends on the experiment type, but generally includes columns + for 2θ angle or TOF and intensity. ### 1. Instrument Category { #instrument-category } @@ -230,10 +236,10 @@ project.experiments['hrpt'].linked_phases.create(id='lbco', scale=10.0) ### 6. Measured Data Category { #measured-data-category } -If you do not have a CIF file for your experiment, you can load measured data -from a file in a supported format. The measured data will be automatically -converted into CIF format and added to the experiment. The expected format -depends on the experiment type. +If you do not have a CIF file for your experiment, you can load measured +data from a file in a supported format. The measured data will be +automatically converted into CIF format and added to the experiment. The +expected format depends on the experiment type. #### Supported data file formats: @@ -245,8 +251,8 @@ depends on the experiment type. - [\_pd_meas.2theta_scan](../parameters/pd_meas.md) - [\_pd_meas.intensity_total](../parameters/pd_meas.md) -If no **standard deviations** are provided, they are automatically calculated as -the **square root** of measured intensities. +If no **standard deviations** are provided, they are automatically +calculated as the **square root** of measured intensities. Optional comments with `#` are possible in data file headers. @@ -602,5 +608,5 @@ loop_ --- -Now that the experiment has been defined, you can proceed to the next step: -[Analysis](analysis.md). +Now that the experiment has been defined, you can proceed to the next +step: [Analysis](analysis.md). diff --git a/docs/docs/user-guide/analysis-workflow/index.md b/docs/docs/user-guide/analysis-workflow/index.md new file mode 100644 index 00000000..84598210 --- /dev/null +++ b/docs/docs/user-guide/analysis-workflow/index.md @@ -0,0 +1,37 @@ +# Analysis Workflow + +To streamline the **data analysis process**, EasyDiffraction follows a +structured workflow divided into **five key steps**: + +```mermaid +flowchart LR + a(Project) + b(Model) + c(Experiment) + d(Analysis) + e(Summary) + a --> b + b --> c + c --> d + d --> e +``` + +- [:material-archive: Project](project.md) – Establish a **project** as + a container for structure and experiment parameters, measured and + calculated data, analysis settings and results. +- [:material-puzzle: Structure](model.md) – Load an existing + **crystallographic model** in CIF format or define a new one from + scratch. +- [:material-microscope: Experiment](experiment.md) – Import + **experimental diffraction data** and configure **instrumental** and + other relevant parameters. +- [:material-calculator: Analysis](analysis.md) – **Calculate the + diffraction pattern** and **optimize the structural model** by + refining its parameters to match experimental measurements. +- [:material-clipboard-text: Summary](summary.md) – Generate a + **report** summarizing the results of the analysis, including refined + parameters. + +Each step is described in detail in its respective section, guiding +users through the **entire diffraction data analysis workflow** in +EasyDiffraction. diff --git a/docs/user-guide/analysis-workflow/model.md b/docs/docs/user-guide/analysis-workflow/model.md similarity index 77% rename from docs/user-guide/analysis-workflow/model.md rename to docs/docs/user-guide/analysis-workflow/model.md index c437ea62..95e309cd 100644 --- a/docs/user-guide/analysis-workflow/model.md +++ b/docs/docs/user-guide/analysis-workflow/model.md @@ -5,35 +5,38 @@ icon: material/puzzle # :material-puzzle: Structure The **Structure** in EasyDiffraction represents the **crystallographic -structure** used to calculate the diffraction pattern, which is then fitted to -the **experimentally measured data** to refine the structural parameters. +structure** used to calculate the diffraction pattern, which is then +fitted to the **experimentally measured data** to refine the structural +parameters. EasyDiffraction allows you to: - **Load an existing model** from a file (**CIF** format). -- **Manually define** a new structure by specifying crystallographic parameters. +- **Manually define** a new structure by specifying crystallographic + parameters. -Below, you will find instructions on how to define and manage crystallographic -models in EasyDiffraction. It is assumed that you have already created a -`project` object, as described in the [Project](project.md) section. +Below, you will find instructions on how to define and manage +crystallographic models in EasyDiffraction. It is assumed that you have +already created a `project` object, as described in the +[Project](project.md) section. ## Adding a Model from CIF -This is the most straightforward way to define a structure in EasyDiffraction. -If you have a crystallographic information file (CIF) for your structure, you -can add it to your project using the `add_from_cif_path` method of the -`project.structures` collection. In this case, the name of the model will be -taken from CIF. +This is the most straightforward way to define a structure in +EasyDiffraction. If you have a crystallographic information file (CIF) +for your structure, you can add it to your project using the +`add_from_cif_path` method of the `project.structures` collection. In +this case, the name of the model will be taken from CIF. ```python # Load a phase from a CIF file project.structures.add_from_cif_path('data/lbco.cif') ``` -Accessing the model after loading it will be done through the `structures` -collection of the `project` instance. The name of the model will be the same as -the data block id in the CIF file. For example, if the CIF file contains a data -block with the id `lbco`, +Accessing the model after loading it will be done through the +`structures` collection of the `project` instance. The name of the model +will be the same as the data block id in the CIF file. For example, if +the CIF file contains a data block with the id `lbco`, @@ -57,10 +60,10 @@ project.structures['lbco'] ## Defining a Model Manually -If you do not have a CIF file or prefer to define the model manually, you can -use the `create` method of the `structures` object of the `project` instance. In -this case, you will need to specify the name of the model, which will be used to -reference it later. +If you do not have a CIF file or prefer to define the model manually, +you can use the `create` method of the `structures` object of the +`project` instance. In this case, you will need to specify the name of +the model, which will be used to reference it later. ```python # Add a structure with default parameters @@ -68,15 +71,17 @@ reference it later. project.structures.create(name='nacl') ``` -The `add` method creates a new structure with default parameters. You can then -modify its parameters to match your specific crystallographic structure. All -parameters are grouped into the following categories, which makes it easier to -manage the model: +The `add` method creates a new structure with default parameters. You +can then modify its parameters to match your specific crystallographic +structure. All parameters are grouped into the following categories, +which makes it easier to manage the model: -1. **Space Group Category**: Defines the symmetry of the crystal structure. -2. **Cell Category**: Specifies the dimensions and angles of the unit cell. -3. **Atom Sites Category**: Describes the positions and properties of atoms - within the unit cell. +1. **Space Group Category**: Defines the symmetry of the crystal + structure. +2. **Cell Category**: Specifies the dimensions and angles of the unit + cell. +3. **Atom Sites Category**: Describes the positions and properties of + atoms within the unit cell. ### 1. Space Group Category { #space-group-category } @@ -177,10 +182,10 @@ Structure 🧩 'lbco' as cif ## Saving a Model -Saving the project, as described in the [Project](project.md) section, will also -save the model. Each model is saved as a separate CIF file in the `structures` -subdirectory of the project directory. The project file contains references to -these files. +Saving the project, as described in the [Project](project.md) section, +will also save the model. Each model is saved as a separate CIF file in +the `structures` subdirectory of the project directory. The project file +contains references to these files. Below is an example of the saved CIF file for the `lbco` model: @@ -223,5 +228,5 @@ O O 0 0.5 0.5 c 1 Biso 1.4041 --- -Now that the crystallographic model has been defined and added to the project, -you can proceed to the next step: [Experiment](experiment.md). +Now that the crystallographic model has been defined and added to the +project, you can proceed to the next step: [Experiment](experiment.md). diff --git a/docs/user-guide/analysis-workflow/project.md b/docs/docs/user-guide/analysis-workflow/project.md similarity index 85% rename from docs/user-guide/analysis-workflow/project.md rename to docs/docs/user-guide/analysis-workflow/project.md index 45e1d9c5..ad41f852 100644 --- a/docs/user-guide/analysis-workflow/project.md +++ b/docs/docs/user-guide/analysis-workflow/project.md @@ -4,25 +4,26 @@ icon: material/archive # :material-archive: Project -The **Project** serves as a container for all data and metadata associated with -a particular data analysis task. It acts as the top-level entity in -EasyDiffraction, ensuring structured organization and easy access to relevant -information. Each project can contain multiple **experimental datasets**, with -each dataset containing contribution from multiple **structures**. +The **Project** serves as a container for all data and metadata +associated with a particular data analysis task. It acts as the +top-level entity in EasyDiffraction, ensuring structured organization +and easy access to relevant information. Each project can contain +multiple **experimental datasets**, with each dataset containing +contribution from multiple **structures**. EasyDiffraction allows you to: - **Manually create** a new project by specifying its metadata. - **Load an existing project** from a file (**CIF** format). -Below are instructions on how to set up a project in EasyDiffraction. It is -assumed that you have already imported the `easydiffraction` package, as -described in the [First Steps](../first-steps.md) section. +Below are instructions on how to set up a project in EasyDiffraction. It +is assumed that you have already imported the `easydiffraction` package, +as described in the [First Steps](../first-steps.md) section. ## Creating a Project Manually -You can manually create a new project and specify its short **name**, **title** -and **description**. All these parameters are optional. +You can manually create a new project and specify its short **name**, +**title** and **description**. All these parameters are optional. ```py # Create a new project @@ -44,10 +45,11 @@ Saving the initial project requires specifying the directory path: project.save_as(dir_path='lbco_hrpt') ``` -If working in the interactive mode in a Jupyter notebook or similar environment, -you can also save the project after every significant change. This is useful for -keeping track of changes and ensuring that your work is not lost. If you already -saved the project with `save_as`, you can just call the `save`: +If working in the interactive mode in a Jupyter notebook or similar +environment, you can also save the project after every significant +change. This is useful for keeping track of changes and ensuring that +your work is not lost. If you already saved the project with `save_as`, +you can just call the `save`: ```python project.save() @@ -55,8 +57,9 @@ project.save() ## Loading a Project from CIF -If you have an existing project, you can load it directly from a CIF file. This -is useful for reusing previously defined projects or sharing them with others. +If you have an existing project, you can load it directly from a CIF +file. This is useful for reusing previously defined projects or sharing +them with others. ```python project.load('data/lbco_hrpt.cif') @@ -89,8 +92,8 @@ The example below illustrates a typical **project structure** for a ## Project Files -Below is a complete project example stored in the `La0.5Ba0.5CoO3` directory, -showing the contents of all files in the project. +Below is a complete project example stored in the `La0.5Ba0.5CoO3` +directory, showing the contents of all files in the project. !!! warning "Important" @@ -101,8 +104,8 @@ showing the contents of all files in the project. ### 1. project.cif -This file provides an overview of the project, including file names of the -**structures** and **experiments** associated with the project. +This file provides an overview of the project, including file names of +the **structures** and **experiments** associated with the project. @@ -127,9 +130,9 @@ hrpt.cif ### 2. structures / lbco.cif -This file contains crystallographic information associated with the structure -model, including **space group**, **unit cell parameters**, and **atomic -positions**. +This file contains crystallographic information associated with the +structure model, including **space group**, **unit cell parameters**, +and **atomic positions**. @@ -168,9 +171,9 @@ O O 0 0.5 0.5 c 1 Biso 1.4041 ### 3. experiments / hrpt.cif -This file contains the **experiment type**, **instrumental parameters**, **peak -parameters**, **associated phases**, **background parameters** and **measured -diffraction data**. +This file contains the **experiment type**, **instrumental parameters**, +**peak parameters**, **associated phases**, **background parameters** +and **measured diffraction data**. @@ -236,8 +239,8 @@ loop_ ### 4. analysis.cif -This file contains settings used for data analysis, including the choice of -**calculation** and **fitting** engines, as well as user defined +This file contains settings used for data analysis, including the choice +of **calculation** and **fitting** engines, as well as user defined **constraints**. diff --git a/docs/user-guide/analysis-workflow/summary.md b/docs/docs/user-guide/analysis-workflow/summary.md similarity index 72% rename from docs/user-guide/analysis-workflow/summary.md rename to docs/docs/user-guide/analysis-workflow/summary.md index 4790f857..34a89b61 100644 --- a/docs/user-guide/analysis-workflow/summary.md +++ b/docs/docs/user-guide/analysis-workflow/summary.md @@ -5,20 +5,20 @@ icon: material/clipboard-text # :material-clipboard-text: Summary The **Summary** section represents the final step in the data processing -workflow. It involves generating a **summary report** that consolidates the -results of the diffraction data analysis, providing a comprehensive overview of -the model refinement process and its outcomes. +workflow. It involves generating a **summary report** that consolidates +the results of the diffraction data analysis, providing a comprehensive +overview of the model refinement process and its outcomes. ## Contents of the Summary Report The summary report includes key details such as: -- Final refined model parameters – Optimized crystallographic and instrumental - parameters. -- Goodness-of-fit indicators – Metrics such as R-factors, chi-square (χ²), and - residuals. -- Graphical representation – Visualization of experimental vs. calculated - diffraction patterns. +- Final refined model parameters – Optimized crystallographic and + instrumental parameters. +- Goodness-of-fit indicators – Metrics such as R-factors, chi-square + (χ²), and residuals. +- Graphical representation – Visualization of experimental vs. + calculated diffraction patterns. ## Viewing the Summary Report @@ -36,8 +36,9 @@ including model parameters, fit statistics, and data visualizations. ## Saving a Summary -Saving the project, as described in the [Project](project.md) section, will also -save the summary report to the `summary.cif` inside the project directory. +Saving the project, as described in the [Project](project.md) section, +will also save the summary report to the `summary.cif` inside the +project directory. ![](../assets/images/user-guide/data-acquisition_instrument.png){ width="450", loading=lazy } @@ -42,10 +43,10 @@ Credits: DOI 10.1126/science.1238932 ## Data Reduction -Data reduction involves processing the raw data to remove background noise, -correct for instrumental effects, and convert the data into a more usable -format. The goal is to produce a clean and reliable dataset suitable for -analysis. +Data reduction involves processing the raw data to remove background +noise, correct for instrumental effects, and convert the data into a +more usable format. The goal is to produce a clean and reliable dataset +suitable for analysis. ![](../assets/images/user-guide/data-reduction_1d-pattern.png){ width="450", loading=lazy } @@ -57,28 +58,32 @@ Credits: DOI 10.1126/science.1238932 ## Data Analysis -Data analysis uses the reduced data to extract meaningful information about the -crystallographic structure. This may include determining the crystal or magnetic -structure, identifying phases, performing quantitative analysis, etc. - -Analysis often involves comparing experimental data with data calculated from a -crystallographic model to validate and interpret the results. For powder -diffraction, techniques such as Rietveld or Le Bail refinement may be used. - -In EasyDiffraction, we focus on this **model-dependent analysis**. A model is -built using prior knowledge of the system, and its parameters are optimized to -achieve the best agreement between experimental and calculated diffraction data. - -By "model", we usually refer to a **crystallographic model** of the sample. This -includes unit cell parameters, space group, atomic positions, thermal -parameters, and more. However, the term "model" also encompasses experimental -aspects such as instrumental resolution, background, peak shape, etc. Therefore, -EasyDiffraction separates the model into two parts: the **structure** and the -**experiment**. - -The aim of data analysis is to refine the structural parameters of the sample by -minimizing the difference (or **residual**) between the experimental and -calculated data — and this is exactly where EasyDiffraction comes into play. +Data analysis uses the reduced data to extract meaningful information +about the crystallographic structure. This may include determining the +crystal or magnetic structure, identifying phases, performing +quantitative analysis, etc. + +Analysis often involves comparing experimental data with data calculated +from a crystallographic model to validate and interpret the results. For +powder diffraction, techniques such as Rietveld or Le Bail refinement +may be used. + +In EasyDiffraction, we focus on this **model-dependent analysis**. A +model is built using prior knowledge of the system, and its parameters +are optimized to achieve the best agreement between experimental and +calculated diffraction data. + +By "model", we usually refer to a **crystallographic model** of the +sample. This includes unit cell parameters, space group, atomic +positions, thermal parameters, and more. However, the term "model" also +encompasses experimental aspects such as instrumental resolution, +background, peak shape, etc. Therefore, EasyDiffraction separates the +model into two parts: the **structure** and the **experiment**. + +The aim of data analysis is to refine the structural parameters of the +sample by minimizing the difference (or **residual**) between the +experimental and calculated data — and this is exactly where +EasyDiffraction comes into play. ![](../assets/images/user-guide/data-analysis_refinement.png){ width="450", loading=lazy } diff --git a/docs/user-guide/data-format.md b/docs/docs/user-guide/data-format.md similarity index 76% rename from docs/user-guide/data-format.md rename to docs/docs/user-guide/data-format.md index 63e2e52e..da7c92b1 100644 --- a/docs/user-guide/data-format.md +++ b/docs/docs/user-guide/data-format.md @@ -1,46 +1,48 @@ # Data Format -Before starting the data analysis workflow, it is important to define the **data -formats** used in EasyDiffraction. +Before starting the data analysis workflow, it is important to define +the **data formats** used in EasyDiffraction. ## Crystallographic Information File -Each software package typically uses its own **data format** and **parameter -names** for storing and sharing data. In EasyDiffraction, we use the -**Crystallographic Information File (CIF)** format, which is widely used in -crystallography and materials science. It provides both a human-readable syntax -and a set of dictionaries that define the meaning of each parameter. +Each software package typically uses its own **data format** and +**parameter names** for storing and sharing data. In EasyDiffraction, we +use the **Crystallographic Information File (CIF)** format, which is +widely used in crystallography and materials science. It provides both a +human-readable syntax and a set of dictionaries that define the meaning +of each parameter. These dictionaries are maintained by the [International Union of Crystallography (IUCr)](https://www.iucr.org). The base dictionary, **coreCIF**, contains the most common parameters in -crystallography. The **pdCIF** dictionary covers parameters specific to powder -diffraction, **magCIF** is used for magnetic structure analysis. +crystallography. The **pdCIF** dictionary covers parameters specific to +powder diffraction, **magCIF** is used for magnetic structure analysis. -As most parameters needed for diffraction data analysis are already covered by -IUCr dictionaries, EasyDiffraction uses the strict **CIF format** and follows -these dictionaries as closely as possible — for both input and output — -throughout the workflow described in the +As most parameters needed for diffraction data analysis are already +covered by IUCr dictionaries, EasyDiffraction uses the strict **CIF +format** and follows these dictionaries as closely as possible — for +both input and output — throughout the workflow described in the [Analysis Workflow](analysis-workflow/index.md) section. The key advantage of CIF is the standardized naming of parameters and -categories, which promotes interoperability and familiarity among researchers. +categories, which promotes interoperability and familiarity among +researchers. If a required parameter is not defined in the standard dictionaries, EasyDiffraction introduces **custom CIF keywords**, documented in the -[Parameters](parameters.md) section under the **CIF name for serialization** -columns. +[Parameters](parameters.md) section under the **CIF name for +serialization** columns. ## Format Comparison -Below, we compare **CIF** with another common data format in programming: -**JSON**. +Below, we compare **CIF** with another common data format in +programming: **JSON**. ### Scientific Journals Let's assume the following structural data for La₀.₅Ba₀.₅CoO₃ (LBCO), as -reported in a scientific publication. These parameters are to be refined during -diffraction data analysis: +reported in a scientific publication. These parameters are to be refined +during diffraction data analysis: Table 1. Crystallographic data. Space group: _Pm3̅m_. @@ -53,8 +55,8 @@ Table 1. Crystallographic data. Space group: _Pm3̅m_. | beta | 90.0 | | gamma | 90.0 | -Table 2. Atomic coordinates (_x_, _y_, _z_), occupancies (occ) and isotropic -displacement parameters (_Biso_) +Table 2. Atomic coordinates (_x_, _y_, _z_), occupancies (occ) and +isotropic displacement parameters (_Biso_) | Label | Type | x | y | z | occ | Biso | | ----- | ---- | --- | --- | --- | --- | ------ | @@ -102,17 +104,17 @@ O O 0 0.5 0.5 c 1 Biso 1.4041 -Here, unit cell parameters are grouped under the `_cell` category, and atomic -positions under the `_atom_site` category. The `loop_` keyword indicates that -multiple rows follow for the listed parameters. Each atom is identified using -`_atom_site.label`. +Here, unit cell parameters are grouped under the `_cell` category, and +atomic positions under the `_atom_site` category. The `loop_` keyword +indicates that multiple rows follow for the listed parameters. Each atom +is identified using `_atom_site.label`. ### JSON -Representing the same data in **JSON** results in a format that is more verbose -and less human-readable, especially for large datasets. JSON is ideal for -structured data in programming environments, whereas CIF is better suited for -human-readable crystallographic data. +Representing the same data in **JSON** results in a format that is more +verbose and less human-readable, especially for large datasets. JSON is +ideal for structured data in programming environments, whereas CIF is +better suited for human-readable crystallographic data. ```json { @@ -173,11 +175,11 @@ human-readable crystallographic data. ## Experiment Definition -The previous example described the **structure** (crystallographic model), but -how is the **experiment** itself represented? +The previous example described the **structure** (crystallographic +model), but how is the **experiment** itself represented? -The experiment is also saved as a CIF file. For example, background intensity in -a powder diffraction experiment might be represented as: +The experiment is also saved as a CIF file. For example, background +intensity in a powder diffraction experiment might be represented as: @@ -197,13 +199,13 @@ loop_ -More details on how to define the experiment in CIF format are provided in the -[Experiment](analysis-workflow/experiment.md) section. +More details on how to define the experiment in CIF format are provided +in the [Experiment](analysis-workflow/experiment.md) section. ## Other Input/Output Blocks -EasyDiffraction uses CIF consistently throughout its workflow, including in the -following blocks: +EasyDiffraction uses CIF consistently throughout its workflow, including +in the following blocks: - **project**: contains the project information - **structure**: defines the structure @@ -217,11 +219,13 @@ Example CIF files for each block are provided in the ## Other Data Formats -While CIF is the primary format in EasyDiffraction, we also support other -formats for importing measured data. These include plain text files with -multiple columns. The meaning of the columns depends on the experiment type. +While CIF is the primary format in EasyDiffraction, we also support +other formats for importing measured data. These include plain text +files with multiple columns. The meaning of the columns depends on the +experiment type. -For example, in a standard constant-wavelength powder diffraction experiment: +For example, in a standard constant-wavelength powder diffraction +experiment: - Column 1: 2θ angle - Column 2: intensity diff --git a/docs/user-guide/first-steps.md b/docs/docs/user-guide/first-steps.md similarity index 56% rename from docs/user-guide/first-steps.md rename to docs/docs/user-guide/first-steps.md index acb50bec..b4366684 100644 --- a/docs/user-guide/first-steps.md +++ b/docs/docs/user-guide/first-steps.md @@ -1,44 +1,44 @@ # First Steps -This section introduces the basic usage of the EasyDiffraction Python API. -You'll learn how to import the package, use core classes and utility functions, -and access built-in helper methods to streamline diffraction data analysis -workflows. +This section introduces the basic usage of the EasyDiffraction Python +API. You'll learn how to import the package, use core classes and +utility functions, and access built-in helper methods to streamline +diffraction data analysis workflows. ## Importing EasyDiffraction ### Importing the entire package -To start using EasyDiffraction, first import the package in your Python script -or Jupyter Notebook. This can be done with the following command: +To start using EasyDiffraction, first import the package in your Python +script or Jupyter Notebook. This can be done with the following command: ```python import easydiffraction ``` -Alternatively, you can import it with an alias to avoid naming conflicts and for -convenience: +Alternatively, you can import it with an alias to avoid naming conflicts +and for convenience: ```python import easydiffraction as ed ``` -The latter syntax allows you to access all the modules and classes within the -package using the `ed` prefix. For example, you can create a project instance -like this: +The latter syntax allows you to access all the modules and classes +within the package using the `ed` prefix. For example, you can create a +project instance like this: ```python project = ed.Project() ``` A complete tutorial using the `import` syntax can be found -[here](../../tutorials/ed-3/). +[here](../tutorials/ed-3.ipynb). ### Importing specific parts -Alternatively, you can import specific classes or methods from the package. For -example, you can import the `Project`, `Structure`, `Experiment` classes and -`download_from_repository` method like this: +Alternatively, you can import specific classes or methods from the +package. For example, you can import the `Project`, `Structure`, +`Experiment` classes and `download_from_repository` method like this: ```python from easydiffraction import Project @@ -47,24 +47,25 @@ from easydiffraction import Experiment from easydiffraction import download_from_repository ``` -This enables you to use these classes and methods directly without the package -prefix. This is especially useful when you're using only a few components and -want to keep your code clean and concise. In this case, you can create a project -instance like this: +This enables you to use these classes and methods directly without the +package prefix. This is especially useful when you're using only a few +components and want to keep your code clean and concise. In this case, +you can create a project instance like this: ```python project = Project() ``` A complete tutorial using the `from` syntax can be found -[here](../../tutorials/ed-4/). +[here](../tutorials/ed-4.ipynb). ## Utility functions -EasyDiffraction also provides several utility functions that can simplify your -workflow. One of them is the `download_from_repository` function, which allows -you to download data files from our remote repository, making it easy to access -and use them while experimenting with EasyDiffraction. +EasyDiffraction also provides several utility functions that can +simplify your workflow. One of them is the `download_from_repository` +function, which allows you to download data files from our remote +repository, making it easy to access and use them while experimenting +with EasyDiffraction. For example, you can download a data file like this: @@ -78,29 +79,31 @@ ed.download_from_repository( ) ``` -This command will download the `hrpt_lbco.xye` file from the `docs` branch of -the EasyDiffraction repository and save it in the `data` directory of your -current working directory. This is particularly useful for quickly accessing -example datasets without having to manually download them. +This command will download the `hrpt_lbco.xye` file from the `docs` +branch of the EasyDiffraction repository and save it in the `data` +directory of your current working directory. This is particularly useful +for quickly accessing example datasets without having to manually +download them. ## Help methods -EasyDiffraction provides several helper methods to display supported engines for -calculation, minimization, and plotting. These methods can be called on the -`Project` instance to display the available options for different categories. +EasyDiffraction provides several helper methods to display supported +engines for calculation, minimization, and plotting. These methods can +be called on the `Project` instance to display the available options for +different categories. ### Supported calculators -The calculator is automatically selected based on the experiment type. You can -use the `show_supported_calculator_types()` method on an experiment to see which -calculation engines are compatible: +The calculator is automatically selected based on the experiment type. +You can use the `show_supported_calculator_types()` method on an +experiment to see which calculation engines are compatible: ```python project.experiments['hrpt'].show_supported_calculator_types() ``` -This will display a list of supported calculators along with their descriptions, -allowing you to choose the one that best fits your needs. +This will display a list of supported calculators along with their +descriptions, allowing you to choose the one that best fits your needs. An example of the output for a Bragg diffraction experiment: @@ -119,24 +122,25 @@ project.show_available_minimizers() ### Available parameters -EasyDiffraction provides several methods for showing the available parameters -grouped in different categories. For example, you can use: +EasyDiffraction provides several methods for showing the available +parameters grouped in different categories. For example, you can use: -- `project.analysis.show_all_params()` – to display all available parameters for - the analysis step. -- `project.analysis.show_fittable_params()` – to display only the parameters - that can be fitted during the analysis. -- `project.analysis.show_free_params()` – to display the parameters that are - currently free to be adjusted during the fitting process. +- `project.analysis.show_all_params()` – to display all available + parameters for the analysis step. +- `project.analysis.show_fittable_params()` – to display only the + parameters that can be fitted during the analysis. +- `project.analysis.show_free_params()` – to display the parameters that + are currently free to be adjusted during the fitting process. -Finally, you can use the `project.analysis.how_to_access_parameters()` method to -get a brief overview of how to access and modify parameters in the analysis -step, along with their unique identifiers in the CIF format. This can be -particularly useful for users who are new to the EasyDiffraction API or those -who want to quickly understand how to work with parameters in their projects. +Finally, you can use the `project.analysis.how_to_access_parameters()` +method to get a brief overview of how to access and modify parameters in +the analysis step, along with their unique identifiers in the CIF +format. This can be particularly useful for users who are new to the +EasyDiffraction API or those who want to quickly understand how to work +with parameters in their projects. -An example of the output for the `project.analysis.how_to_access_parameters()` -method is: +An example of the output for the +`project.analysis.how_to_access_parameters()` method is: | | Code variable | Unique ID for CIF | | --- | --------------------------------------------------- | -------------------------------- | @@ -151,8 +155,9 @@ method is: ### Supported plotters -To see the available plotters, you can use the `show_available_plotters()` -method on the `plotter` attribute of the `Project` instance: +To see the available plotters, you can use the +`show_available_plotters()` method on the `plotter` attribute of the +`Project` instance: ```python project.plotter.show_supported_engines() @@ -167,16 +172,19 @@ An example of the output is: ## Data analysis workflow -Once the EasyDiffraction package is imported, you can proceed with the **data -analysis**. This step can be split into several sub-steps, such as creating a -project, defining structures, adding experimental data, etc. - -EasyDiffraction provides a **Python API** that allows you to perform these steps -programmatically in a certain linear order. This is especially useful for users -who prefer to work in a script or Jupyter Notebook environment. The API is -designed to be intuitive and easy to use, allowing you to focus on the analysis -rather than low-level implementation details. - -Because this workflow is an important part of the EasyDiffraction package, it is -described in detail in the separate -[Analysis Workflow](analysis-workflow/index.md) section of the documentation. +Once the EasyDiffraction package is imported, you can proceed with the +**data analysis**. This step can be split into several sub-steps, such +as creating a project, defining structures, adding experimental data, +etc. + +EasyDiffraction provides a **Python API** that allows you to perform +these steps programmatically in a certain linear order. This is +especially useful for users who prefer to work in a script or Jupyter +Notebook environment. The API is designed to be intuitive and easy to +use, allowing you to focus on the analysis rather than low-level +implementation details. + +Because this workflow is an important part of the EasyDiffraction +package, it is described in detail in the separate +[Analysis Workflow](analysis-workflow/index.md) section of the +documentation. diff --git a/docs/user-guide/glossary.md b/docs/docs/user-guide/glossary.md similarity index 80% rename from docs/user-guide/glossary.md rename to docs/docs/user-guide/glossary.md index 827cfdf5..75f0300c 100644 --- a/docs/user-guide/glossary.md +++ b/docs/docs/user-guide/glossary.md @@ -1,25 +1,29 @@ # Glossary -Before guiding you through the use of EasyDiffraction, we define some common -terms and abbreviations used throughout the documentation and tutorials. +Before guiding you through the use of EasyDiffraction, we define some +common terms and abbreviations used throughout the documentation and +tutorials. ## Dictionary Type Labels -The following labels are used to identify different types of CIF dictionaries: +The following labels are used to identify different types of CIF +dictionaries: - [coreCIF][1]{:.label-cif} – Core CIF dictionary by the [IUCr](https://www.iucr.org). - [pdCIF][2]{:.label-cif} – Powder CIF dictionary by the [IUCr](https://www.iucr.org). -- [easydiffractionCIF][0]{:.label-cif} – Custom CIF dictionary developed for - EasyDiffraction. +- [easydiffractionCIF][0]{:.label-cif} – Custom CIF dictionary developed + for EasyDiffraction. -For more information about CIF, see the [Data Format](data-format.md) section. +For more information about CIF, see the [Data Format](data-format.md) +section. ## Experiment Type Labels -EasyDiffraction supports a variety of experiment types, each with its own set of -parameters. The following labels identify the supported experiment types: +EasyDiffraction supports a variety of experiment types, each with its +own set of parameters. The following labels identify the supported +experiment types: ### Neutron Diffraction @@ -27,8 +31,8 @@ parameters. The following labels identify the supported experiment types: constant wavelength. - [pd-neut-tof][0]{:.label-experiment} – Powder neutron diffraction with time-of-flight. -- [sc-neut-cwl][0]{:.label-experiment} – Single-crystal neutron diffraction with - constant wavelength. +- [sc-neut-cwl][0]{:.label-experiment} – Single-crystal neutron + diffraction with constant wavelength. ### X-ray Diffraction diff --git a/docs/user-guide/index.md b/docs/docs/user-guide/index.md similarity index 57% rename from docs/user-guide/index.md rename to docs/docs/user-guide/index.md index a6671f3d..2ddc28b9 100644 --- a/docs/user-guide/index.md +++ b/docs/docs/user-guide/index.md @@ -4,20 +4,21 @@ icon: material/book-open-variant # :material-book-open-variant: User Guide -This section provides an overview of the **core concepts**, **key parameters** -and **workflow steps** required for using EasyDiffraction effectively. +This section provides an overview of the **core concepts**, **key +parameters** and **workflow steps** required for using EasyDiffraction +effectively. Here is a brief overview of the User Guide sections: -- [Glossary](glossary.md) – Defines common terms and labels used throughout the - documentation. -- [Concept](concept.md) – Introduces the overall idea behind diffraction data - processing and where EasyDiffraction fits. -- [Data Format](data-format.md) – Explains the Crystallographic Information File - (CIF) and how it's used in EasyDiffraction. -- [Parameters](parameters.md) – Describes how parameters are structured, named, - and accessed within the EasyDiffraction library. -- [First Steps](first-steps.md) – Shows how to begin using EasyDiffraction in - Python or Jupyter notebooks. +- [Glossary](glossary.md) – Defines common terms and labels used + throughout the documentation. +- [Concept](concept.md) – Introduces the overall idea behind diffraction + data processing and where EasyDiffraction fits. +- [Data Format](data-format.md) – Explains the Crystallographic + Information File (CIF) and how it's used in EasyDiffraction. +- [Parameters](parameters.md) – Describes how parameters are structured, + named, and accessed within the EasyDiffraction library. +- [First Steps](first-steps.md) – Shows how to begin using + EasyDiffraction in Python or Jupyter notebooks. - [Analysis Workflow](analysis-workflow/index.md) – Breaks down the data analysis pipeline into practical, sequential steps. diff --git a/docs/user-guide/parameters.md b/docs/docs/user-guide/parameters.md similarity index 90% rename from docs/user-guide/parameters.md rename to docs/docs/user-guide/parameters.md index 794f65aa..622bf6f1 100644 --- a/docs/user-guide/parameters.md +++ b/docs/docs/user-guide/parameters.md @@ -1,52 +1,58 @@ # Parameters -The data analysis process, introduced in the [Concept](concept.md) section, -assumes that you mainly work with different parameters. The parameters are used -to describe the structure and the experiment and are required to set up the -analysis. +The data analysis process, introduced in the [Concept](concept.md) +section, assumes that you mainly work with different parameters. The +parameters are used to describe the structure and the experiment and are +required to set up the analysis. -Each parameter in EasyDiffraction has a specific name used for code reference, -and it belongs to a specific category. +Each parameter in EasyDiffraction has a specific name used for code +reference, and it belongs to a specific category. - In many cases, the EasyDiffraction name is the same as the CIF name. -- In some cases, the EasyDiffraction name is a slightly modified version of the - CIF name to comply with Python naming conventions. For example, `name_H-M_alt` - becomes `name_h_m`, replacing hyphens with underscores and using lowercase - letters. -- In rare cases, the EasyDiffraction name is a bit shorter, like `b_iso` instead - of CIF `B_iso_or_equiv`, to make the code a bit more user-friendly. -- When there is no defined CIF name for a parameter, EasyDiffraction introduces - its own name, which is used in the code as well as an equivalent CIF name to - be placed in the custom CIF dictionary `easydiffractionCIF`. - -EasyDiffraction names are used in code, while CIF names are used to store and -retrieve the full state of a data analysis project in CIF format. You can find -more about the project in the [Project](analysis-workflow/project.md) section. +- In some cases, the EasyDiffraction name is a slightly modified version + of the CIF name to comply with Python naming conventions. For example, + `name_H-M_alt` becomes `name_h_m`, replacing hyphens with underscores + and using lowercase letters. +- In rare cases, the EasyDiffraction name is a bit shorter, like `b_iso` + instead of CIF `B_iso_or_equiv`, to make the code a bit more + user-friendly. +- When there is no defined CIF name for a parameter, EasyDiffraction + introduces its own name, which is used in the code as well as an + equivalent CIF name to be placed in the custom CIF dictionary + `easydiffractionCIF`. + +EasyDiffraction names are used in code, while CIF names are used to +store and retrieve the full state of a data analysis project in CIF +format. You can find more about the project in the +[Project](analysis-workflow/project.md) section. ## Parameter Attributes -Parameters in EasyDiffraction are more than just variables. They are objects -that, in addition to the name and value, also include attributes such as the -description, unit, uncertainty, minimum and maximum values, etc. All these -attributes are described in the [API Reference](../api-reference/index.md) -section. Examples of how to use these parameters in code are provided in the +Parameters in EasyDiffraction are more than just variables. They are +objects that, in addition to the name and value, also include attributes +such as the description, unit, uncertainty, minimum and maximum values, +etc. All these attributes are described in the +[API Reference](../api-reference/index.md) section. Examples of how to +use these parameters in code are provided in the [Analysis Workflow](analysis-workflow/index.md) and [Tutorials](../tutorials/index.md) sections. -The most important attribute, besides `name` and `value`, is `free`, which is -used to define whether the parameter is free or fixed for optimization during -the fitting process. The `free` attribute is set to `False` by default, which -means the parameter is fixed. To optimize a parameter, set `free` to `True`. +The most important attribute, besides `name` and `value`, is `free`, +which is used to define whether the parameter is free or fixed for +optimization during the fitting process. The `free` attribute is set to +`False` by default, which means the parameter is fixed. To optimize a +parameter, set `free` to `True`. -Although parameters are central, EasyDiffraction hides their creation and -attribute handling from the user. The user only accesses the required parameters -through the top-level objects, such as `project`, `structures`, `experiments`, -etc. The parameters are created and initialized automatically when a new project -is created or an existing one is loaded. +Although parameters are central, EasyDiffraction hides their creation +and attribute handling from the user. The user only accesses the +required parameters through the top-level objects, such as `project`, +`structures`, `experiments`, etc. The parameters are created and +initialized automatically when a new project is created or an existing +one is loaded. In the following sections, you can see a list of the parameters used in -EasyDiffraction. Use the tabs to switch between how to access a parameter in -code and its CIF name for serialization. +EasyDiffraction. Use the tabs to switch between how to access a +parameter in code and its CIF name for serialization. !!! warning "Important" @@ -59,23 +65,27 @@ code and its CIF name for serialization. project.structures['nacl'].space_group.name_h_m ``` -In the example above, `space_group` is a structure category, and `name_h_m` is -the parameter. For simplicity, only the last part (`category.parameter`) of the -full access name will be shown in the tables below. +In the example above, `space_group` is a structure category, and +`name_h_m` is the parameter. For simplicity, only the last part +(`category.parameter`) of the full access name will be shown in the +tables below. -In addition, the CIF names are also provided for each parameter, which are used -to serialize the parameters in the CIF format. +In addition, the CIF names are also provided for each parameter, which +are used to serialize the parameters in the CIF format. -Tags defining the corresponding experiment type are also given before the table. +Tags defining the corresponding experiment type are also given before +the table. ## Structure parameters -Below is a list of parameters used to describe the structure in EasyDiffraction. +Below is a list of parameters used to describe the structure in +EasyDiffraction. ### Crystall structure parameters -[pd-neut-cwl][3]{:.label-experiment} [pd-neut-tof][3]{:.label-experiment} -[pd-xray][3]{:.label-experiment} [sc-neut-cwl][3]{:.label-experiment} +[pd-neut-cwl][3]{:.label-experiment} +[pd-neut-tof][3]{:.label-experiment} [pd-xray][3]{:.label-experiment} +[sc-neut-cwl][3]{:.label-experiment} === "How to access in the code" @@ -130,8 +140,9 @@ EasyDiffraction. ### Common parameters -[pd-neut-cwl][3]{:.label-experiment} [pd-neut-tof][3]{:.label-experiment} -[pd-xray][3]{:.label-experiment} [sc-neut-cwl][3]{:.label-experiment} +[pd-neut-cwl][3]{:.label-experiment} +[pd-neut-tof][3]{:.label-experiment} [pd-xray][3]{:.label-experiment} +[sc-neut-cwl][3]{:.label-experiment} === "How to access in the code" @@ -153,8 +164,8 @@ EasyDiffraction. ### Standard powder diffraction -[pd-neut-cwl][3]{:.label-experiment} [pd-neut-tof][3]{:.label-experiment} -[pd-xray][3]{:.label-experiment} +[pd-neut-cwl][3]{:.label-experiment} +[pd-neut-tof][3]{:.label-experiment} [pd-xray][3]{:.label-experiment} === "How to access in the code" @@ -240,7 +251,8 @@ EasyDiffraction. ### Total scattering -[pd-neut-total][3]{:.label-experiment} [pd-xray-total][3]{:.label-experiment} +[pd-neut-total][3]{:.label-experiment} +[pd-xray-total][3]{:.label-experiment} === "How to access in the code" diff --git a/docs/user-guide/parameters/_diffrn_radiation.md b/docs/docs/user-guide/parameters/_diffrn_radiation.md similarity index 87% rename from docs/user-guide/parameters/_diffrn_radiation.md rename to docs/docs/user-guide/parameters/_diffrn_radiation.md index e3c0a7e9..6e2f0a06 100644 --- a/docs/user-guide/parameters/_diffrn_radiation.md +++ b/docs/docs/user-guide/parameters/_diffrn_radiation.md @@ -4,13 +4,13 @@ Data items in this category describe the radiation used in measuring the diffraction intensities. Please see the -[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) for -further details. +[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) +for further details. ## [\_diffrn_radiation.probe](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) -The nature of the radiation used (i.e. the name of the subatomic particle or the -region of the electromagnetic spectrum). +The nature of the radiation used (i.e. the name of the subatomic +particle or the region of the electromagnetic spectrum). Supported values: `neutron` and `x-ray` diff --git a/docs/user-guide/parameters/_diffrn_radiation_wavelength.md b/docs/docs/user-guide/parameters/_diffrn_radiation_wavelength.md similarity index 95% rename from docs/user-guide/parameters/_diffrn_radiation_wavelength.md rename to docs/docs/user-guide/parameters/_diffrn_radiation_wavelength.md index 4d205788..3b750b22 100644 --- a/docs/user-guide/parameters/_diffrn_radiation_wavelength.md +++ b/docs/docs/user-guide/parameters/_diffrn_radiation_wavelength.md @@ -4,8 +4,8 @@ Data items in this category describe the wavelength of radiation used in diffraction measurements. Please see the -[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) for -further details. +[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) +for further details. ## [\_diffrn_radiation_wavelength.wavelength](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) diff --git a/docs/user-guide/parameters/_exptl_crystal.md b/docs/docs/user-guide/parameters/_exptl_crystal.md similarity index 100% rename from docs/user-guide/parameters/_exptl_crystal.md rename to docs/docs/user-guide/parameters/_exptl_crystal.md diff --git a/docs/user-guide/parameters/_extinction.md b/docs/docs/user-guide/parameters/_extinction.md similarity index 100% rename from docs/user-guide/parameters/_extinction.md rename to docs/docs/user-guide/parameters/_extinction.md diff --git a/docs/user-guide/parameters/_pd_calib.md b/docs/docs/user-guide/parameters/_pd_calib.md similarity index 94% rename from docs/user-guide/parameters/_pd_calib.md rename to docs/docs/user-guide/parameters/_pd_calib.md index 2b96a4c8..407c5f73 100644 --- a/docs/user-guide/parameters/_pd_calib.md +++ b/docs/docs/user-guide/parameters/_pd_calib.md @@ -2,8 +2,8 @@ # \_pd_calib -This section defines the parameters used for the calibration of the instrument, -similar to this +This section defines the parameters used for the calibration of the +instrument, similar to this [IUCr section](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd). ## [\_pd_calib.2theta_offset](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) diff --git a/docs/user-guide/parameters/atom_site.md b/docs/docs/user-guide/parameters/atom_site.md similarity index 82% rename from docs/user-guide/parameters/atom_site.md rename to docs/docs/user-guide/parameters/atom_site.md index 44325b8a..7acdb650 100644 --- a/docs/user-guide/parameters/atom_site.md +++ b/docs/docs/user-guide/parameters/atom_site.md @@ -2,16 +2,16 @@ # \_atom_site -Data items in this category record details about the atom sites in a crystal -structure, such as the positional coordinates and atomic displacement -parameters. Please see the +Data items in this category record details about the atom sites in a +crystal structure, such as the positional coordinates and atomic +displacement parameters. Please see the [IUCr page](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/CATOM_SITE.html) for further details. ## [\_atom_site.label](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Iatom_site.label.html) -This is a unique identifier for a particular site in the asymmetric unit of the -crystal unit cell. +This is a unique identifier for a particular site in the asymmetric unit +of the crystal unit cell. ## [\_atom_site.type_symbol](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Iatom_site.type_symbol.html) @@ -19,7 +19,8 @@ A code to identify the atom specie(s) occupying this site. ## \_atom_site.fract -Atom-site coordinates as fractions of the [\_cell_length](cell.md) values. +Atom-site coordinates as fractions of the [\_cell_length](cell.md) +values. - [\_atom_site.fract_x](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Iatom_site.fract_x.html) - [\_atom_site.fract_y](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Iatom_site.fract_y.html) @@ -31,8 +32,8 @@ The fraction of the atom type present at this site. ## [\_atom_site.ADP_type](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Iatom_site.adp_type.html) -Code for type of atomic displacement parameters used for the site. Currently -only `Biso` (isotropic B) is supported. +Code for type of atomic displacement parameters used for the site. +Currently only `Biso` (isotropic B) is supported. ## [\_atom_site.B_iso_or_equiv](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Iatom_site.B_iso_or_equiv.html) @@ -43,17 +44,17 @@ displacement parameter, in angstroms squared. `optional parameter` -The number of different sites that are generated by the application of the -space-group symmetry to the coordinates given for this site. It is equal to the -multiplicity given for this Wyckoff site in International Tables for -Crystallography Vol. A (2002). +The number of different sites that are generated by the application of +the space-group symmetry to the coordinates given for this site. It is +equal to the multiplicity given for this Wyckoff site in International +Tables for Crystallography Vol. A (2002). ## [\_atom_site.Wyckoff_symbol](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Iatom_site.Wyckoff_symbol.html) `optional parameter` -The Wyckoff symbol (letter) as listed in the space-group tables of International -Tables for Crystallography Vol. A. +The Wyckoff symbol (letter) as listed in the space-group tables of +International Tables for Crystallography Vol. A. [0]: # diff --git a/docs/user-guide/parameters/background.md b/docs/docs/user-guide/parameters/background.md similarity index 75% rename from docs/user-guide/parameters/background.md rename to docs/docs/user-guide/parameters/background.md index 89985470..e307c981 100644 --- a/docs/user-guide/parameters/background.md +++ b/docs/docs/user-guide/parameters/background.md @@ -2,26 +2,27 @@ # \_pd_background -This category defines various background functions that could be used when -calculating diffractograms. Please see the -[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) for -further details. +This category defines various background functions that could be used +when calculating diffractograms. Please see the +[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) +for further details. ## [\_pd_background.line_segment_X](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) -List of X-coordinates used to create many straight-line segments representing -the background in a calculated diffractogram. +List of X-coordinates used to create many straight-line segments +representing the background in a calculated diffractogram. Supported values: `2theta` and `time-of-flight` ## [\_pd_background.line_segment_intensity](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) -List of intensities used to create many straight-line segments representing the -background in a calculated diffractogram. +List of intensities used to create many straight-line segments +representing the background in a calculated diffractogram. ## [\_pd_background.X_coordinate](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) -The type of X-coordinate against which the pd_background values were calculated. +The type of X-coordinate against which the pd_background values were +calculated. [0]: # diff --git a/docs/user-guide/parameters/cell.md b/docs/docs/user-guide/parameters/cell.md similarity index 95% rename from docs/user-guide/parameters/cell.md rename to docs/docs/user-guide/parameters/cell.md index 5aeb12ae..806df516 100644 --- a/docs/user-guide/parameters/cell.md +++ b/docs/docs/user-guide/parameters/cell.md @@ -2,8 +2,8 @@ # \_cell -Data items in this category record details about the crystallographic cell -parameters and their measurement. Please see the +Data items in this category record details about the crystallographic +cell parameters and their measurement. Please see the [IUCr page](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/CCELL.html) for further details. diff --git a/docs/user-guide/parameters/expt_type.md b/docs/docs/user-guide/parameters/expt_type.md similarity index 95% rename from docs/user-guide/parameters/expt_type.md rename to docs/docs/user-guide/parameters/expt_type.md index 5aeb12ae..806df516 100644 --- a/docs/user-guide/parameters/expt_type.md +++ b/docs/docs/user-guide/parameters/expt_type.md @@ -2,8 +2,8 @@ # \_cell -Data items in this category record details about the crystallographic cell -parameters and their measurement. Please see the +Data items in this category record details about the crystallographic +cell parameters and their measurement. Please see the [IUCr page](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/CCELL.html) for further details. diff --git a/docs/user-guide/parameters/instrument.md b/docs/docs/user-guide/parameters/instrument.md similarity index 73% rename from docs/user-guide/parameters/instrument.md rename to docs/docs/user-guide/parameters/instrument.md index 3975161c..fbeca6c9 100644 --- a/docs/user-guide/parameters/instrument.md +++ b/docs/docs/user-guide/parameters/instrument.md @@ -2,21 +2,22 @@ # \_pd_instr -This section contains information relevant to the instrument used for the -diffraction measurement, similar to this +This section contains information relevant to the instrument used for +the diffraction measurement, similar to this [IUCr section](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd). ## [\_pd_instr.resolution](#) -In general, the profile of a Bragg reflection centred at the peak position can -be approximated by mathematical convolution of contributions from the -instrument, called the instrumental resolution function, and from the -microstructure of the sample. Because many contributions to powder diffraction -peaks have a nearly Gaussian or Lorentzian shape, the pseudo-Voigt function, is -widely used to describe peak profiles in powder diffraction. +In general, the profile of a Bragg reflection centred at the peak +position can be approximated by mathematical convolution of +contributions from the instrument, called the instrumental resolution +function, and from the microstructure of the sample. Because many +contributions to powder diffraction peaks have a nearly Gaussian or +Lorentzian shape, the pseudo-Voigt function, is widely used to describe +peak profiles in powder diffraction. -Half-width parameters (normally characterising the instrumental resolution -function) as implemented in [CrysPy](https://cryspy.fr): +Half-width parameters (normally characterising the instrumental +resolution function) as implemented in [CrysPy](https://cryspy.fr): - \_pd_instr.resolution_u - \_pd_instr.resolution_v @@ -34,7 +35,8 @@ Lorentzian isotropic particle size parameteras implemented in ## [\_pd_instr.reflex_asymmetry](#) -Peak profile asymmetry parameters as implemented in [CrysPy](https://cryspy.fr). +Peak profile asymmetry parameters as implemented in +[CrysPy](https://cryspy.fr). - \_pd_instr.reflex_asymmetry_p1 - \_pd_instr.reflex_asymmetry_p2 diff --git a/docs/user-guide/parameters/linked_phases.md b/docs/docs/user-guide/parameters/linked_phases.md similarity index 86% rename from docs/user-guide/parameters/linked_phases.md rename to docs/docs/user-guide/parameters/linked_phases.md index df3f6d29..1d00f1d2 100644 --- a/docs/user-guide/parameters/linked_phases.md +++ b/docs/docs/user-guide/parameters/linked_phases.md @@ -2,10 +2,10 @@ # \_pd_phase_block -A table of phases relevant to the current data block. Each phase is identified -by its data block identifier. Please see the -[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) for -further details. +A table of phases relevant to the current data block. Each phase is +identified by its data block identifier. Please see the +[IUCr page](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) +for further details. ## [\_pd_phase_block.id](https://www.iucr.org/resources/cif/dictionaries/browse/cif_pd) diff --git a/docs/user-guide/parameters/pd_meas.md b/docs/docs/user-guide/parameters/pd_meas.md similarity index 97% rename from docs/user-guide/parameters/pd_meas.md rename to docs/docs/user-guide/parameters/pd_meas.md index b294a96a..fcb4db17 100644 --- a/docs/user-guide/parameters/pd_meas.md +++ b/docs/docs/user-guide/parameters/pd_meas.md @@ -7,8 +7,8 @@ This section contains the measured diffractogram, similar to this ## [\_pd_meas.2theta_scan](https://raw.githubusercontent.com/COMCIFS/Powder_Dictionary/master/cif_pow.dic) -2θ diffraction angle (in degrees) for intensity points measured in a scanning -method. +2θ diffraction angle (in degrees) for intensity points measured in a +scanning method. ## [\_pd_meas.time-of-flight](https://raw.githubusercontent.com/COMCIFS/Powder_Dictionary/master/cif_pow.dic) diff --git a/docs/user-guide/parameters/peak.md b/docs/docs/user-guide/parameters/peak.md similarity index 95% rename from docs/user-guide/parameters/peak.md rename to docs/docs/user-guide/parameters/peak.md index 5aeb12ae..806df516 100644 --- a/docs/user-guide/parameters/peak.md +++ b/docs/docs/user-guide/parameters/peak.md @@ -2,8 +2,8 @@ # \_cell -Data items in this category record details about the crystallographic cell -parameters and their measurement. Please see the +Data items in this category record details about the crystallographic +cell parameters and their measurement. Please see the [IUCr page](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/CCELL.html) for further details. diff --git a/docs/user-guide/parameters/space_group.md b/docs/docs/user-guide/parameters/space_group.md similarity index 87% rename from docs/user-guide/parameters/space_group.md rename to docs/docs/user-guide/parameters/space_group.md index 883be103..ae5bce6b 100644 --- a/docs/user-guide/parameters/space_group.md +++ b/docs/docs/user-guide/parameters/space_group.md @@ -2,16 +2,16 @@ # \_space_group -Contains all the data items that refer to the space group as a whole. Please see -the +Contains all the data items that refer to the space group as a whole. +Please see the [IUCr page](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/CSPACE_GROUP.html) for further details. ## [\_space_group.name_H-M_alt](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Ispace_group.name_H-M_alt.html) -The international Hermann-Mauguin space-group symbol as defined in International -Tables for Crystallography Volume A. It allows any Hermann-Mauguin symbol to be -given. +The international Hermann-Mauguin space-group symbol as defined in +International Tables for Crystallography Volume A. It allows any +Hermann-Mauguin symbol to be given. ## [\_space_group.IT_coordinate_system_code](https://www.iucr.org/__data/iucr/cifdic_html/3/CORE_DIC/Ispace_group.IT_coordinate_system_code.html) diff --git a/docs/includes/abbreviations.md b/docs/includes/abbreviations.md new file mode 100644 index 00000000..682f16ff --- /dev/null +++ b/docs/includes/abbreviations.md @@ -0,0 +1,15 @@ + + +*[CIF]: Crystallographic Information File. +*[curl]: Command-line tool for transferring data with URLs. +*[GitHub]: A web-based platform for version control and collaboration. +*[Google Colab]: Cloud service that allows you to run Jupyter Notebooks in the cloud. +*[IUCr]: International Union of Crystallography. +*[Jupyter Notebook]: An open-source web application that allows you to create and share documents that contain live code, equations, visualizations, and narrative text. +*[JupyterLab]: Web-based interactive development environment for notebooks, code, and data. +*[pip]: Package installer for Python. +*[PyPI]: The Python Package Index is a repository of software for the Python programming language. +*[Conda]: Conda is a cross-platform, language-agnostic binary package manager. +*[Pixi]: A modern package manager for Windows, macOS, and Linux. + + diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 467bfec8..55ca5f22 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -1,45 +1,167 @@ -########################### -# Override default settings -########################### - # Project information -site_name: '' #EasyDiffraction Library -site_url: https://easyscience.github.io/diffraction-lib/ #https://docs.easydiffraction.org/lib/ +site_name: EasyDiffraction Library +site_url: https://easyscience.github.io/diffraction-lib # Repository -repo_url: https://github.com/easyscience/diffraction-lib/ +repo_url: https://github.com/easyscience/diffraction-lib edit_uri: edit/develop/docs/ # Copyright -copyright: © 2026 EasyDiffraction +copyright: © 2021-2026 EasyDiffraction + +# Sets the theme and theme-specific configuration +theme: + name: material + custom_dir: overrides + features: + #- content.action.edit # Temporary disable edit button (until decided on which branch to use and where to host the notebooks) + #- content.action.view + - content.code.annotate + - content.code.copy # Auto generated button to copy a code block's content + - content.tooltips + - navigation.footer + - navigation.indexes + #- navigation.instant # Instant loading, but it causes issues with rendering equations + #- navigation.sections + - navigation.top # Back-to-top button + - navigation.tracking # Anchor tracking + - search.highlight + - search.share + - search.suggest + - toc.follow + palette: + # Palette toggle for light mode + - media: '(prefers-color-scheme: light)' + scheme: default + primary: custom + toggle: + icon: fontawesome/solid/sun + name: Switch to dark mode + # Palette toggle for dark mode + - media: '(prefers-color-scheme: dark)' + scheme: slate + primary: custom + toggle: + icon: fontawesome/solid/moon + name: Switch to light mode + font: + text: Mulish + code: Roboto Mono + icon: + edit: material/file-edit-outline + favicon: assets/images/favicon.png + logo_dark_mode: assets/images/logo_dark.svg + logo_light_mode: assets/images/logo_light.svg -# Extra icons in the bottom right corner +# A set of key-value pairs, where the values can be any valid YAML +# construct, that will be passed to the template extra: - social: + generator: false # Disable `Made with Material for MkDocs` (bottom left) + social: # Extra icons in the bottom right corner + - icon: easyscience # File: overrides/.icons/easyscience.svg + link: https://easyscience.org + name: EasyScience Framework Webpage - icon: easydiffraction # File: overrides/.icons/easydiffraction.svg - link: https://easydiffraction.org + link: https://easyscience.github.io/diffraction name: EasyDiffraction Main Webpage - icon: app # File: overrides/.icons/app.svg - link: https://docs.easydiffraction.org/app/ + link: https://easyscience.github.io/diffraction-app name: EasyDiffraction Application Docs - icon: fontawesome/brands/github # Name as in Font Awesome link: https://github.com/easyscience/diffraction-lib name: EasyDiffraction Library Source Code on GitHub + # Set custom variables to be used in Markdown and HTML files + vars: + ci_branch: !ENV CI_BRANCH + github_repository: !ENV GITHUB_REPOSITORY + release_version: !ENV RELEASE_VERSION + docs_version: !ENV DOCS_VERSION + notebooks_dir: !ENV NOTEBOOKS_DIR + # Renders a version selector in the header + version: + provider: mike + +# Customization to be included by the theme +extra_css: + - assets/stylesheets/extra.css + +extra_javascript: + - assets/javascripts/extra.js + # MathJax for rendering mathematical expressions + - assets/javascripts/mathjax.js # Custom MathJax config to ensure compatibility with mkdocs-jupyter + - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js # Official MathJax CDN + +# A list of extensions beyond the ones that MkDocs uses by default (meta, toc, tables, and fenced_code) +markdown_extensions: + - abbr + - admonition + - attr_list + - def_list + - footnotes + - pymdownx.arithmatex: # rendering of equations and integrates with MathJax or KaTeX + generic: true + - pymdownx.blocks.caption + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + options: + custom_icons: + - docs/overrides/.icons + - pymdownx.highlight: # whether highlighting should be carried out during build time by Pygments + use_pygments: true + pygments_lang_class: true + - pymdownx.snippets: + auto_append: + - docs/includes/abbreviations.md + - pymdownx.superfences: # whether highlighting should be carried out during build time by Pygments + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tabbed: # enables content tabs + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + - toc: + toc_depth: 3 -# Jupyter notebooks +# A list of plugins (with optional configuration settings) to use when building the site plugins: + - autorefs + - inline-svg + - markdownextradata # Plugin that injects the mkdocs.yml extra variables into the Markdown template + - mike # Plugin that makes it easy to deploy multiple versions of the docs - mkdocs-jupyter: - execute_ignore: - - '*.ipynb' # Do not execute any notebooks -# - 'quick*.ipynb' -# - 'basic*.ipynb' -# - 'advanced*.ipynb' -# - 'cryst*.ipynb' -# - 'pdf*.ipynb' + include: ['*.ipynb'] # Default: ['*.py', '*.ipynb'] + execute: false # Do not execute notebooks during build. They are expected to be pre-executed. + allow_errors: false + include_source: true + include_requirejs: true # Required for Plotly + #custom_mathjax_url: 'https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js' # See 'extra_javascript' above + ignore_h1_titles: true # Use titles defined in the nav section below + remove_tag_config: + remove_input_tags: + - hide-in-docs + - mkdocstrings: + handlers: + python: + paths: ['src'] # Change 'src' to your actual sources directory + options: + docstring_style: numpy + group_by_category: false + heading_level: 1 + show_root_heading: true + show_root_full_path: false + show_submodules: true + show_source: true + - search -################## -# Add new settings -################## +# Determines additional directories to watch when running mkdocs serve +watch: + - includes + - overrides + - ../src # Exclude files and folders from the global navigation not_in_nav: | @@ -100,7 +222,6 @@ nav: - experiment: api-reference/datablocks/experiment.md - structure: api-reference/datablocks/structure.md - display: api-reference/display.md - - experiments: api-reference/experiment.md - io: api-reference/io.md - project: api-reference/project.md - summary: api-reference/summary.md diff --git a/docs/overrides/.icons/app.svg b/docs/overrides/.icons/app.svg new file mode 100644 index 00000000..b4fdd4f3 --- /dev/null +++ b/docs/overrides/.icons/app.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/overrides/.icons/easydiffraction.svg b/docs/overrides/.icons/easydiffraction.svg new file mode 100644 index 00000000..5813e570 --- /dev/null +++ b/docs/overrides/.icons/easydiffraction.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/overrides/.icons/easyscience.svg b/docs/overrides/.icons/easyscience.svg new file mode 100644 index 00000000..fb514912 --- /dev/null +++ b/docs/overrides/.icons/easyscience.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/overrides/.icons/google-colab.svg b/docs/overrides/.icons/google-colab.svg new file mode 100644 index 00000000..9cd9d1b0 --- /dev/null +++ b/docs/overrides/.icons/google-colab.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 00000000..2e146827 --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} + +{% block content %} + +{% if page.nb_url %} + {# Parse notebook path/URL #} + {% set parts = page.nb_url.split('/') %} + {% set tutorial_name = parts[-2] %} + {% set filename = parts[-1] %} + + {# Colab url #} + {% set base_colab_url = "https://colab.research.google.com/github/" %} + {% set colab_url = + base_colab_url ~ config.extra.vars.github_repository ~ + "/blob/gh-pages/" ~ config.extra.vars.docs_version ~ + "/tutorials/" ~ tutorial_name ~ "/" ~ filename + %} + + {# Download link: relative to the current page #} + {% set file_url = filename %} + + {# Open in Colab (absolute GitHub URL; works anywhere) #} + + {% include ".icons/google-colab.svg" %} + + + {# Download: use a RELATIVE link to the file next to this page #} + + {% include ".icons/material/download.svg" %} + +{% endif %} + +{{ super() }} +{% endblock content %} diff --git a/docs/overrides/partials/logo.html b/docs/overrides/partials/logo.html new file mode 100644 index 00000000..78fa69ca --- /dev/null +++ b/docs/overrides/partials/logo.html @@ -0,0 +1,15 @@ +{% if ( config.theme.logo_light_mode and config.theme.logo_dark_mode ) %} +logo +logo +{% elif config.theme.logo %} +logo +{% else %} {% set icon = config.theme.icon.logo or "material/library" %} {% +include ".icons/" ~ icon ~ ".svg" %} {% endif %} diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md deleted file mode 100644 index a7c2cc37..00000000 --- a/docs/tutorials/index.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -icon: material/school ---- - -# :material-school: Tutorials - -This section presents a collection of **Jupyter Notebook** tutorials that -demonstrate how to use EasyDiffraction for various tasks. These tutorials serve -as self-contained, step-by-step **guides** to help users grasp the workflow of -diffraction data analysis using EasyDiffraction. - -Instructions on how to run the tutorials are provided in the -[:material-cog-box: Installation & Setup](../installation-and-setup/index.md#how-to-run-tutorials) -section of the documentation. - -The tutorials are organized into the following categories. - -## Getting Started - -- [LBCO `quick` CIF](ed-1.ipynb) – A minimal example intended as a quick - reference for users already familiar with the EasyDiffraction API or who want - to see how Rietveld refinement of the La0.5Ba0.5CoO3 crystal structure can be - performed when both the structure and experiment are loaded from CIF files. - Data collected from constant wavelength neutron powder diffraction at HRPT at - PSI. -- [LBCO `quick` `code`](ed-2.ipynb) – A minimal example intended as a quick - reference for users already familiar with the EasyDiffraction API or who want - to see an example refinement when both the structure and experiment are - defined directly in code. This tutorial covers a Rietveld refinement of the - La0.5Ba0.5CoO3 crystal structure using constant wavelength neutron powder - diffraction data from HRPT at PSI. -- [LBCO `complete`](ed-3.ipynb) – Demonstrates the use of the EasyDiffraction - API in a simplified, user-friendly manner that closely follows the GUI - workflow for a Rietveld refinement of the La0.5Ba0.5CoO3 crystal structure - using constant wavelength neutron powder diffraction data from HRPT at PSI. - This tutorial provides a full explanation of the workflow with detailed - comments and descriptions of every step, making it suitable for users who are - new to EasyDiffraction or those who prefer a more guided approach. - -## Powder Diffraction - -- [Co2SiO4 `pd-neut-cwl`](ed-5.ipynb) – Demonstrates a Rietveld refinement of - the Co2SiO4 crystal structure using constant wavelength neutron powder - diffraction data from D20 at ILL. -- [HS `pd-neut-cwl`](ed-6.ipynb) – Demonstrates a Rietveld refinement of the HS - crystal structure using constant wavelength neutron powder diffraction data - from HRPT at PSI. -- [Si `pd-neut-tof`](ed-7.ipynb) – Demonstrates a Rietveld refinement of the Si - crystal structure using time-of-flight neutron powder diffraction data from - SEPD at Argonne. -- [NCAF `pd-neut-tof`](ed-8.ipynb) – Demonstrates a Rietveld refinement of the - Na2Ca3Al2F14 crystal structure using two time-of-flight neutron powder - diffraction datasets (from two detector banks) of the WISH instrument at ISIS. - -## Single Crystal Diffraction - -- [Tb2TiO7 `sg-neut-cwl`](ed-14.ipynb) – Demonstrates structure refinement of - Tb2TiO7 using constant wavelength neutron single crystal diffraction data from - HEiDi at FRM II. -- [Taurine `sg-neut-tof`](ed-15.ipynb) – Demonstrates structure refinement of - Taurine using time-of-flight neutron single crystal diffraction data from - SENJU at J-PARC. - -## Pair Distribution Function (PDF) - -- [Ni `pd-neut-cwl`](ed-10.ipynb) – Demonstrates a PDF analysis of Ni using data - collected from a constant wavelength neutron powder diffraction experiment. -- [Si `pd-neut-tof`](ed-11.ipynb) – Demonstrates a PDF analysis of Si using data - collected from a time-of-flight neutron powder diffraction experiment at NOMAD - at SNS. -- [NaCl `pd-xray`](ed-12.ipynb) – Demonstrates a PDF analysis of NaCl using data - collected from an X-ray powder diffraction experiment. - -## Multi-Structure & Multi-Experiment Refinement - -- [PbSO4 NPD+XRD](ed-4.ipynb) – Joint fit of PbSO4 using X-ray and neutron - constant wavelength powder diffraction data. -- [LBCO+Si McStas](ed-9.ipynb) – Multi-phase Rietveld refinement of - La0.5Ba0.5CoO3 with Si impurity using time-of-flight neutron data simulated - with McStas. -- [Si Bragg+PDF](ed-16.ipynb) – Joint refinement of Si combining Bragg - diffraction (SEPD) and pair distribution function (NOMAD) analysis. A single - shared structure is refined simultaneously against both datasets. - -## Workshops & Schools - -- [DMSC Summer School](ed-13.ipynb) – A workshop tutorial that demonstrates a - Rietveld refinement of the La0.5Ba0.5CoO3 crystal structure using - time-of-flight neutron powder diffraction data simulated with McStas. This - tutorial is designed for the ESS DMSC Summer School. diff --git a/docs/user-guide/analysis-workflow/index.md b/docs/user-guide/analysis-workflow/index.md deleted file mode 100644 index 51c1f623..00000000 --- a/docs/user-guide/analysis-workflow/index.md +++ /dev/null @@ -1,34 +0,0 @@ -# Analysis Workflow - -To streamline the **data analysis process**, EasyDiffraction follows a -structured workflow divided into **five key steps**: - -```mermaid -flowchart LR - a(Project) - b(Model) - c(Experiment) - d(Analysis) - e(Summary) - a --> b - b --> c - c --> d - d --> e -``` - -- [:material-archive: Project](project.md) – Establish a **project** as a - container for structure and experiment parameters, measured and calculated - data, analysis settings and results. -- [:material-puzzle: Structure](model.md) – Load an existing **crystallographic - model** in CIF format or define a new one from scratch. -- [:material-microscope: Experiment](experiment.md) – Import **experimental - diffraction data** and configure **instrumental** and other relevant - parameters. -- [:material-calculator: Analysis](analysis.md) – **Calculate the diffraction - pattern** and **optimize the structural model** by refining its parameters to - match experimental measurements. -- [:material-clipboard-text: Summary](summary.md) – Generate a **report** - summarizing the results of the analysis, including refined parameters. - -Each step is described in detail in its respective section, guiding users -through the **entire diffraction data analysis workflow** in EasyDiffraction. diff --git a/pixi.lock b/pixi.lock index 1685316c..1b3db850 100644 --- a/pixi.lock +++ b/pixi.lock @@ -14,14 +14,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gsl-2.8-hbf7d49c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda @@ -32,15 +32,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.7.0-he4ff34a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.8.2-he4ff34a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.12-hc97d973_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -53,16 +53,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -70,77 +70,86 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d1/3b/6103194ea934f1c3a4ea080905c8849f71e83de455c16cb625d25f49b779/chardet-7.4.0.post1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/af/3b86dbd18d8dab5646f5b7c7db5bd9c43108e093864032aabd35d41b127d/diffpy_pdffit2-1.5.2.tar.gz - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/bc/60d93477b653eeb1ddf5f9ec34be689b79234d82dbdded269ac0252715b8/fonttools-4.62.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/8c/db8e79c4c744ebae1dcf25f7dbcc5d7df912cdbcdf7221e761479e8bd04b/gemmi-0.7.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -164,10 +173,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -178,7 +187,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/28/83/36557b04cfdc317ed8a525c4993b23e43a8fbcddaddd78619112ca07138c/msgspec-0.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -202,12 +211,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -226,15 +235,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -242,18 +252,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/94/98/aa2d4b9d28969cc7f62409f9f9fc5b5a853af651255eba03e9bee8546dd9/scipp-25.12.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -262,7 +273,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/53/01/1c0485ae02e645bc517bf5d5a6ca674f62c97e247890b954cbfe85c64dae/spglib-2.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -270,11 +283,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -284,10 +297,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/34/b104c413079874493eed7bf11838b47b697cf1f0ed7e9de374ea37b4e4e0/uv-0.10.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -309,14 +320,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.6-hb5e19a0_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/gsl-2.8-hc707ee6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.2-h14c5de8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.3-h25d91c4_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20260107.1-cxx17_h7ed6875_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-6_he492b99_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-6_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.2-h19cb2f5_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.4-h991f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda @@ -325,14 +336,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.2-h11316ed_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-hf3981d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.68.1-h70048d4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.32-openmp_h9e49c7b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.52.0-h77d7759_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.1-h0d3cbff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.7.0-hf6efa0e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.8.2-hf3170e9_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.12-h894a449_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -345,17 +356,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -363,77 +374,86 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/e9/32/83a15c6077e7f240834ffd9ed78ef12f20f6e1924d7d7986d33f3d2af905/chardet-7.4.0.post1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/eb/bb3ff420acdaf9bcaf94c510f42df11974bc3fc475ef50d619366f33fda3/diffpy_pdffit2-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c1/dc/c409c8ceec0d3119e9ab0b7b1a2e3c76d1f4d66e4a9db5c59e6b7652e7df/fonttools-4.62.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/80/fd758344a72ca7b5e1c5bbdc1d263f3b215d3897941b5f450380445ca0a9/gemmi-0.7.5-cp313-cp313-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -457,10 +477,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -471,7 +491,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8a/d1/b902d38b6e5ba3bdddbec469bba388d647f960aeed7b5b3623a8debe8a76/msgspec-0.20.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -495,12 +515,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -519,15 +539,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -535,18 +556,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/22/75e119e0a200914f88f121cd956e1eb7f72c8ace4b63171f52ba0334d142/scipp-25.12.0-cp313-cp313-macosx_11_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e2/69/1dcb8e967f62759578938db5b29792b82ea8939a2d712e79491fa3e1cf0a/scipp-26.3.1-cp313-cp313-macosx_14_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl @@ -555,7 +577,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1b/a5/174d33068d4383df4be9ee1ea9251f17820a622f3be744ca2ab7334818ee/spglib-2.6.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -563,11 +587,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -577,10 +601,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/34/2e5cd576d312eb1131b615f49ee95ff6efb740965324843617adae729cf2/uv-0.10.9-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -602,14 +624,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gsl-2.8-h8d0574d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.2-h55c6f16_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda @@ -618,14 +640,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.1-hc7d1edf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.7.0-hbfc8e16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.8.2-h7039424_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.12-h20e6be0_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -638,17 +660,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -656,76 +678,85 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/83/d3/80554c1cc15631446c9b90aec6fe63b7310aa0b82d3004f7ba38bd8a8270/chardet-7.4.0.post1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/28/d050c2716c74c6fce9ace360e727e6f86b68212fb6b0ea57c005ebe574ea/diffpy_pdffit2-1.5.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/c7/985c1670aa6d82ef270f04cde11394c168f2002700353bd2bde405e59b8f/fonttools-4.62.0-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/9c/1236dd7d22ed48527286b613c84e3376ea731b65e6734b6e6a0b4d03744c/gemmi-0.7.5-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -749,10 +780,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -763,7 +794,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/57/b6/eff0305961a1d9447ec2b02f8c73c8946f22564d302a504185b730c9a761/msgspec-0.20.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -787,12 +818,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -811,15 +842,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -827,18 +859,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/18/5c/bc0ca94bff65fe0d206a369b54625f8ec7852dfd1d835174692026f34df9/scipp-25.12.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl @@ -847,7 +880,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/47/634fe8323c6c2bfa86e10eb41ebfe410db5e6231aa1727a31ce4f002480f/spglib-2.6.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -855,11 +890,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -869,10 +904,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/35/684f641de4de2b20db7d2163c735b2bb211e3b3c84c241706d6448e5e868/uv-0.10.9-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -892,8 +925,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/gsl-2.8-h5b8d9c4_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda @@ -904,10 +937,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.7.0-h80d1838_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.1-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_11.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.12-h09917c8_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -923,16 +956,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -940,77 +973,86 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/91/d7/47988d40231b41376f5a66346ef3b322c81091dfd4c0f84df5a1e3bb06b5/chardet-7.4.0.post1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1f/0c/6826cb2151628c59cca66ca6089ff910ab3ccd62b0524c2b398dc145ee52/diffpy_pdffit2-1.5.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/7a/e25245a30457595740041dba9d0ea8ec1b2517f2f1a6a741f15eba1a4edc/fonttools-4.62.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/ab/7d7463cda94f8b68b969ea97aaad679655a0e436efd6a643e528a8de114e/gemmi-0.7.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -1034,10 +1076,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -1048,7 +1090,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/25/5e8080fe0117f799b1b68008dc29a65862077296b92550632de015128579/msgspec-0.20.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -1071,12 +1113,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -1094,35 +1136,38 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/eb/d1/b3cd2733a96a36c54c36889b2cfdd0331c1e5b57fa1757485a22d0ec3142/scipp-25.12.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl @@ -1131,7 +1176,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/56/a31e8d3c9e8d21100b83bbe1c1f3f7c94db317393a229e193461e5e6d2a4/spglib-2.6.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -1139,11 +1186,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -1153,10 +1200,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/e9/adf7a12136573937d12ac189569e2e90e7fad18b458192083df6986f3013/uv-0.10.9-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -1172,7 +1217,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl - pypi: ./ - py311-dev: + py-311-env: channels: - url: https://conda.anaconda.org/conda-forge/ indexes: @@ -1186,14 +1231,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gsl-2.8-hbf7d49c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda @@ -1203,17 +1248,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.7.0-he4ff34a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.8.2-he4ff34a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda @@ -1225,16 +1270,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl @@ -1242,78 +1287,87 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/f4/44d3b830a20e89ff82a3134912d9a1cf6084d64f3b95dcad40f74449a654/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e3/a2/dab58511fbeef06dd88866568ea1a11b2f15654223cafc2681e2da84b1f2/chardet-7.4.0.post1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/af/3b86dbd18d8dab5646f5b7c7db5bd9c43108e093864032aabd35d41b127d/diffpy_pdffit2-1.5.2.tar.gz - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/d7/8e4845993ee233c2023d11babe9b3dae7d30333da1d792eeccebcb77baab/fonttools-4.62.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/48/eb/46e443fc70b4aabe6e775521ff476aefb051db9acabb16a5cb51f04e3e2b/gemmi-0.7.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -1337,10 +1391,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -1351,7 +1405,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/6b/96/5c095b940de3aa6b43a71ec76275ac3537b21bd45c7499b5a17a429110fa/msgspec-0.20.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -1376,12 +1430,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -1400,15 +1454,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -1416,18 +1471,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4e/b6/ffe0bb67cec66cd450acff599bb07507bbf5ffda1a3a15dd2d8dbe7a6da7/scipp-25.12.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d4/06/19ff1efd58b85906149ce83dfddce23252cea5bec7e0fa5f834336cfe836/scipp-26.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -1436,7 +1492,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/55/41/591cd1e94254c20f00bb1f32c0b1a6de68c03d54e6daf78dd7b146d0b3fc/spglib-2.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/21/dd/3b7c53f1dbbf736fd27041aee68f8ac52226b610f914085b1652c2323442/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -1444,11 +1502,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -1458,10 +1516,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/34/b104c413079874493eed7bf11838b47b697cf1f0ed7e9de374ea37b4e4e0/uv-0.10.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -1484,14 +1540,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.6-hb5e19a0_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/gsl-2.8-hc707ee6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.2-h14c5de8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.3-h25d91c4_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20260107.1-cxx17_h7ed6875_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-6_he492b99_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-6_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.2-h19cb2f5_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.4-h991f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda @@ -1499,14 +1555,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.2-h11316ed_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.68.1-h70048d4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.32-openmp_h9e49c7b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.52.0-h77d7759_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.1-h0d3cbff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.7.0-hf6efa0e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.8.2-hf3170e9_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.15-ha9537fe_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda @@ -1518,17 +1574,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl @@ -1536,78 +1592,87 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3e/38/fe380893cbba72febb24d5dc0c2f9ac99f437153c36a409a8e254ed77bb6/chardet-7.4.0.post1-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/9e/0e27056c6165ab3e2536d5efbc358cf495e6cd57fbaf51e68b1113fa7771/diffpy_pdffit2-1.5.2-cp311-cp311-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c0/7a/9aeec114bc9fc00d757a41f092f7107863d372e684a5b5724c043654477c/fonttools-4.62.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7f/79/b13830a65bf9fc85474a984604f094cc18817dc93a784f4c567a2dc05169/gemmi-0.7.5-cp311-cp311-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/95/a825894f3e45cbac7554c4e97314ce886b233a20033787eda755ca8fecc7/h5py-3.16.0-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -1631,10 +1696,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -1645,7 +1710,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/03/59/fdcb3af72f750a8de2bcf39d62ada70b5eb17b06d7f63860e0a679cb656b/msgspec-0.20.0-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -1670,12 +1735,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -1694,15 +1759,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -1710,18 +1776,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/12/0d/3f98a936a30bff4a460b51b9f85c4d994f94249930b2d8bedeb8111a359e/scipp-25.12.0-cp311-cp311-macosx_11_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/81/21/4962b1daddf0422e56c5ed4c41bea1ccb6d2a9ab72b795196835a20969c7/scipp-26.3.1-cp311-cp311-macosx_14_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl @@ -1730,7 +1797,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/44/30888e2a5b2fa2e6df18606b442cb8b126b0bea5a2f1ec4a2a82538ffecf/spglib-2.6.0-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -1738,11 +1807,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -1752,10 +1821,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/34/2e5cd576d312eb1131b615f49ee95ff6efb740965324843617adae729cf2/uv-0.10.9-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -1778,14 +1845,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gsl-2.8-h8d0574d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.2-h55c6f16_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda @@ -1793,14 +1860,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.1-hc7d1edf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.7.0-hbfc8e16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.8.2-h7039424_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.15-h8561d8f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda @@ -1812,17 +1879,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl @@ -1830,77 +1897,86 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/5e/24/3c1522d777b66e2e3615ee33d1d4291c47b0ec258a9471b559339b01fac5/chardet-7.4.0.post1-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ce/c9/7b61255980383781774d9857aa9e97fe7e9b8b08f97c0974afeef3083dd9/diffpy_pdffit2-1.5.2-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/33/63d79ca41020dd460b51f1e0f58ad1ff0a36b7bcbdf8f3971d52836581e9/fonttools-4.62.0-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/15/26cac702cdf6281ddeb185d5912ce14e555e277c6e4caeb1d36966e43822/gemmi-0.7.5-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -1924,10 +2000,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -1938,7 +2014,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/5a/15/3c225610da9f02505d37d69a77f4a2e7daae2a125f99d638df211ba84e59/msgspec-0.20.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -1963,12 +2039,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -1987,15 +2063,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -2003,18 +2080,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6a/3a/ab0eb61593569d5a0d080002e4b8c0998cb1116d8710781b7225c304b880/scipp-25.12.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/60/54/5011adb56853caabfd90686c2e543d1e3c76a8ef2755809b7e12e3f3583b/scipp-26.3.1-cp311-cp311-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl @@ -2023,7 +2101,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/ca/270d463f6c34f539bb55acdab14099c092d3be28c8af64d61399aa07610c/spglib-2.6.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/d7/6d/b8b78b5b80f3c3ab3f7fa90faa195ec3401f6d884b60221260fd4d51864c/sqlalchemy-2.0.48-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -2031,11 +2111,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -2045,10 +2125,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/35/684f641de4de2b20db7d2163c735b2bb211e3b3c84c241706d6448e5e868/uv-0.10.9-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -2069,8 +2147,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/gsl-2.8-h5b8d9c4_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda @@ -2080,10 +2158,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.7.0-h80d1838_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.1-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_11.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-h3155e25_2.conda @@ -2098,16 +2176,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl @@ -2115,78 +2193,87 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fe/1f/a853b73d386521fd44b7f67ded6b17b7b2367067d9106a5c4b44f9a34274/charset_normalizer-3.4.5-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/91/d7/47988d40231b41376f5a66346ef3b322c81091dfd4c0f84df5a1e3bb06b5/chardet-7.4.0.post1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/33/fae9a52a6cb97efd21176303dfef44e487b56e3473c1329e019d5682d158/diffpy_pdffit2-1.5.2-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/77/ce/f5a4c42c117f8113ce04048053c128d17426751a508f26398110c993a074/fonttools-4.62.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b9/5e/62402bf021183bc6122cb01b8f1be17cac67545713fb30f888f59357a782/gemmi-0.7.5-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/3a/efb2cf697fbccdf75b24e2c18025e7dfa54c4f31fab75c51d0fe79942cef/greenlet-3.3.2-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -2210,10 +2297,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -2224,7 +2311,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/89/5e/406b7d578926b68790e390d83a1165a9bfc2d95612a1a9c1c4d5c72ea815/msgspec-0.20.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -2248,12 +2335,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -2271,35 +2358,38 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/79/c3/3e75075c7f71735f22b66fab0481f2c98e3a4d58cba55cb50ba29114bcf6/pywinpty-3.0.3-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/75/6a3786de6645ac2ccd94fbf83c59cc6b929bfa3a89cb62c8cb3be4de0606/scipp-25.12.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e6/0d/8882a4c7a5ebe59a46b709e82411d9c730d67250d41a2e11bc4bcd4d431d/scipp-26.3.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl @@ -2308,7 +2398,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/a8/d89e1bde525baba10eb8d0be79a5bbaf56c59a47b32bb954866d96a228e3/spglib-2.6.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/58/d5/dd767277f6feef12d05651538f280277e661698f617fa4d086cce6055416/sqlalchemy-2.0.48-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -2316,11 +2408,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -2330,10 +2422,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/e9/adf7a12136573937d12ac189569e2e90e7fad18b458192083df6986f3013/uv-0.10.9-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -2350,7 +2440,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/93/22/b85eca6fa2ad9491af48c973e4c8cf6b103a73dbb271fe3346949449fca0/yarl-1.23.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl - pypi: ./ - py313-dev: + py-313-env: channels: - url: https://conda.anaconda.org/conda-forge/ indexes: @@ -2364,14 +2454,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gsl-2.8-hbf7d49c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda @@ -2382,15 +2472,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.7.0-he4ff34a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.8.2-he4ff34a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.12-hc97d973_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -2403,16 +2493,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -2420,77 +2510,86 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d1/3b/6103194ea934f1c3a4ea080905c8849f71e83de455c16cb625d25f49b779/chardet-7.4.0.post1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/af/3b86dbd18d8dab5646f5b7c7db5bd9c43108e093864032aabd35d41b127d/diffpy_pdffit2-1.5.2.tar.gz - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/bc/60d93477b653eeb1ddf5f9ec34be689b79234d82dbdded269ac0252715b8/fonttools-4.62.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/8c/db8e79c4c744ebae1dcf25f7dbcc5d7df912cdbcdf7221e761479e8bd04b/gemmi-0.7.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -2514,10 +2613,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -2528,7 +2627,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/28/83/36557b04cfdc317ed8a525c4993b23e43a8fbcddaddd78619112ca07138c/msgspec-0.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -2552,12 +2651,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -2576,15 +2675,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -2592,18 +2692,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/94/98/aa2d4b9d28969cc7f62409f9f9fc5b5a853af651255eba03e9bee8546dd9/scipp-25.12.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -2612,7 +2713,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/53/01/1c0485ae02e645bc517bf5d5a6ca674f62c97e247890b954cbfe85c64dae/spglib-2.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -2620,11 +2723,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -2634,10 +2737,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/34/b104c413079874493eed7bf11838b47b697cf1f0ed7e9de374ea37b4e4e0/uv-0.10.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -2659,14 +2760,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.6-hb5e19a0_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/gsl-2.8-hc707ee6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.2-h14c5de8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.3-h25d91c4_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20260107.1-cxx17_h7ed6875_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-6_he492b99_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.2.0-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.2.0-h8616949_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-6_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.2-h19cb2f5_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.4-h991f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda @@ -2675,14 +2776,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.2-h11316ed_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-hf3981d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.68.1-h70048d4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.32-openmp_h9e49c7b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.52.0-h77d7759_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.1-h0d3cbff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.7.0-hf6efa0e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.8.2-hf3170e9_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.1-hb6871ef_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.12-h894a449_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -2695,17 +2796,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -2713,77 +2814,86 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/e9/32/83a15c6077e7f240834ffd9ed78ef12f20f6e1924d7d7986d33f3d2af905/chardet-7.4.0.post1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/eb/bb3ff420acdaf9bcaf94c510f42df11974bc3fc475ef50d619366f33fda3/diffpy_pdffit2-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c1/dc/c409c8ceec0d3119e9ab0b7b1a2e3c76d1f4d66e4a9db5c59e6b7652e7df/fonttools-4.62.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/80/fd758344a72ca7b5e1c5bbdc1d263f3b215d3897941b5f450380445ca0a9/gemmi-0.7.5-cp313-cp313-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -2807,10 +2917,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -2821,7 +2931,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8a/d1/b902d38b6e5ba3bdddbec469bba388d647f960aeed7b5b3623a8debe8a76/msgspec-0.20.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -2845,12 +2955,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -2869,15 +2979,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -2885,18 +2996,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/22/75e119e0a200914f88f121cd956e1eb7f72c8ace4b63171f52ba0334d142/scipp-25.12.0-cp313-cp313-macosx_11_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e2/69/1dcb8e967f62759578938db5b29792b82ea8939a2d712e79491fa3e1cf0a/scipp-26.3.1-cp313-cp313-macosx_14_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl @@ -2905,7 +3017,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1b/a5/174d33068d4383df4be9ee1ea9251f17820a622f3be744ca2ab7334818ee/spglib-2.6.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -2913,11 +3027,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -2927,10 +3041,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/34/2e5cd576d312eb1131b615f49ee95ff6efb740965324843617adae729cf2/uv-0.10.9-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -2952,14 +3064,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gsl-2.8-h8d0574d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.2-h55c6f16_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.4-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda @@ -2968,14 +3080,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.1-hc7d1edf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.7.0-hbfc8e16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.8.2-h7039424_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.1-hd24854e_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.12-h20e6be0_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -2988,17 +3100,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -3006,76 +3118,85 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/83/d3/80554c1cc15631446c9b90aec6fe63b7310aa0b82d3004f7ba38bd8a8270/chardet-7.4.0.post1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/28/d050c2716c74c6fce9ace360e727e6f86b68212fb6b0ea57c005ebe574ea/diffpy_pdffit2-1.5.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/c7/985c1670aa6d82ef270f04cde11394c168f2002700353bd2bde405e59b8f/fonttools-4.62.0-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/9c/1236dd7d22ed48527286b613c84e3376ea731b65e6734b6e6a0b4d03744c/gemmi-0.7.5-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -3099,10 +3220,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -3113,7 +3234,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/57/b6/eff0305961a1d9447ec2b02f8c73c8946f22564d302a504185b730c9a761/msgspec-0.20.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -3137,12 +3258,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -3161,15 +3282,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -3177,18 +3299,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/18/5c/bc0ca94bff65fe0d206a369b54625f8ec7852dfd1d835174692026f34df9/scipp-25.12.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl @@ -3197,7 +3320,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/47/634fe8323c6c2bfa86e10eb41ebfe410db5e6231aa1727a31ce4f002480f/spglib-2.6.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -3205,11 +3330,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -3219,10 +3344,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/35/684f641de4de2b20db7d2163c735b2bb211e3b3c84c241706d6448e5e868/uv-0.10.9-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -3242,8 +3365,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/gsl-2.8-h5b8d9c4_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda @@ -3254,10 +3377,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.7.0-h80d1838_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.1-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_11.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.12-h09917c8_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda @@ -3273,16 +3396,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl @@ -3290,77 +3413,86 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/91/d7/47988d40231b41376f5a66346ef3b322c81091dfd4c0f84df5a1e3bb06b5/chardet-7.4.0.post1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/e8/c14cc8af8cd38e86887053843382629bd8ebd117f83f15eb1194d65a2c9d/cryspy-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1f/0c/6826cb2151628c59cca66ca6089ff910ab3ccd62b0524c2b398dc145ee52/diffpy_pdffit2-1.5.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/7a/e25245a30457595740041dba9d0ea8ec1b2517f2f1a6a741f15eba1a4edc/fonttools-4.62.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/ab/7d7463cda94f8b68b969ea97aaad679655a0e436efd6a643e528a8de114e/gemmi-0.7.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl @@ -3384,10 +3516,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl @@ -3398,7 +3530,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/25/5e8080fe0117f799b1b68008dc29a65862077296b92550632de015128579/msgspec-0.20.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -3421,12 +3553,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl @@ -3444,35 +3576,38 @@ environments: - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/eb/d1/b3cd2733a96a36c54c36889b2cfdd0331c1e5b57fa1757485a22d0ec3142/scipp-25.12.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl @@ -3481,7 +3616,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/56/a31e8d3c9e8d21100b83bbe1c1f3f7c94db317393a229e193461e5e6d2a4/spglib-2.6.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -3489,11 +3626,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl @@ -3503,10 +3640,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/e9/adf7a12136573937d12ac189569e2e90e7fad18b458192083df6986f3013/uv-0.10.9-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/4a/c6fd02a642bbe4e9f25cdd3714a328e3fc3eb6bd7b5e96f1a2285bd928b9/varname-0.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -3728,17 +3863,16 @@ packages: requires_dist: - typing-extensions>=4.0.0 ; python_full_version < '3.9' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl name: anyio - version: 4.12.1 - sha256: d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c + version: 4.13.0 + sha256: 08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708 requires_dist: - exceptiongroup>=1.0.2 ; python_full_version < '3.11' - idna>=2.8 - typing-extensions>=4.5 ; python_full_version < '3.13' - - trio>=0.32.0 ; python_full_version >= '3.10' and extra == 'trio' - - trio>=0.31.0 ; python_full_version < '3.10' and extra == 'trio' - requires_python: '>=3.9' + - trio>=0.32.0 ; extra == 'trio' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl name: appnope version: 0.1.4 @@ -3811,16 +3945,17 @@ packages: requires_dist: - setuptools - flake8 ; extra == 'qa' -- pypi: https://files.pythonhosted.org/packages/3e/9b/9b55b4d4855743de61ba91566d03b2560285ed8fc0387b9cf914795d4abf/ase-3.27.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl name: ase - version: 3.27.0 - sha256: 058c48ea504fe7fbbe7c932f778415243ef2df45b1ab869866f24efcc17f0538 + version: 3.28.0 + sha256: 0e24056302d7307b7247f90de281de15e3031c14cf400bedb1116c3b0d0e50b8 requires_dist: - numpy>=1.21.6 - scipy>=1.8.1 - matplotlib>=3.5.2 - sphinx ; extra == 'docs' - sphinx-book-theme ; extra == 'docs' + - sphinxcontrib-video ; extra == 'docs' - sphinx-gallery ; extra == 'docs' - pillow ; extra == 'docs' - pytest>=7.4.0 ; extra == 'test' @@ -3855,17 +3990,17 @@ packages: - pytest-cov ; extra == 'test' - pytest-xdist ; extra == 'test' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl name: async-lru - version: 2.2.0 - sha256: e2c1cf731eba202b59c5feedaef14ffd9d02ad0037fcda64938699f2c380eafe + version: 2.3.0 + sha256: eea27b01841909316f2cc739807acea1c623df2be8c5cfad7583286397bb8315 requires_dist: - typing-extensions>=4.0.0 ; python_full_version < '3.11' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl name: attrs - version: 25.4.0 - sha256: adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373 + version: 26.1.0 + sha256: c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl name: autopep8 @@ -3935,10 +4070,10 @@ packages: version: 1.9.0 sha256: ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl name: build - version: 1.4.0 - sha256: 6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596 + version: 1.4.2 + sha256: 7a4d8651ea877cb2a89458b1b198f2e69f536c95e89129dbf5d448045d60db88 requires_dist: - packaging>=24.0 - pyproject-hooks @@ -4136,35 +4271,70 @@ packages: version: 3.5.0 sha256: a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/03/f4/44d3b830a20e89ff82a3134912d9a1cf6084d64f3b95dcad40f74449a654/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/3e/38/fe380893cbba72febb24d5dc0c2f9ac99f437153c36a409a8e254ed77bb6/chardet-7.4.0.post1-cp311-cp311-macosx_10_9_x86_64.whl + name: chardet + version: 7.4.0.post1 + sha256: 2769be12361a6c7873392e435c708eca88c9f0fb6a647af75fa1386db64032d6 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/5e/24/3c1522d777b66e2e3615ee33d1d4291c47b0ec258a9471b559339b01fac5/chardet-7.4.0.post1-cp311-cp311-macosx_11_0_arm64.whl + name: chardet + version: 7.4.0.post1 + sha256: 8e1eaa942ae81d43d535092ff3ba660c967344178cc3876b54834a56c1207f3a + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/83/d3/80554c1cc15631446c9b90aec6fe63b7310aa0b82d3004f7ba38bd8a8270/chardet-7.4.0.post1-cp313-cp313-macosx_11_0_arm64.whl + name: chardet + version: 7.4.0.post1 + sha256: e6285d35f79d0cdc8838d3cb01876f979c8419a74662e8de39444e40639e0b2b + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/91/d7/47988d40231b41376f5a66346ef3b322c81091dfd4c0f84df5a1e3bb06b5/chardet-7.4.0.post1-py3-none-any.whl + name: chardet + version: 7.4.0.post1 + sha256: 57a62ef50f69bc2fb3a3ea1ffffec6d10f3d2112d3b05d6e3cb15c2c9b55f6cc + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d1/3b/6103194ea934f1c3a4ea080905c8849f71e83de455c16cb625d25f49b779/chardet-7.4.0.post1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: chardet + version: 7.4.0.post1 + sha256: 329aa8766c4917d3acc1b1d0462f0b2e820e24e9f341d0f858aee85396ae3002 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e3/a2/dab58511fbeef06dd88866568ea1a11b2f15654223cafc2681e2da84b1f2/chardet-7.4.0.post1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: chardet + version: 7.4.0.post1 + sha256: ad98a6c2e61624b1120919353d222121b8f5848b9d33c885d949fe0235575682 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e9/32/83a15c6077e7f240834ffd9ed78ef12f20f6e1924d7d7986d33f3d2af905/chardet-7.4.0.post1-cp313-cp313-macosx_10_13_x86_64.whl + name: chardet + version: 7.4.0.post1 + sha256: efdb3785c8700b3d0b354553827a166480a439f9754f7366f795bbe8b42d6daf + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl name: charset-normalizer - version: 3.4.5 - sha256: 5bcb3227c3d9aaf73eaaab1db7ccd80a8995c509ee9941e2aae060ca6e4e5d81 + version: 3.4.6 + sha256: 11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl +- pypi: https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: charset-normalizer - version: 3.4.5 - sha256: 610f72c0ee565dfb8ae1241b666119582fdbfe7c0975c175be719f940e110694 + version: 3.4.6 + sha256: 530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: charset-normalizer - version: 3.4.5 - sha256: 0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819 + version: 3.4.6 + sha256: 9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl name: charset-normalizer - version: 3.4.5 - sha256: e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7 + version: 3.4.6 + sha256: 82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl +- pypi: https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl name: charset-normalizer - version: 3.4.5 - sha256: ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23 + version: 3.4.6 + sha256: 572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/fe/1f/a853b73d386521fd44b7f67ded6b17b7b2367067d9106a5c4b44f9a34274/charset_normalizer-3.4.5-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl name: charset-normalizer - version: 3.4.5 - sha256: f8102ae93c0bc863b1d41ea0f4499c20a83229f52ed870850892df555187154a + version: 3.4.6 + sha256: a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4 requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl name: click @@ -4390,59 +4560,79 @@ packages: - pytest-xdist ; extra == 'test-no-images' - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/df/91/4a151c94320458895049a3e23b7b2cfc08953c60b14892de837e8eb51d0a/copier-9.14.0-py3-none-any.whl + name: copier + version: 9.14.0 + sha256: e12a18cfef22e67254e5229f0b4bdab85e1e3e82926e448226be0b70d0f4de53 + requires_dist: + - colorama>=0.4.6 + - dunamai>=1.7.0 + - funcy>=1.17 + - jinja2-ansible-filters>=1.3.1 + - jinja2>=3.1.5 + - packaging>=23.0 + - pathspec>=0.9.0 + - platformdirs>=4.3.6 + - plumbum>=1.6.9 + - pydantic>=2.4.2 + - pygments>=2.7.1 + - pyyaml>=5.3.1 + - questionary>=1.8.1 + - typing-extensions>=4.0.0,<5.0.0 ; python_full_version < '3.11' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl name: coverage - version: 7.13.4 - sha256: 2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f + version: 7.13.5 + sha256: 941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl name: coverage - version: 7.13.4 - sha256: 8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7 + version: 7.13.5 + sha256: 145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl name: coverage - version: 7.13.4 - sha256: 3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac + version: 7.13.5 + sha256: 66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl name: coverage - version: 7.13.4 - sha256: d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053 + version: 7.13.5 + sha256: 631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl name: coverage - version: 7.13.4 - sha256: b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9 + version: 7.13.5 + sha256: 5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: coverage - version: 7.13.4 - sha256: e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd + version: 7.13.5 + sha256: ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: coverage - version: 7.13.4 - sha256: b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b + version: 7.13.5 + sha256: 78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl name: coverage - version: 7.13.4 - sha256: 19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11 + version: 7.13.5 + sha256: 258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' @@ -4482,10 +4672,10 @@ packages: requires_dist: - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'macos-listener' requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl name: dask - version: 2026.1.2 - sha256: 46a0cf3b8d87f78a3d2e6b145aea4418a6d6d606fe6a16c79bd8ca2bb862bc91 + version: 2026.3.0 + sha256: be614b9242b0b38288060fb2d7696125946469c98a1c30e174883fd199e0428d requires_dist: - click>=8.1 - cloudpickle>=3.0.0 @@ -4499,7 +4689,7 @@ packages: - dask[array] ; extra == 'dataframe' - pandas>=2.0 ; extra == 'dataframe' - pyarrow>=16.0 ; extra == 'dataframe' - - distributed>=2026.1.2,<2026.1.3 ; extra == 'distributed' + - distributed>=2026.3.0,<2026.3.1 ; extra == 'distributed' - bokeh>=3.1.0 ; extra == 'diagnostics' - jinja2>=2.10.3 ; extra == 'diagnostics' - dask[array,dataframe,diagnostics,distributed] ; extra == 'complete' @@ -4539,10 +4729,10 @@ packages: version: 0.7.1 sha256: a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' -- pypi: https://files.pythonhosted.org/packages/13/40/e412e277c43983693456d7f11f2bc72ca9209aa1342255bb446496d2fb48/dfo_ls-1.6.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl name: dfo-ls - version: 1.6.2 - sha256: 961d15e7194f3868e9e48da45010cb6d24d85491007fbee4e38a9f3ab8050cef + version: 1.6.5 + sha256: d147d42e471e240f9abf8bc38351a88f555ea6a8fcfd83119bbbf93c36f75ab2 requires_dist: - setuptools - numpy @@ -4610,15 +4800,15 @@ packages: - numpy - pycifrw requires_python: '>=3.11,<3.14' -- pypi: https://files.pythonhosted.org/packages/3f/5e/fcb9a14641d65d9fc79efd7c97eef38aa8f1d99c7cef657b6aedd742efef/diffpy_utils-3.7.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl name: diffpy-utils - version: 3.7.1 - sha256: 8bf33eb3e228bf6a18242e4cc01cf4b7bff307fbef90f745bf7d680c7ed4d3b7 + version: 3.7.2 + sha256: 6100600736791a8e4638e3dd476704f4dabe3cab75bcb5c60c83c16a2032519a requires_dist: - numpy - xraydb - scipy - requires_python: '>=3.11,<3.15' + requires_python: '>=3.10,<3.15' - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl name: dill version: 0.4.1 @@ -4658,33 +4848,49 @@ packages: - trio>=0.30 ; extra == 'trio' - wmi>=1.5.1 ; sys_platform == 'win32' and extra == 'wmi' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - name: docformatter - version: 1.7.7 - sha256: 7af49f8a46346a77858f6651f431b882c503c2f4442c8b4524b920c863277834 - requires_dist: - - charset-normalizer>=3.0.0,<4.0.0 - - tomli>=2.0.0,<3.0.0 ; python_full_version < '3.11' and extra == 'tomli' - - untokenize>=0.1.1,<0.2.0 - requires_python: '>=3.9,<4.0' +- pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + name: docstring-parser-fork + version: 0.0.14 + sha256: 4c544f234ef2cc2749a3df32b70c437d77888b1099143a1ad5454452c574b9af + requires_dist: + - docstring-parser[docs] ; extra == 'dev' + - docstring-parser[test] ; extra == 'dev' + - pre-commit>=2.16.0 ; python_full_version >= '3.9' and extra == 'dev' + - pydoctor>=25.4.0 ; extra == 'docs' + - pytest ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/87/10/2c7edbf230e5c507d38367af498fa94258ed97205d9b4b6f63a921fe9c49/dunamai-1.26.0-py3-none-any.whl + name: dunamai + version: 1.26.0 + sha256: f584edf0fda0d308cce0961f807bc90a8fe3d9ff4d62f94e72eca7b43f0ed5f6 + requires_dist: + - importlib-metadata>=1.6.0 ; python_full_version < '3.8' + - packaging>=20.9 + requires_python: '>=3.5' - pypi: ./ name: easydiffraction - version: 0.10.2+devdirty36 - sha256: c26412f987f3f60607ea00f77d4f12aadc3b38ea31833f634b218e09965dbdbc + version: 0.10.2+dev46 + sha256: 9ada6af993ed7303fd58593648683db9afb40a7c191b5f5f7384f37934362c42 requires_dist: - asciichartpy - asteval - bumps - colorama - cryspy + - darkdetect - dfo-ls - diffpy-pdffit2 - diffpy-utils - essdiffraction - gemmi + - jupyterlab - lmfit - numpy + - pandas + - pixi-kernel + - plotly - pooch + - py3dmol - rich - scipy - sympy @@ -4693,67 +4899,37 @@ packages: - typer - uncertainties - varname - - build ; extra == 'all' - - darkdetect ; extra == 'all' - - docformatter ; extra == 'all' - - interrogate ; extra == 'all' - - jinja2 ; extra == 'all' - - jupyterquiz ; extra == 'all' - - jupytext ; extra == 'all' - - mike ; extra == 'all' - - mkdocs ; extra == 'all' - - mkdocs-autorefs ; extra == 'all' - - mkdocs-jupyter ; extra == 'all' - - mkdocs-markdownextradata-plugin ; extra == 'all' - - mkdocs-material ; extra == 'all' - - mkdocs-plugin-inline-svg ; extra == 'all' - - mkdocstrings-python ; extra == 'all' - - nbmake ; extra == 'all' - - nbqa ; extra == 'all' - - nbstripout ; extra == 'all' - - pandas ; extra == 'all' - - plotly ; extra == 'all' - - pre-commit ; extra == 'all' - - py3dmol ; extra == 'all' - - pytest ; extra == 'all' - - pytest-cov ; extra == 'all' - - pytest-xdist ; extra == 'all' - - pyyaml ; extra == 'all' - - radon ; extra == 'all' - - ruff ; extra == 'all' - - validate-pyproject[all] ; extra == 'all' - - versioningit ; extra == 'all' - build ; extra == 'dev' - - docformatter ; extra == 'dev' + - copier ; extra == 'dev' + - format-docstring ; extra == 'dev' + - gitpython ; extra == 'dev' - interrogate ; extra == 'dev' - jinja2 ; extra == 'dev' - jupyterquiz ; extra == 'dev' - jupytext ; extra == 'dev' + - mike ; extra == 'dev' + - mkdocs ; extra == 'dev' + - mkdocs-autorefs ; extra == 'dev' + - mkdocs-jupyter ; extra == 'dev' + - mkdocs-markdownextradata-plugin ; extra == 'dev' + - mkdocs-material ; extra == 'dev' + - mkdocs-plugin-inline-svg ; extra == 'dev' + - mkdocstrings-python ; extra == 'dev' - nbmake ; extra == 'dev' - nbqa ; extra == 'dev' - nbstripout ; extra == 'dev' - pre-commit ; extra == 'dev' + - pydoclint ; extra == 'dev' - pytest ; extra == 'dev' - pytest-cov ; extra == 'dev' - pytest-xdist ; extra == 'dev' + - pyyaml ; extra == 'dev' - radon ; extra == 'dev' - ruff ; extra == 'dev' + - spdx-headers ; extra == 'dev' - validate-pyproject[all] ; extra == 'dev' - versioningit ; extra == 'dev' - - mike ; extra == 'docs' - - mkdocs ; extra == 'docs' - - mkdocs-autorefs ; extra == 'docs' - - mkdocs-jupyter ; extra == 'docs' - - mkdocs-markdownextradata-plugin ; extra == 'docs' - - mkdocs-material ; extra == 'docs' - - mkdocs-plugin-inline-svg ; extra == 'docs' - - mkdocstrings-python ; extra == 'docs' - - pyyaml ; extra == 'docs' - - darkdetect ; extra == 'visualization' - - pandas ; extra == 'visualization' - - plotly ; extra == 'visualization' - - py3dmol ; extra == 'visualization' - requires_python: '>=3.11,<3.14' + requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl name: email-validator version: 2.3.0 @@ -4762,20 +4938,20 @@ packages: - dnspython>=2.0.0 - idna>=2.0.0 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/06/42/75a6098682e3d4d7d5dfcb94d0dd48dccd9e106112992a99524243bc63e6/essdiffraction-26.3.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/55/cd/d729a1bb63fa95387228cc508552dea4685ea0116e484e73238db10f9521/essdiffraction-26.4.0-py3-none-any.whl name: essdiffraction - version: 26.3.0 - sha256: 3e3063d8af817ece183258b35579491d7edf85c4ca95fa3f1cef7877ad24365a + version: 26.4.0 + sha256: a58c1ac09c2196ae3769ab8c2d1f8c0152e4e1a69a02bc286caf419cdaa9ec1d requires_dist: - dask>=2022.1.0 - - essreduce>=26.2.1 + - essreduce>=26.4.0 - graphviz - numpy>=2 - plopp>=26.2.0 - pythreejs>=2.4.1 - sciline>=25.4.1 - scipp>=25.11.0 - - scippneutron>=25.2.0 + - scippneutron>=26.3.0 - scippnexus>=23.12.0 - tof>=25.12.0 - ncrystal[cif]>=4.1.0 @@ -4785,13 +4961,13 @@ packages: - pytest>=7.0 ; extra == 'test' - ipywidgets>=8.1.7 ; extra == 'test' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/a8/bf/530a2b63de72bdb8e518049a1e6639d1e6d79e591bfcdaaae08a9a3a7b84/essreduce-26.3.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/f1/bb/66c80d7f801b191f7b3ee6149a39be9d1a8a81c233e20adaf796d171f93a/essreduce-26.4.0-py3-none-any.whl name: essreduce - version: 26.3.1 - sha256: 0f629a52ad1793904cc41f6298229321f249f87e972b35f0b0fcfad8f280a78b + version: 26.4.0 + sha256: 06a9ebf58cba3cc29ac70f7b89a3e596be92d6a61130361b8c19fa8afec2b1b5 requires_dist: - sciline>=25.11.0 - - scipp>=25.4.0 + - scipp>=26.3.0 - scippneutron>=25.11.1 - scippnexus>=25.6.0 - graphviz>=0.20 ; extra == 'test' @@ -4854,15 +5030,15 @@ packages: - pytest-benchmark ; extra == 'devel' - pytest-cache ; extra == 'devel' - validictory ; extra == 'devel' -- pypi: https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl name: filelock - version: 3.25.1 - sha256: 18972df45473c4aa2c7921b609ee9ca4925910cc3a0fb226c96b92fc224ef7bf + version: 3.25.2 + sha256: ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/77/ce/f5a4c42c117f8113ce04048053c128d17426751a508f26398110c993a074/fonttools-4.62.0-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl name: fonttools - version: 4.62.0 - sha256: 4da779e8f342a32856075ddb193b2a024ad900bc04ecb744014c32409ae871ed + version: 4.62.1 + sha256: 68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4893,10 +5069,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/82/c7/985c1670aa6d82ef270f04cde11394c168f2002700353bd2bde405e59b8f/fonttools-4.62.0-cp313-cp313-macosx_10_13_universal2.whl +- pypi: https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl name: fonttools - version: 4.62.0 - sha256: 274c8b8a87e439faf565d3bcd3f9f9e31bca7740755776a4a90a4bfeaa722efa + version: 4.62.1 + sha256: 9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4927,10 +5103,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/8a/d7/8e4845993ee233c2023d11babe9b3dae7d30333da1d792eeccebcb77baab/fonttools-4.62.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl name: fonttools - version: 4.62.0 - sha256: 591220d5333264b1df0d3285adbdfe2af4f6a45bbf9ca2b485f97c9f577c49ff + version: 4.62.1 + sha256: 7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4961,10 +5137,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/c0/7a/9aeec114bc9fc00d757a41f092f7107863d372e684a5b5724c043654477c/fonttools-4.62.0-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl name: fonttools - version: 4.62.0 - sha256: 153afc3012ff8761b1733e8fbe5d98623409774c44ffd88fbcb780e240c11d13 + version: 4.62.1 + sha256: c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4995,10 +5171,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/c1/dc/c409c8ceec0d3119e9ab0b7b1a2e3c76d1f4d66e4a9db5c59e6b7652e7df/fonttools-4.62.0-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl name: fonttools - version: 4.62.0 - sha256: 93e27131a5a0ae82aaadcffe309b1bae195f6711689722af026862bede05c07c + version: 4.62.1 + sha256: 40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -5029,10 +5205,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e4/33/63d79ca41020dd460b51f1e0f58ad1ff0a36b7bcbdf8f3971d52836581e9/fonttools-4.62.0-cp311-cp311-macosx_10_9_universal2.whl +- pypi: https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: fonttools - version: 4.62.0 - sha256: 196cafef9aeec5258425bd31a4e9a414b2ee0d1557bca184d7923d3d3bcd90f9 + version: 4.62.1 + sha256: 1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -5063,10 +5239,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/f5/7a/e25245a30457595740041dba9d0ea8ec1b2517f2f1a6a741f15eba1a4edc/fonttools-4.62.0-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl name: fonttools - version: 4.62.0 - sha256: 6826a5aa53fb6def8a66bf423939745f415546c4e92478a7c531b8b6282b6c3b + version: 4.62.1 + sha256: e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -5097,10 +5273,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/fb/bc/60d93477b653eeb1ddf5f9ec34be689b79234d82dbdded269ac0252715b8/fonttools-4.62.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: fonttools - version: 4.62.0 - sha256: 106aec9226f9498fc5345125ff7200842c01eda273ae038f5049b0916907acee + version: 4.62.1 + sha256: 6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -5131,6 +5307,15 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.45.0 ; extra == 'all' requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl + name: format-docstring + version: 0.2.7 + sha256: c9d50eafebe0f260e3270ca662ff3a0ed4050f64d95e352f8c5f88d9aede42d6 + requires_dist: + - click>=8.0 + - jupyter-notebook-parser>=0.1.4 + - tomli>=1.1.0 ; python_full_version < '3.11' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl name: fqdn version: 1.5.1 @@ -5286,6 +5471,10 @@ packages: - zstandard ; python_full_version < '3.14' and extra == 'test-full' - tqdm ; extra == 'tqdm' requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl + name: funcy + version: '2.0' + sha256: 53df23c8bb1651b12f095df764bfb057935d49537a56de211b098f4c79614bb0 - pypi: https://files.pythonhosted.org/packages/42/15/26cac702cdf6281ddeb185d5912ce14e555e277c6e4caeb1d36966e43822/gemmi-0.7.5-cp311-cp311-macosx_11_0_arm64.whl name: gemmi version: 0.7.5 @@ -5336,6 +5525,35 @@ packages: - markdown ; extra == 'dev' - flake8 ; extra == 'dev' - wheel ; extra == 'dev' +- pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + name: gitdb + version: 4.0.12 + sha256: 67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf + requires_dist: + - smmap>=3.0.1,<6 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl + name: gitpython + version: 3.1.46 + sha256: 79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058 + requires_dist: + - gitdb>=4.0.1,<5 + - typing-extensions>=3.10.0.2 ; python_full_version < '3.10' + - coverage[toml] ; extra == 'test' + - ddt>=1.1.1,!=1.4.3 ; extra == 'test' + - mock ; python_full_version < '3.8' and extra == 'test' + - mypy==1.18.2 ; python_full_version >= '3.9' and extra == 'test' + - pre-commit ; extra == 'test' + - pytest>=7.3.1 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-sugar ; extra == 'test' + - typing-extensions ; python_full_version < '3.11' and extra == 'test' + - sphinx>=7.1.2,<7.2 ; extra == 'doc' + - sphinx-rtd-theme ; extra == 'doc' + - sphinx-autodoc-typehints ; extra == 'doc' + requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl name: graphviz version: '0.21' @@ -5422,10 +5640,10 @@ packages: - psutil ; extra == 'test' - setuptools ; extra == 'test' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl name: griffelib - version: 2.0.0 - sha256: 01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f + version: 2.0.1 + sha256: b769eed581c0e857d362fc8fcd8e57ecd2330c124b6104ac8b4c1c86d76970aa requires_dist: - pip>=24.0 ; extra == 'pypi' - platformdirs>=4.2 ; extra == 'pypi' @@ -5573,9 +5791,9 @@ packages: - socksio==1.* ; extra == 'socks' - zstandard>=0.18.0 ; extra == 'zstd' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda - sha256: 142a722072fa96cf16ff98eaaf641f54ab84744af81754c292cb81e0881c0329 - md5: 186a18e3ba246eccfc7cff00cd19a870 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + sha256: fbf86c4a59c2ed05bbffb2ba25c7ed94f6185ec30ecb691615d42342baa1a16a + md5: c80d8a3b84358cb967fa81e7075fbc8a depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -5583,32 +5801,32 @@ packages: license: MIT license_family: MIT purls: [] - size: 12728445 - timestamp: 1767969922681 -- conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.2-h14c5de8_0.conda - sha256: f3066beae7fe3002f09c8a412cdf1819f49a2c9a485f720ec11664330cf9f1fe - md5: 30334add4de016489b731c6662511684 + size: 12723451 + timestamp: 1773822285671 +- conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.3-h25d91c4_0.conda + sha256: 1294117122d55246bb83ad5b589e2a031aacdf2d0b1f99fd338aa4394f881735 + md5: 627eca44e62e2b665eeec57a984a7f00 depends: - - __osx >=10.13 + - __osx >=11.0 license: MIT license_family: MIT purls: [] - size: 12263724 - timestamp: 1767970604977 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-hef89b57_0.conda - sha256: 24bc62335106c30fecbcc1dba62c5eba06d18b90ea1061abd111af7b9c89c2d7 - md5: 114e6bfe7c5ad2525eb3597acdbf2300 + size: 12273764 + timestamp: 1773822733780 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda + sha256: 3a7907a17e9937d3a46dfd41cffaf815abad59a569440d1e25177c15fd0684e5 + md5: f1182c91c0de31a7abd40cedf6a5ebef depends: - __osx >=11.0 license: MIT license_family: MIT purls: [] - size: 12389400 - timestamp: 1772209104304 -- pypi: https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl + size: 12361647 + timestamp: 1773822915649 +- pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl name: identify - version: 2.6.17 - sha256: be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0 + version: 2.6.18 + sha256: 8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737 requires_dist: - ukkonen ; extra == 'license' requires_python: '>=3.10' @@ -5622,18 +5840,16 @@ packages: - pytest>=8.3.2 ; extra == 'all' - flake8>=7.1.1 ; extra == 'all' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl name: importlib-metadata - version: 8.7.1 - sha256: 5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151 + version: 9.0.0 + sha256: 2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7 requires_dist: - zipp>=3.20 - pytest>=6,!=8.1.* ; extra == 'test' - packaging ; extra == 'test' - pyfakefs ; extra == 'test' - - flufl-flake8 ; extra == 'test' - pytest-perf>=0.9.2 ; extra == 'test' - - jaraco-test>=5.4 ; extra == 'test' - sphinx>=3.5 ; extra == 'doc' - jaraco-packaging>=9.3 ; extra == 'doc' - rst-linker>=1.9 ; extra == 'doc' @@ -5641,13 +5857,12 @@ packages: - sphinx-lint ; extra == 'doc' - jaraco-tidelift>=1.4 ; extra == 'doc' - ipython ; extra == 'perf' - - pytest-checkdocs>=2.4 ; extra == 'check' + - pytest-checkdocs>=2.14 ; extra == 'check' - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' - pytest-cov ; extra == 'cover' - pytest-enabler>=3.4 ; extra == 'enabler' - - pytest-mypy>=1.0.1 ; extra == 'type' - - mypy<1.19 ; platform_python_implementation == 'PyPy' and extra == 'type' - requires_python: '>=3.9' + - pytest-mypy>=1.0.1 ; platform_python_implementation != 'PyPy' and extra == 'type' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl name: iniconfig version: 2.3.0 @@ -5696,23 +5911,23 @@ packages: - pytest-cov ; extra == 'test' - nbval>=0.9.2 ; extra == 'test' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl name: ipykernel - version: 6.31.0 - sha256: abe5386f6ced727a70e0eb0cf1da801fa7c5fa6ff82147747d5a0406cd8c94af + version: 7.2.0 + sha256: 3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661 requires_dist: - appnope>=0.1.2 ; sys_platform == 'darwin' - comm>=0.1.1 - debugpy>=1.6.5 - ipython>=7.23.1 - - jupyter-client>=8.0.0 - - jupyter-core>=4.12,!=5.0.* + - jupyter-client>=8.8.0 + - jupyter-core>=5.1,!=6.0.* - matplotlib-inline>=0.1 - nest-asyncio>=1.4 - packaging>=22 - psutil>=5.7 - pyzmq>=25 - - tornado>=6.2 + - tornado>=6.4.1 - traitlets>=5.4.0 - coverage[toml] ; extra == 'cov' - matplotlib ; extra == 'cov' @@ -5721,8 +5936,8 @@ packages: - intersphinx-registry ; extra == 'docs' - myst-parser ; extra == 'docs' - pydata-sphinx-theme ; extra == 'docs' - - sphinx ; extra == 'docs' - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx<8.2.0 ; extra == 'docs' - sphinxcontrib-github-alt ; extra == 'docs' - sphinxcontrib-spelling ; extra == 'docs' - trio ; extra == 'docs' @@ -5734,8 +5949,8 @@ packages: - pytest-asyncio>=0.23.5 ; extra == 'test' - pytest-cov ; extra == 'test' - pytest-timeout ; extra == 'test' - - pytest>=7.0,<9 ; extra == 'test' - requires_python: '>=3.9' + - pytest>=7.0,<10 ; extra == 'test' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl name: ipython version: 9.10.0 @@ -5906,16 +6121,25 @@ packages: - markupsafe>=2.0 - babel>=2.7 ; extra == 'i18n' requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl + name: jinja2-ansible-filters + version: 1.3.2 + sha256: e1082f5564917649c76fed239117820610516ec10f87735d0338688800a55b34 + requires_dist: + - jinja2 + - pyyaml + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl name: json5 version: 0.13.0 sha256: 9a08e1dd65f6a4d4c6fa82d216cf2477349ec2346a38fd70cc11d2557499fbcc requires_python: '>=3.8.0' -- pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl name: jsonpointer - version: 3.0.0 - sha256: 13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942 - requires_python: '>=3.7' + version: 3.1.1 + sha256: 8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl name: jsonschema version: 4.26.0 @@ -6034,6 +6258,11 @@ packages: - jupyter-server>=1.1.2 - importlib-metadata>=4.8.3 ; python_full_version < '3.10' requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + name: jupyter-notebook-parser + version: 0.1.4 + sha256: 27b3b67cf898684e646d569f017cb27046774ad23866cb0bdf51d5f76a46476b + requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl name: jupyter-server version: 2.17.0 @@ -6106,10 +6335,10 @@ packages: - pytest-timeout ; extra == 'test' - pytest>=7.0 ; extra == 'test' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl name: jupyterlab - version: 4.5.5 - sha256: a35694a40a8e7f2e82f387472af24e61b22adcce87b5a8ab97a5d9c486202a6d + version: 4.5.6 + sha256: d6b3dac883aa4d9993348e0f8e95b24624f75099aed64eab6a4351a9cdd1e580 requires_dist: - async-lru>=1.0.0 - httpx>=0.25.0,<1 @@ -6360,9 +6589,9 @@ packages: - changelist==0.5 ; extra == 'dev' - spin==0.15 ; extra == 'dev' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda - sha256: 565941ac1f8b0d2f2e8f02827cbca648f4d18cd461afc31f15604cd291b5c5f3 - md5: 12bd9a3f089ee6c9266a37dab82afabd +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c + md5: 18335a698559cdbcd86150a48bf54ba6 depends: - __glibc >=2.17,<3.0.a0 - zstd >=1.5.7,<1.6.0a0 @@ -6371,8 +6600,8 @@ packages: license: GPL-3.0-only license_family: GPL purls: [] - size: 725507 - timestamp: 1770267139900 + size: 728002 + timestamp: 1774197446916 - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda sha256: a7a4481a4d217a3eadea0ec489826a69070fcc3153f00443aa491ed21527d239 md5: 6f7b4302263347698fd24565fbf11310 @@ -6416,76 +6645,72 @@ packages: purls: [] size: 1229639 timestamp: 1770863511331 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda - build_number: 5 - sha256: 18c72545080b86739352482ba14ba2c4815e19e26a7417ca21a95b76ec8da24c - md5: c160954f7418d7b6e87eaf05a8913fa9 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda + build_number: 6 + sha256: 7bfe936dbb5db04820cf300a9cc1f5ee8d5302fc896c2d66e30f1ee2f20fbfd6 + md5: 6d6d225559bfa6e2f3c90ee9c03d4e2e depends: - - libopenblas >=0.3.30,<0.3.31.0a0 - - libopenblas >=0.3.30,<1.0a0 + - libopenblas >=0.3.32,<0.3.33.0a0 + - libopenblas >=0.3.32,<1.0a0 constrains: + - blas 2.306 openblas + - liblapack 3.11.0 6*_openblas + - liblapacke 3.11.0 6*_openblas + - libcblas 3.11.0 6*_openblas - mkl <2026 - - liblapack 3.11.0 5*_openblas - - libcblas 3.11.0 5*_openblas - - blas 2.305 openblas - - liblapacke 3.11.0 5*_openblas license: BSD-3-Clause - license_family: BSD purls: [] - size: 18213 - timestamp: 1765818813880 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-5_he492b99_openblas.conda - build_number: 5 - sha256: 4754de83feafa6c0b41385f8dab1b13f13476232e16f524564a340871a9fc3bc - md5: 36d2e68a156692cbae776b75d6ca6eae + size: 18621 + timestamp: 1774503034895 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-6_he492b99_openblas.conda + build_number: 6 + sha256: 6865098475f3804208038d0c424edf926f4dc9eacaa568d14e29f59df53731fd + md5: 93e7fc07b395c9e1341d3944dcf2aced depends: - - libopenblas >=0.3.30,<0.3.31.0a0 - - libopenblas >=0.3.30,<1.0a0 + - libopenblas >=0.3.32,<0.3.33.0a0 + - libopenblas >=0.3.32,<1.0a0 constrains: - - liblapack 3.11.0 5*_openblas - - blas 2.305 openblas - - libcblas 3.11.0 5*_openblas + - libcblas 3.11.0 6*_openblas + - blas 2.306 openblas - mkl <2026 - - liblapacke 3.11.0 5*_openblas + - liblapacke 3.11.0 6*_openblas + - liblapack 3.11.0 6*_openblas license: BSD-3-Clause - license_family: BSD purls: [] - size: 18476 - timestamp: 1765819054657 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-5_h51639a9_openblas.conda - build_number: 5 - sha256: 620a6278f194dcabc7962277da6835b1e968e46ad0c8e757736255f5ddbfca8d - md5: bcc025e2bbaf8a92982d20863fe1fb69 + size: 18724 + timestamp: 1774503646078 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda + build_number: 6 + sha256: 979227fc03628925037ab2dfda008eb7b5592644d9c2c21dd285cefe8c42553d + md5: e551103471911260488a02155cef9c94 depends: - - libopenblas >=0.3.30,<0.3.31.0a0 - - libopenblas >=0.3.30,<1.0a0 + - libopenblas >=0.3.32,<0.3.33.0a0 + - libopenblas >=0.3.32,<1.0a0 constrains: - - libcblas 3.11.0 5*_openblas - - liblapack 3.11.0 5*_openblas - - liblapacke 3.11.0 5*_openblas - - blas 2.305 openblas + - liblapacke 3.11.0 6*_openblas + - liblapack 3.11.0 6*_openblas + - blas 2.306 openblas + - libcblas 3.11.0 6*_openblas - mkl <2026 license: BSD-3-Clause - license_family: BSD purls: [] - size: 18546 - timestamp: 1765819094137 -- conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda - build_number: 5 - sha256: f0cb7b2697461a306341f7ff32d5b361bb84f3e94478464c1e27ee01fc8f276b - md5: f9decf88743af85c9c9e05556a4c47c0 + size: 18859 + timestamp: 1774504387211 +- conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda + build_number: 6 + sha256: 10c8054f007adca8c780cd8bb9335fa5d990f0494b825158d3157983a25b1ea2 + md5: 95543eec964b4a4a7ca3c4c9be481aa1 depends: - - mkl >=2025.3.0,<2026.0a0 + - mkl >=2025.3.1,<2026.0a0 constrains: - - liblapack 3.11.0 5*_mkl - - libcblas 3.11.0 5*_mkl - - blas 2.305 mkl - - liblapacke 3.11.0 5*_mkl + - blas 2.306 mkl + - liblapacke 3.11.0 6*_mkl + - liblapack 3.11.0 6*_mkl + - libcblas 3.11.0 6*_mkl license: BSD-3-Clause - license_family: BSD purls: [] - size: 67438 - timestamp: 1765819100043 + size: 68082 + timestamp: 1774503684284 - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda sha256: 318f36bd49ca8ad85e6478bd8506c88d82454cc008c1ac1c6bf00a3c42fa610e md5: 72c8fd1af66bd67bf580645b426513ed @@ -6585,86 +6810,82 @@ packages: purls: [] size: 290754 timestamp: 1764018009077 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda - build_number: 5 - sha256: 0cbdcc67901e02dc17f1d19e1f9170610bd828100dc207de4d5b6b8ad1ae7ad8 - md5: 6636a2b6f1a87572df2970d3ebc87cc0 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda + build_number: 6 + sha256: 57edafa7796f6fa3ebbd5367692dd4c7f552be42109c2dd1a7c89b55089bf374 + md5: 36ae340a916635b97ac8a0655ace2a35 depends: - - libblas 3.11.0 5_h4a7cf45_openblas + - libblas 3.11.0 6_h4a7cf45_openblas constrains: - - liblapacke 3.11.0 5*_openblas - - blas 2.305 openblas - - liblapack 3.11.0 5*_openblas + - blas 2.306 openblas + - liblapack 3.11.0 6*_openblas + - liblapacke 3.11.0 6*_openblas license: BSD-3-Clause - license_family: BSD purls: [] - size: 18194 - timestamp: 1765818837135 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-5_h9b27e0a_openblas.conda - build_number: 5 - sha256: 8077c29ea720bd152be6e6859a3765228cde51301fe62a3b3f505b377c2cb48c - md5: b31d771cbccff686e01a687708a7ca41 + size: 18622 + timestamp: 1774503050205 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-6_h9b27e0a_openblas.conda + build_number: 6 + sha256: 8422e1ce083e015bdb44addd25c9a8fe99aa9b0edbd9b7f1239b7ac1e3d04f77 + md5: 2a174868cb9e136c4e92b3ffc2815f04 depends: - - libblas 3.11.0 5_he492b99_openblas + - libblas 3.11.0 6_he492b99_openblas constrains: - - liblapack 3.11.0 5*_openblas - - blas 2.305 openblas - - liblapacke 3.11.0 5*_openblas + - liblapacke 3.11.0 6*_openblas + - blas 2.306 openblas + - liblapack 3.11.0 6*_openblas license: BSD-3-Clause - license_family: BSD purls: [] - size: 18484 - timestamp: 1765819073006 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-5_hb0561ab_openblas.conda - build_number: 5 - sha256: 38809c361bbd165ecf83f7f05fae9b791e1baa11e4447367f38ae1327f402fc0 - md5: efd8bd15ca56e9d01748a3beab8404eb + size: 18713 + timestamp: 1774503667477 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda + build_number: 6 + sha256: 2e6b3e9b1ab672133b70fc6730e42290e952793f132cb5e72eee22835463eba0 + md5: 805c6d31c5621fd75e53dfcf21fb243a depends: - - libblas 3.11.0 5_h51639a9_openblas + - libblas 3.11.0 6_h51639a9_openblas constrains: - - liblapacke 3.11.0 5*_openblas - - liblapack 3.11.0 5*_openblas - - blas 2.305 openblas + - liblapacke 3.11.0 6*_openblas + - blas 2.306 openblas + - liblapack 3.11.0 6*_openblas license: BSD-3-Clause - license_family: BSD purls: [] - size: 18548 - timestamp: 1765819108956 -- conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda - build_number: 5 - sha256: 49dc59d8e58360920314b8d276dd80da7866a1484a9abae4ee2760bc68f3e68d - md5: b3fa8e8b55310ba8ef0060103afb02b5 + size: 18863 + timestamp: 1774504433388 +- conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda + build_number: 6 + sha256: 02b2a2225f4899c6aaa1dc723e06b3f7a4903d2129988f91fc1527409b07b0a5 + md5: 9e4bf521c07f4d423cba9296b7927e3c depends: - - libblas 3.11.0 5_hf2e6a31_mkl + - libblas 3.11.0 6_hf2e6a31_mkl constrains: - - liblapack 3.11.0 5*_mkl - - liblapacke 3.11.0 5*_mkl - - blas 2.305 mkl + - blas 2.306 mkl + - liblapacke 3.11.0 6*_mkl + - liblapack 3.11.0 6*_mkl license: BSD-3-Clause - license_family: BSD purls: [] - size: 68079 - timestamp: 1765819124349 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.0-h19cb2f5_1.conda - sha256: fa002b43752fe5860e588435525195324fe250287105ebd472ac138e97de45e6 - md5: 836389b6b9ae58f3fbcf7cafebd5c7f2 + size: 68221 + timestamp: 1774503722413 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.2-h19cb2f5_0.conda + sha256: 46561199545890e050a8a90edcfce984e5f881da86b09388926e3a6c6b759dec + md5: ed6f7b7a35f942a0301e581d72616f7d depends: - __osx >=11.0 license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 570141 - timestamp: 1772001147762 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.0-h55c6f16_1.conda - sha256: ce1049fa6fda9cf08ff1c50fb39573b5b0ea6958375d8ea7ccd8456ab81a0bcb - md5: e9c56daea841013e7774b5cd46f41564 + size: 564908 + timestamp: 1774439353713 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.2-h55c6f16_0.conda + sha256: d1402087c8792461bfc081629e8aa97e6e577a31ae0b84e6b9cc144a18f48067 + md5: 4280e0a7fd613b271e022e60dea0138c depends: - __osx >=11.0 license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 568910 - timestamp: 1772001095642 + size: 568094 + timestamp: 1774439202359 - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda sha256: 1cd6048169fa0395af74ed5d8f1716e22c19a81a8a36f934c110ca3ad4dd27b4 md5: 172bf1cd1ff8629f2b1179945ed45055 @@ -7034,55 +7255,55 @@ packages: purls: [] size: 89411 timestamp: 1769482314283 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - sha256: a4a7dab8db4dc81c736e9a9b42bdfd97b087816e029e221380511960ac46c690 - md5: b499ce4b026493a13774bcf0f4c33849 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda + sha256: 663444d77a42f2265f54fb8b48c5450bfff4388d9c0f8253dd7855f0d993153f + md5: 2a45e7f8af083626f009645a6481f12d depends: - __glibc >=2.17,<3.0.a0 - - c-ares >=1.34.5,<2.0a0 + - c-ares >=1.34.6,<2.0a0 - libev >=4.33,<4.34.0a0 - libev >=4.33,<5.0a0 - libgcc >=14 - libstdcxx >=14 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.5,<4.0a0 license: MIT license_family: MIT purls: [] - size: 666600 - timestamp: 1756834976695 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - sha256: c48d7e1cc927aef83ff9c48ae34dd1d7495c6ccc1edc4a3a6ba6aff1624be9ac - md5: e7630cef881b1174d40f3e69a883e55f + size: 663344 + timestamp: 1773854035739 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.68.1-h70048d4_0.conda + sha256: 899551e16aac9dfb85bfc2fd98b655f4d1b7fea45720ec04ccb93d95b4d24798 + md5: dba4c95e2fe24adcae4b77ebf33559ae depends: - - __osx >=10.13 - - c-ares >=1.34.5,<2.0a0 + - __osx >=11.0 + - c-ares >=1.34.6,<2.0a0 - libcxx >=19 - libev >=4.33,<4.34.0a0 - libev >=4.33,<5.0a0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.5,<4.0a0 license: MIT license_family: MIT purls: [] - size: 605680 - timestamp: 1756835898134 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - sha256: a07cb53b5ffa2d5a18afc6fd5a526a5a53dd9523fbc022148bd2f9395697c46d - md5: a4b4dd73c67df470d091312ab87bf6ae + size: 606749 + timestamp: 1773854765508 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + sha256: 2bc7bc3978066f2c274ebcbf711850cc9ab92e023e433b9631958a098d11e10a + md5: 6ea18834adbc3b33df9bd9fb45eaf95b depends: - __osx >=11.0 - - c-ares >=1.34.5,<2.0a0 + - c-ares >=1.34.6,<2.0a0 - libcxx >=19 - libev >=4.33,<4.34.0a0 - libev >=4.33,<5.0a0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.5,<4.0a0 license: MIT license_family: MIT purls: [] - size: 575454 - timestamp: 1756835746393 + size: 576526 + timestamp: 1773854624224 - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5 md5: d864d34357c3b65a4b731f78c0801dc4 @@ -7094,51 +7315,48 @@ packages: purls: [] size: 33731 timestamp: 1750274110928 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda - sha256: 199d79c237afb0d4780ccd2fbf829cea80743df60df4705202558675e07dd2c5 - md5: be43915efc66345cccb3c310b6ed0374 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + sha256: 6dc30b28f32737a1c52dada10c8f3a41bc9e021854215efca04a7f00487d09d9 + md5: 89d61bc91d3f39fda0ca10fcd3c68594 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - libgfortran - libgfortran5 >=14.3.0 constrains: - - openblas >=0.3.30,<0.3.31.0a0 + - openblas >=0.3.32,<0.3.33.0a0 license: BSD-3-Clause - license_family: BSD purls: [] - size: 5927939 - timestamp: 1763114673331 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h6006d49_4.conda - sha256: ba642353f7f41ab2d2eb6410fbe522238f0f4483bcd07df30b3222b4454ee7cd - md5: 9241a65e6e9605e4581a2a8005d7f789 + size: 5928890 + timestamp: 1774471724897 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.32-openmp_h9e49c7b_0.conda + sha256: 6764229359cd927c9efc036930ba28f83436b8d6759c5ac4ea9242fc29a7184e + md5: 4058c5f8dbef6d28cb069f96b95ae6df depends: - - __osx >=10.13 + - __osx >=11.0 - libgfortran - libgfortran5 >=14.3.0 - llvm-openmp >=19.1.7 constrains: - - openblas >=0.3.30,<0.3.31.0a0 + - openblas >=0.3.32,<0.3.33.0a0 license: BSD-3-Clause - license_family: BSD purls: [] - size: 6268795 - timestamp: 1763117623665 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_4.conda - sha256: ebbbc089b70bcde87c4121a083c724330f02a690fb9d7c6cd18c30f1b12504fa - md5: a6f6d3a31bb29e48d37ce65de54e2df0 + size: 6289730 + timestamp: 1774474444702 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda + sha256: 713e453bde3531c22a660577e59bf91ef578dcdfd5edb1253a399fa23514949a + md5: 3a1111a4b6626abebe8b978bb5a323bf depends: - __osx >=11.0 - libgfortran - libgfortran5 >=14.3.0 - llvm-openmp >=19.1.7 constrains: - - openblas >=0.3.30,<0.3.31.0a0 + - openblas >=0.3.32,<0.3.33.0a0 license: BSD-3-Clause - license_family: BSD purls: [] - size: 4284132 - timestamp: 1768547079205 + size: 4308797 + timestamp: 1774472508546 - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda sha256: d716847b7deca293d2e49ed1c8ab9e4b9e04b9d780aea49a97c26925b28a7993 md5: fd893f6a3002a635b5e50ceb9dd2c0f4 @@ -7295,98 +7513,97 @@ packages: purls: [] size: 520078 timestamp: 1772704728534 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 - md5: edb0dca6bc32e4f4789199455a1dbeb8 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + sha256: 55044c403570f0dc26e6364de4dc5368e5f3fc7ff103e867c487e2b5ab2bcda9 + md5: d87ff7921124eccd67248aa483c23fec depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 constrains: - - zlib 1.3.1 *_2 + - zlib 1.3.2 *_2 license: Zlib license_family: Other purls: [] - size: 60963 - timestamp: 1727963148474 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - sha256: 8412f96504fc5993a63edf1e211d042a1fd5b1d51dedec755d2058948fcced09 - md5: 003a54a4e32b02f7355b50a837e699da + size: 63629 + timestamp: 1774072609062 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_2.conda + sha256: 4c6da089952b2d70150c74234679d6f7ac04f4a98f9432dec724968f912691e7 + md5: 30439ff30578e504ee5e0b390afc8c65 depends: - - __osx >=10.13 + - __osx >=11.0 constrains: - - zlib 1.3.1 *_2 + - zlib 1.3.2 *_2 license: Zlib license_family: Other purls: [] - size: 57133 - timestamp: 1727963183990 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - sha256: ce34669eadaba351cd54910743e6a2261b67009624dbc7daeeafdef93616711b - md5: 369964e85dc26bfe78f41399b366c435 + size: 59000 + timestamp: 1774073052242 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + sha256: 361415a698514b19a852f5d1123c5da746d4642139904156ddfca7c922d23a05 + md5: bc5a5721b6439f2f62a84f2548136082 depends: - __osx >=11.0 constrains: - - zlib 1.3.1 *_2 + - zlib 1.3.2 *_2 license: Zlib license_family: Other purls: [] - size: 46438 - timestamp: 1727963202283 -- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - sha256: ba945c6493449bed0e6e29883c4943817f7c79cbff52b83360f7b341277c6402 - md5: 41fbfac52c601159df6c01f875de31b9 + size: 47759 + timestamp: 1774072956767 +- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + sha256: 88609816e0cc7452bac637aaf65783e5edf4fee8a9f8e22bdc3a75882c536061 + md5: dbabbd6234dea34040e631f87676292f depends: - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 constrains: - - zlib 1.3.1 *_2 + - zlib 1.3.2 *_2 license: Zlib license_family: Other purls: [] - size: 55476 - timestamp: 1727963768015 -- conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.0-h0d3cbff_0.conda - sha256: b63df4e592b3362e7d13e3d1cf8e55ce932ff4f17611c8514b5d36368ec2094c - md5: 3921780bab286f2439ba483c22b90345 + size: 58347 + timestamp: 1774072851498 +- conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.1-h0d3cbff_0.conda + sha256: 65c30298a921b3f6d49e2f4ec220bc2d2237cbdabd1c19031f4fafbfdad8aa5e + md5: d5e67fb9aeb3f32fc474ca7859a5583b depends: - __osx >=11.0 constrains: - - openmp 22.1.0|22.1.0.* - intel-openmp <0.0a0 + - openmp 22.1.1|22.1.1.* license: Apache-2.0 WITH LLVM-exception license_family: APACHE purls: [] - size: 311938 - timestamp: 1772024731611 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.0-hc7d1edf_0.conda - sha256: 0daeedb3872ad0fdd6f0d7e7165c63488e8a315d7057907434145fba0c1e7b3d - md5: ff0820b5588b20be3b858552ecf8ffae + size: 311088 + timestamp: 1774349643537 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.1-hc7d1edf_0.conda + sha256: c6f67e928f47603aca7e4b83632d8f3e82bd698051c7c0b34fcce3796eb9b63c + md5: 5a44f53783d87427790fc8692542f1bb depends: - __osx >=11.0 constrains: - - openmp 22.1.0|22.1.0.* - intel-openmp <0.0a0 + - openmp 22.1.1|22.1.1.* license: Apache-2.0 WITH LLVM-exception license_family: APACHE purls: [] - size: 285558 - timestamp: 1772028716784 -- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda - sha256: bb55a3736380759d338f87aac68df4fd7d845ae090b94400525f5d21a55eea31 - md5: e5505e0b7d6ef5c19d5c0c1884a2f494 + size: 285912 + timestamp: 1774349644882 +- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.1-h4fa8253_0.conda + sha256: 64c7fe6490583f3c49c36c2f413e681072102db8abea13a3e1832f44eaf55518 + md5: d9f479404fe316e575f4a4575f3df406 depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: - - openmp 22.1.0|22.1.0.* + - openmp 22.1.1|22.1.1.* - intel-openmp <0.0a0 license: Apache-2.0 WITH LLVM-exception license_family: APACHE purls: [] - size: 347404 - timestamp: 1772025050288 + size: 347138 + timestamp: 1774349485844 - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl name: lmfit version: 1.3.4 @@ -7785,22 +8002,22 @@ packages: - markupsafe>=2.0.1 - mkdocs>=1.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/ca/fa/ab291bbb8f7453f8449a982843b044b37050c85f5895ef97f599683a1249/mkdocs_get_deps-0.2.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl name: mkdocs-get-deps - version: 0.2.1 - sha256: 07d6076298715dfcb8232e7dec083d09015b4e65482ce7f6743cb07cd1da847e + version: 0.2.2 + sha256: e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650 requires_dist: - importlib-metadata>=4.3 ; python_full_version < '3.10' - mergedeep>=1.3.4 - platformdirs>=2.2.0 - pyyaml>=5.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl name: mkdocs-jupyter - version: 0.25.1 - sha256: 3f679a857609885d322880e72533ef5255561bbfdb13cfee2a1e92ef4d4ad8d8 + version: 0.26.1 + sha256: 527242c2c8f1d30970764bbab752de41243e5703f458d8bc05336ec53828192e requires_dist: - - ipykernel>6.0.0,<7.0.0 + - ipykernel>6.0.0,<8 - jupytext>1.13.8,<2 - mkdocs-material>9.0.0 - mkdocs>=1.4.0,<2 @@ -7815,10 +8032,10 @@ packages: - mkdocs - pyyaml requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/e7/94/e3535a9ed078b238df3df75a44694ca0ff5772fd538df4939c658a58c59d/mkdocs_material-9.7.4-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl name: mkdocs-material - version: 9.7.4 - sha256: 6549ad95e4d130ed5099759dfa76ea34c593eefdb9c18c97273605518e99cfbf + version: 9.7.6 + sha256: 71b84353921b8ea1ba84fe11c50912cc512da8fe0881038fcc9a0761c0e635ba requires_dist: - babel>=2.10 - backrefs>=5.7.post1 @@ -7826,7 +8043,7 @@ packages: - jinja2>=3.1 - markdown>=3.2 - mkdocs-material-extensions>=1.3 - - mkdocs>=1.6 + - mkdocs>=1.6,<2 - paginate>=0.5 - pygments>=2.16 - pymdown-extensions>=10.2 @@ -7876,11 +8093,11 @@ packages: - griffelib>=2.0 - typing-extensions>=4.0 ; python_full_version < '3.11' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda - sha256: b2b4c84b95210760e4d12319416c60ab66e03674ccdcbd14aeb59f82ebb1318d - md5: fd05d1e894497b012d05a804232254ed +- conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_11.conda + sha256: f2c2b2a3c2e7d08d78c10bef7c135a4262c80d1d48c85fb5902ca30d61d645f4 + md5: 3fd3009cef89c36e9898a6feeb0f5530 depends: - - llvm-openmp >=21.1.8 + - llvm-openmp >=22.1.1 - tbb >=2022.3.0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 @@ -7888,8 +8105,8 @@ packages: license: LicenseRef-IntelSimplifiedSoftwareOct2022 license_family: Proprietary purls: [] - size: 100224829 - timestamp: 1767634557029 + size: 99997309 + timestamp: 1774449747739 - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl name: mpld3 version: 0.5.12 @@ -8087,10 +8304,10 @@ packages: requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl name: narwhals - version: 2.17.0 - sha256: 2ac5307b7c2b275a7d66eeda906b8605e3d7a760951e188dcfff86e8ebe083dd + version: 2.18.1 + sha256: a0a8bb80205323851338888ba3a12b4f65d352362c8a94be591244faf36504ad requires_dist: - cudf-cu12>=24.10.0 ; extra == 'cudf' - dask[dataframe]>=2024.8 ; extra == 'dask' @@ -8394,87 +8611,83 @@ packages: version: 1.10.0 sha256: 5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*' -- conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.7.0-he4ff34a_0.conda - sha256: 4791285a34195615e22a94dc62cbd43181548b34eb34e6cb1dcf8f469476a32e - md5: ba562095149fde99c700365d90e6d632 +- conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.8.2-he4ff34a_0.conda + sha256: d1a673d1418d9e956b6e4e46c23e72a511c5c1d45dc5519c947457427036d5e2 + md5: baffb1570b3918c784d4490babc52fbf depends: - - __glibc >=2.28,<3.0.a0 - - libstdcxx >=14 - libgcc >=14 - - libnghttp2 >=1.67.0,<2.0a0 + - libstdcxx >=14 + - __glibc >=2.28,<3.0.a0 + - libnghttp2 >=1.68.1,<2.0a0 + - libuv >=1.51.0,<2.0a0 - c-ares >=1.34.6,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - libsqlite >=3.52.0,<4.0a0 + - icu >=78.3,<79.0a0 + - libzlib >=1.3.2,<2.0a0 + - libabseil >=20260107.1,<20260108.0a0 + - libabseil * cxx17* - zstd >=1.5.7,<1.6.0a0 - libbrotlicommon >=1.2.0,<1.3.0a0 - libbrotlienc >=1.2.0,<1.3.0a0 - libbrotlidec >=1.2.0,<1.3.0a0 - - libuv >=1.51.0,<2.0a0 - - openssl >=3.5.5,<4.0a0 - - libabseil >=20260107.1,<20260108.0a0 - - libabseil * cxx17* - - icu >=78.2,<79.0a0 - - libzlib >=1.3.1,<2.0a0 - - libsqlite >=3.51.2,<4.0a0 license: MIT - license_family: MIT purls: [] - size: 18875002 - timestamp: 1772300115686 -- conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.7.0-hf6efa0e_0.conda - sha256: a23a3b536f1ccd3c10a2cbdf8c638b601c51f6e9bbf1e6c708610cf5b1df3d0f - md5: 448185eb18358e4b5fe2a157cc7e415f + size: 18829340 + timestamp: 1774514313036 +- conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-25.8.2-hf3170e9_0.conda + sha256: 6e82ed9c2de2e5a472a9c25f7a4a3a296d33aa38b94151acbbb5a28754962d8d + md5: de36be6257a15d17e85c96b47d290f82 depends: - libcxx >=19 - - __osx >=10.15 - - openssl >=3.5.5,<4.0a0 - - c-ares >=1.34.6,<2.0a0 - - libuv >=1.51.0,<2.0a0 + - __osx >=11.0 + - libzlib >=1.3.2,<2.0a0 + - libnghttp2 >=1.68.1,<2.0a0 + - libabseil >=20260107.1,<20260108.0a0 + - libabseil * cxx17* + - icu >=78.3,<79.0a0 - libbrotlicommon >=1.2.0,<1.3.0a0 - libbrotlienc >=1.2.0,<1.3.0a0 - libbrotlidec >=1.2.0,<1.3.0a0 - - libabseil >=20260107.1,<20260108.0a0 - - libabseil * cxx17* - - libnghttp2 >=1.67.0,<2.0a0 - - icu >=78.2,<79.0a0 - - libsqlite >=3.51.2,<4.0a0 - - libzlib >=1.3.1,<2.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libuv >=1.51.0,<2.0a0 + - c-ares >=1.34.6,<2.0a0 + - openssl >=3.5.5,<4.0a0 - zstd >=1.5.7,<1.6.0a0 license: MIT - license_family: MIT purls: [] - size: 18342335 - timestamp: 1772299132495 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.7.0-hbfc8e16_0.conda - sha256: 6398a45d2bb943585540d98a9b1b3bed1fb48fabd327b56eba2ece00e3dd202f - md5: 22a4a51f30590f274bb3f831d911c09a + size: 18382168 + timestamp: 1774517889949 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.8.2-h7039424_0.conda + sha256: 4782b172b3b8a557b60bf5f591821cf100e2092ba7a5494ce047dfa41626de26 + md5: ca8277c52fdface8bb8ebff7cd9a6f56 depends: - - __osx >=11.0 - libcxx >=19 - - libsqlite >=3.51.2,<4.0a0 - - zstd >=1.5.7,<1.6.0a0 + - __osx >=11.0 + - icu >=78.3,<79.0a0 - libbrotlicommon >=1.2.0,<1.3.0a0 - libbrotlienc >=1.2.0,<1.3.0a0 - libbrotlidec >=1.2.0,<1.3.0a0 + - libnghttp2 >=1.68.1,<2.0a0 - libuv >=1.51.0,<2.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + - c-ares >=1.34.6,<2.0a0 - libabseil >=20260107.1,<20260108.0a0 - libabseil * cxx17* - - icu >=78.2,<79.0a0 - - c-ares >=1.34.6,<2.0a0 - - openssl >=3.5.5,<4.0a0 - - libnghttp2 >=1.67.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 license: MIT - license_family: MIT purls: [] - size: 17351648 - timestamp: 1772299122966 -- conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.7.0-h80d1838_0.conda - sha256: 0ea0ddad32366396d1beda7ce93ddd3d9f705286c1a4f99f05ec0049183c1e97 - md5: 61b62d3be12c9edbe34f202d51891927 + size: 17101803 + timestamp: 1774517834028 +- conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda + sha256: 5e38e51da1aa4bc352db9b4cec1c3e25811de0f4408edaa24e009a64de6dbfdf + md5: e626ee7934e4b7cb21ce6b721cff8677 license: MIT - license_family: MIT purls: [] - size: 31254428 - timestamp: 1772299132945 + size: 31271315 + timestamp: 1774517904472 - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl name: notebook-shim version: 0.2.4 @@ -9614,11 +9827,6 @@ packages: - trove-classifiers>=2024.10.12 ; extra == 'tests' - defusedxml ; extra == 'xmp' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - name: pip - version: 26.0.1 - sha256: bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/c4/36/ce5f75aa7c736a663a901766edc3580098c7ea3959a0e878363a54a3714e/pixi_kernel-0.7.1-py3-none-any.whl name: pixi-kernel version: 0.7.1 @@ -9636,10 +9844,10 @@ packages: version: 4.9.4 sha256: 68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/ab/f2/11ebc9b4cc8aaa23423a082c738b27792b88ebe0a8a6590ca6993c09a258/plopp-26.3.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl name: plopp - version: 26.3.0 - sha256: 44e231f202e8e9bfba4556762e7a49ec4832c5c1df0c96a744f87e7d50fddc11 + version: 26.3.1 + sha256: 56531f2f71fa4f7f33c172312d2423d969deb9b9dd29b2524ad3ed7e33d220eb requires_dist: - lazy-loader>=0.4 - matplotlib>=3.8 @@ -9718,6 +9926,14 @@ packages: - pytest-benchmark ; extra == 'testing' - coverage ; extra == 'testing' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl + name: plumbum + version: 1.10.0 + sha256: 9583d737ac901c474d99d030e4d5eec4c4e6d2d7417b1cf49728cf3be34f6dc8 + requires_dist: + - pywin32 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' + - paramiko ; extra == 'ssh' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl name: ply version: '3.11' @@ -10156,6 +10372,16 @@ packages: requires_dist: - typing-extensions>=4.14.1 requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl + name: pydoclint + version: 0.8.3 + sha256: 5fc9b82d0d515afce0908cb70e8ff695a68b19042785c248c4f227ad66b4a164 + requires_dist: + - click>=8.1.0 + - docstring-parser-fork>=0.0.12 + - tomli>=2.0.1 ; python_full_version < '3.11' + - flake8>=4 ; extra == 'flake8' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl name: pygments version: 2.19.2 @@ -10205,10 +10431,10 @@ packages: - setuptools ; extra == 'dev' - xmlschema ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl name: pytest-cov - version: 7.0.0 - sha256: 3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861 + version: 7.1.0 + sha256: a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678 requires_dist: - coverage[toml]>=7.10.6 - pluggy>=1.2 @@ -10427,10 +10653,10 @@ packages: requires_dist: - six>=1.5 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' -- pypi: https://files.pythonhosted.org/packages/03/48/8bdfaec240edb1a79b79201eff38b737fc3c29ce59e2e71271bdd8bafdda/python_discovery-1.1.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl name: python-discovery - version: 1.1.2 - sha256: d18edd61b382d62f8bcd004a71ebaabc87df31dbefb30aeed59f4fc6afa005be + version: 1.2.1 + sha256: b6a957b24c1cd79252484d3566d1b49527581d46e789aaf43181005e56201502 requires_dist: - filelock>=3.15.4 - platformdirs>=4.3.6,<5 @@ -10529,6 +10755,14 @@ packages: - pytest-check-links ; extra == 'test' - numpy>=1.14 ; extra == 'test' requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl + name: pywin32 + version: '311' + sha256: 3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503 +- pypi: https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl + name: pywin32 + version: '311' + sha256: 718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d - pypi: https://files.pythonhosted.org/packages/79/c3/3e75075c7f71735f22b66fab0481f2c98e3a4d58cba55cb50ba29114bcf6/pywinpty-3.0.3-cp311-cp311-win_amd64.whl name: pywinpty version: 3.0.3 @@ -10628,6 +10862,13 @@ packages: requires_dist: - cffi ; implementation_name == 'pypy' requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl + name: questionary + version: 2.1.1 + sha256: a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59 + requires_dist: + - prompt-toolkit>=2.0,<4.0 + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl name: radon version: 6.0.1 @@ -10680,18 +10921,24 @@ packages: - rpds-py>=0.7.0 - typing-extensions>=4.4.0 ; python_full_version < '3.13' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl name: requests - version: 2.32.5 - sha256: 2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 + version: 2.33.0 + sha256: 3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b requires_dist: - charset-normalizer>=2,<4 - idna>=2.5,<4 - - urllib3>=1.21.1,<3 - - certifi>=2017.4.17 + - urllib3>=1.26,<3 + - certifi>=2023.5.7 - pysocks>=1.5.6,!=1.5.7 ; extra == 'socks' - - chardet>=3.0.2,<6 ; extra == 'use-chardet-on-py3' - requires_python: '>=3.9' + - chardet>=3.0.2,<8 ; extra == 'use-chardet-on-py3' + - pytest-httpbin==2.1.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-xdist ; extra == 'test' + - pysocks>=1.5.6,!=1.5.7 ; extra == 'test' + - pytest>=3 ; extra == 'test' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl name: returns version: 0.26.0 @@ -10771,25 +11018,25 @@ packages: version: 0.30.0 sha256: e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl name: ruff - version: 0.15.5 - sha256: 6edd3792d408ebcf61adabc01822da687579a1a023f297618ac27a5b51ef0080 + version: 0.15.8 + sha256: 04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl name: ruff - version: 0.15.5 - sha256: 821d41c5fa9e19117616c35eaa3f4b75046ec76c65e7ae20a333e9a8696bc7fe + version: 0.15.8 + sha256: 6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl name: ruff - version: 0.15.5 - sha256: 89f463f7c8205a9f8dea9d658d59eff49db05f88f89cc3047fb1a02d9f344010 + version: 0.15.8 + sha256: d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: ruff - version: 0.15.5 - sha256: c1cb7169f53c1ddb06e71a9aebd7e98fc0fea936b39afb36d8e86d36ecc2636a + version: 0.15.8 + sha256: 0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1 requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl name: sciline @@ -10808,10 +11055,10 @@ packages: - rich ; extra == 'test' - rich ; extra == 'progress' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/12/0d/3f98a936a30bff4a460b51b9f85c4d994f94249930b2d8bedeb8111a359e/scipp-25.12.0-cp311-cp311-macosx_11_0_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: scipp - version: 25.12.0 - sha256: 6391dc46739006e1e4eb7f2fcbcdbdd40f11a3cbae53e93b63b5ba32909bd792 + version: 26.3.1 + sha256: 993706e7c31f0317be2db5f528f9142ba67b2e52d7af174fcad195f702e1d6c7 requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -10834,10 +11081,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/18/5c/bc0ca94bff65fe0d206a369b54625f8ec7852dfd1d835174692026f34df9/scipp-25.12.0-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl name: scipp - version: 25.12.0 - sha256: 0285c91b202dea9aeb18f29d73ff135208a19b2068cd30e17ee81fc435b1943c + version: 26.3.1 + sha256: 03c6dbf8936a2ed62587c5abe8ab5266a5098834a0709321ce799bd1328eb3e6 requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -10860,10 +11107,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/4e/b6/ffe0bb67cec66cd450acff599bb07507bbf5ffda1a3a15dd2d8dbe7a6da7/scipp-25.12.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/60/54/5011adb56853caabfd90686c2e543d1e3c76a8ef2755809b7e12e3f3583b/scipp-26.3.1-cp311-cp311-macosx_14_0_arm64.whl name: scipp - version: 25.12.0 - sha256: 9ec0200eeb660965056b27f5f05505a961d8921b78d0a92c04743d1c22ce7203 + version: 26.3.1 + sha256: 67d275fc83b062053df9aa7ce3af4d2205109c2bc3ab22467bcd73ceb0a83df2 requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -10886,10 +11133,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/6a/3a/ab0eb61593569d5a0d080002e4b8c0998cb1116d8710781b7225c304b880/scipp-25.12.0-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl name: scipp - version: 25.12.0 - sha256: e27082f5bd3655f15479ce87be495235b9bcd9b5db654a7219261be0c6117b31 + version: 26.3.1 + sha256: 8dfe8adedb5cba05acaaea15e3b6fe1820ac2f497e87c1e581ba4be9d82c53bb requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -10912,10 +11159,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/94/98/aa2d4b9d28969cc7f62409f9f9fc5b5a853af651255eba03e9bee8546dd9/scipp-25.12.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/81/21/4962b1daddf0422e56c5ed4c41bea1ccb6d2a9ab72b795196835a20969c7/scipp-26.3.1-cp311-cp311-macosx_14_0_x86_64.whl name: scipp - version: 25.12.0 - sha256: e5694bef45299c4813ee2fe863fab600c3b0f5e13e2735072dbbb5cf1804d0e0 + version: 26.3.1 + sha256: 7c90e78fcba1d272df059fc01350c9e18f017aec26369b03def723a3702d763d requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -10938,10 +11185,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/bd/75/6a3786de6645ac2ccd94fbf83c59cc6b929bfa3a89cb62c8cb3be4de0606/scipp-25.12.0-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/d4/06/19ff1efd58b85906149ce83dfddce23252cea5bec7e0fa5f834336cfe836/scipp-26.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: scipp - version: 25.12.0 - sha256: aee5f585232e2a7a664f57bb9695164715271b74896704e7ee8a669dd7b06008 + version: 26.3.1 + sha256: ab5859a24b3150b588dd2c67e68b0c7f07c9444eae501f3b6326d6b4a34cbf10 requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -10964,10 +11211,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/e5/22/75e119e0a200914f88f121cd956e1eb7f72c8ace4b63171f52ba0334d142/scipp-25.12.0-cp313-cp313-macosx_11_0_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/e2/69/1dcb8e967f62759578938db5b29792b82ea8939a2d712e79491fa3e1cf0a/scipp-26.3.1-cp313-cp313-macosx_14_0_x86_64.whl name: scipp - version: 25.12.0 - sha256: 27ebc37f3b7e20c9dca9cf1a0ac53c4ffaea31c02dfc8ba2b5aa008a252cbcba + version: 26.3.1 + sha256: 4c9c8632ba24ce74bd98430a1376310fa5b3fcd2c3467a7e6a484ebb091e915f requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -10990,10 +11237,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/eb/d1/b3cd2733a96a36c54c36889b2cfdd0331c1e5b57fa1757485a22d0ec3142/scipp-25.12.0-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/e6/0d/8882a4c7a5ebe59a46b709e82411d9c730d67250d41a2e11bc4bcd4d431d/scipp-26.3.1-cp311-cp311-win_amd64.whl name: scipp - version: 25.12.0 - sha256: 015db5035749750cf026db56fe537af10f159dc3cd0f51f0ea9f4ecc3a7a5da8 + version: 26.3.1 + sha256: 37877cf07b4f54f224d5465c265d6a1e591d605d0c23dd350a4b48d95c26ab7b requires_dist: - numpy>=2 - pytest ; extra == 'test' @@ -11494,11 +11741,38 @@ packages: version: 1.17.0 sha256: 4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' +- pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + name: smmap + version: 5.0.3 + sha256: c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f + requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl name: soupsieve version: 2.8.3 sha256: ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95 requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + name: spdx-headers + version: 1.5.1 + sha256: 73bcb1ed087824b55ccaa497d03d8f0f0b0eaf30e5f0f7d5bbd29d2c4fe78fcf + requires_dist: + - chardet>=5.2.0 + - requests>=2.32.3 + - black>=23.0.0 ; extra == 'dev' + - build>=0.10.0 ; extra == 'dev' + - hatch>=1.9.0 ; extra == 'dev' + - isort>=5.12.0 ; extra == 'dev' + - mypy>=1.0.0 ; extra == 'dev' + - pre-commit>=4.3.0 ; extra == 'dev' + - pytest-cov>=4.0.0 ; extra == 'dev' + - pytest>=7.0.0 ; extra == 'dev' + - ruff>=0.5.0 ; extra == 'dev' + - twine>=4.0.0 ; extra == 'dev' + - types-requests>=2.31.0.6 ; extra == 'dev' + - pytest-cov>=4.0.0 ; extra == 'test' + - pytest-mock>=3.10.0 ; extra == 'test' + - pytest>=7.0.0 ; extra == 'test' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/16/56/a31e8d3c9e8d21100b83bbe1c1f3f7c94db317393a229e193461e5e6d2a4/spglib-2.6.0-cp313-cp313-win_amd64.whl name: spglib version: 2.6.0 @@ -12091,10 +12365,10 @@ packages: purls: [] size: 3526350 timestamp: 1769460339384 -- pypi: https://files.pythonhosted.org/packages/7c/b9/ac773f87400bce0637b5f87bb272d0b347c91d7063bff09fb7055b65dd2f/tof-26.1.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl name: tof - version: 26.1.0 - sha256: 2603a7b94a7296ee503a0edadb314701431808c8ededb5c442334f89633962a5 + version: 26.3.0 + sha256: e89783a072b05fdb53d9e76fbf919dc8935e75e118fdaf17ca5cc33727ef002b requires_dist: - plopp>=23.10.0 - pooch>=1.5.0 @@ -12108,70 +12382,70 @@ packages: version: 6.2.0 sha256: a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl name: tomli - version: 2.4.0 - sha256: d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa + version: 2.4.1 + sha256: 36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: tomli - version: 2.4.0 - sha256: 9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e + version: 2.4.1 + sha256: f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl name: tomli - version: 2.4.0 - sha256: 84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0 + version: 2.4.1 + sha256: eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl name: tomli - version: 2.4.0 - sha256: 3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87 + version: 2.4.1 + sha256: 5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: tomli - version: 2.4.0 - sha256: b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867 + version: 2.4.1 + sha256: 5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl name: tomli - version: 2.4.0 - sha256: 5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9 + version: 2.4.1 + sha256: 8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl name: tomli - version: 2.4.0 - sha256: 1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e + version: 2.4.1 + sha256: 4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl name: tomli - version: 2.4.0 - sha256: 5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76 + version: 2.4.1 + sha256: f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30 requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl name: toolz version: 1.1.0 sha256: 15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl name: tornado - version: 6.5.4 - sha256: e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f + version: 6.5.5 + sha256: 6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl +- pypi: https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl name: tornado - version: 6.5.4 - sha256: d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9 + version: 6.5.5 + sha256: 487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl name: tornado - version: 6.5.4 - sha256: fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc + version: 6.5.5 + sha256: 65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: tornado - version: 6.5.4 - sha256: 2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843 + version: 6.5.5 + sha256: e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5 requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl name: traitlets @@ -12269,10 +12543,6 @@ packages: - python-docs-theme ; extra == 'doc' - uncertainties[arrays,doc,test] ; extra == 'all' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - name: untokenize - version: 0.1.1 - sha256: 3865dbbbb8efb4bb5eaa72f1be7f3e0be00ea8b7f125c69cbd1f5fda926f37a2 - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl name: uri-template version: 1.3.0 @@ -12310,26 +12580,6 @@ packages: - pysocks>=1.5.6,!=1.5.7,<2.0 ; extra == 'socks' - backports-zstd>=1.0.0 ; python_full_version < '3.14' and extra == 'zstd' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/6f/34/2e5cd576d312eb1131b615f49ee95ff6efb740965324843617adae729cf2/uv-0.10.9-py3-none-macosx_10_12_x86_64.whl - name: uv - version: 0.10.9 - sha256: 880dd4cffe4bd184e8871ddf4c7d3c3b042e1f16d2682310644aa8d61eaea3e6 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/79/34/b104c413079874493eed7bf11838b47b697cf1f0ed7e9de374ea37b4e4e0/uv-0.10.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: uv - version: 0.10.9 - sha256: 7c9d6deb30edbc22123be75479f99fb476613eaf38a8034c0e98bba24a344179 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/89/35/684f641de4de2b20db7d2163c735b2bb211e3b3c84c241706d6448e5e868/uv-0.10.9-py3-none-macosx_11_0_arm64.whl - name: uv - version: 0.10.9 - sha256: a7a784254380552398a6baf4149faf5b31a4003275f685c28421cf8197178a08 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/c9/e9/adf7a12136573937d12ac189569e2e90e7fad18b458192083df6986f3013/uv-0.10.9-py3-none-win_amd64.whl - name: uv - version: 0.10.9 - sha256: af79552276d8bd622048ab2d67ec22120a6af64d83963c46b1482218c27b571f - requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl name: validate-pyproject version: '0.25' diff --git a/pixi.toml b/pixi.toml index c0a8608e..2cbf09c5 100644 --- a/pixi.toml +++ b/pixi.toml @@ -5,7 +5,7 @@ # Platform-independent [activation.env] -PYTHONIOENCODING = "utf-8" +PYTHONIOENCODING = 'utf-8' # Platform-specific @@ -27,6 +27,7 @@ PYTHONPATH = "${PIXI_PROJECT_ROOT}/src;%PYTHONPATH%" ########### [workspace] + # Supported platforms for the lock file (pixi.lock) platforms = ['win-64', 'linux-64', 'osx-64', 'osx-arm64'] @@ -34,7 +35,7 @@ platforms = ['win-64', 'linux-64', 'osx-64', 'osx-arm64'] channels = ['conda-forge'] ##################### -# System requirements +# SYSTEM REQUIREMENTS ##################### [system-requirements] @@ -52,34 +53,20 @@ macos = '14.0' # Default feature configuration [dependencies] -gsl = '*' # GNU Scientific Library; required for pdffit2. - -#[target.win-64.dependencies] -#libcblas = '*' # CBLAS library for linear algebra; required for pdffit2. +nodejs = '*' # Required for Prettier (non-Python formatting) +gsl = '*' # GNU Scientific Library; required for diffpy.pdffit2 [pypi-dependencies] # == [feature.default.pypi-dependencies] -pip = '*' # Native package installer -uv = '*' # Package manager -jupyterlab = '*' # Jupyter notebooks -pixi-kernel = '*' # Pixi Jupyter kernel -#easydiffraction = { version = '*', extras = ['all'] } # Main package -easydiffraction = { path = ".", editable = true, extras = ['all'] } - -# Specific features +#pip = '*' # Native package installer +easydiffraction = { path = '.', editable = true, extras = ['dev'] } -# Each feature sets a specific Python version for the environment. +# Specific features: Set specific Python versions -[feature.py311.dependencies] +[feature.py-min.dependencies] python = '3.11.*' - -[feature.py313.dependencies] +[feature.py-max.dependencies] python = '3.13.*' -# This feature installs Node.js for formatting non-Python files with Prettier. - -[feature.nodejs.dependencies] -nodejs = '*' - ############## # ENVIRONMENTS ############## @@ -88,13 +75,12 @@ nodejs = '*' # The `default` feature is always included in all environments. # Additional features can be specified per environment. +py-311-env = { features = ['default', 'py-min'] } +py-313-env = { features = ['default', 'py-max'] } # The `default` environment is always created and includes the `default` feature. # It does not need to be specified explicitly unless non-default features are included. - -default = { features = ['py313', 'nodejs'] } -py311-dev = { features = ['py311', 'nodejs'] } -py313-dev = { features = ['py313', 'nodejs'] } +default = { features = ['default', 'py-max'] } ####### # TASKS @@ -102,59 +88,59 @@ py313-dev = { features = ['py313', 'nodejs'] } [tasks] -## 🧪 Testing Tasks +################## +# 🧪 Testing Tasks +################## + unit-tests = 'python -m pytest tests/unit/ --color=yes -v' integration-tests = 'python -m pytest tests/integration/ --color=yes -n auto -v' -notebook-tests = 'python -m pytest --nbmake tutorials/ --nbmake-timeout=600 --color=yes -n auto -v' script-tests = 'python -m pytest tools/test_scripts.py --color=yes -n auto -v' -extra = 'python -m pytest tests/unit/extra.py -q --tb=no --disable-warnings --color=yes' +notebook-tests = 'python -m pytest --nbmake docs/docs/tutorials/ --nbmake-timeout=600 --color=yes -n auto -v' test = { depends-on = ['unit-tests'] } -# 🧹 Code Quality +########### +# ✔️ Checks +########### -### ✔️ Checks pyproject-check = 'python -m validate_pyproject pyproject.toml' -py-lint-check-pre = "python -m ruff check" -py-lint-check = 'pixi run py-lint-check-pre .' -py-format-check-pre = "python -m ruff format --check" -py-format-check = "pixi run py-format-check-pre ." -nonpy-format-check-pre = "npx prettier --list-different --config=prettierrc.toml" -nonpy-format-check-modified = "pixi run nonpy-format-check-pre $(git diff --diff-filter=d --name-only HEAD | grep -E '\\.(json|ya?ml|toml|md|css|html)$' || echo .)" -nonpy-format-check = "pixi run nonpy-format-check-pre ." -notebook-format-check = 'nbqa ruff tutorials/' -docs-format-check = 'docformatter src/ tutorials/ --check' -# Run like a real commit: staged files only (almost) -pre-commit-check = 'pre-commit run --hook-stage pre-commit' -# CI check: lint/format everything -pre-commit-check-all = 'pre-commit run --all-files --hook-stage pre-commit' -# Pre-push check: lint/format everything -pre-push-check = 'pre-commit run --all-files --hook-stage pre-push' - -check = { depends-on = [ - 'docs-format-check', - 'py-format-check', - 'py-lint-check', - 'nonpy-format-check-modified', -] } +param-docstring-check = 'python tools/param_consistency.py src/ --check' +docstring-lint-check = 'pydoclint --quiet src/' +notebook-lint-check = 'nbqa ruff docs/docs/tutorials/' +py-lint-check = 'ruff check src/ tests/ docs/docs/tutorials/' +py-format-check = 'ruff format --check src/ tests/ docs/docs/tutorials/' +nonpy-format-check = 'npx prettier --list-different --config=prettierrc.toml --ignore-unknown .' +nonpy-format-check-modified = 'python tools/nonpy_prettier_modified.py' + +check = 'pre-commit run --hook-stage manual --all-files' + +########## +# 🛠️ Fixes +########## -### 🛠️ Fixes -py-lint-fix = 'pixi run py-lint-check --fix' -#py-format-fix = "python -m ruff format $(git diff --cached --name-only -- '*.py')" -py-format-fix = "python -m ruff format" -nonpy-format-fix = 'pixi run nonpy-format-check --write' -nonpy-format-fix-modified = "pixi run nonpy-format-check-modified --write" -notebook-format-fix = 'pixi run notebook-format-check --fix' -docs-format-fix = 'docformatter src/ tutorials/ --in-place' +param-docstring-fix = 'python tools/param_consistency.py src/ --fix' +docstring-format-fix = 'format-docstring src/' +notebook-lint-fix = 'nbqa ruff --fix docs/docs/tutorials/' +py-lint-fix = 'ruff check --fix src/ tests/ docs/docs/tutorials/' +py-format-fix = 'ruff format src/ tests/ docs/docs/tutorials/' +nonpy-format-fix = 'npx prettier --write --list-different --config=prettierrc.toml --ignore-unknown .' +nonpy-format-fix-modified = 'python tools/nonpy_prettier_modified.py --write' +success-message = 'echo "✅ All auto-formatting steps completed successfully!"' fix = { depends-on = [ + #'param-docstring-fix', # ED only + 'docstring-format-fix', 'py-format-fix', - 'docs-format-fix', 'py-lint-fix', 'nonpy-format-fix', + 'notebook-lint-fix', + 'success-message', ] } -## 🧮 Code Complexity +#################### +# 🧮 Code Complexity +#################### + complexity-check = 'radon cc -s src/' complexity-check-json = 'radon cc -s -j src/' maintainability-check = 'radon mi src/' @@ -162,62 +148,127 @@ maintainability-check-json = 'radon mi -j src/' raw-metrics = 'radon raw -s src/' raw-metrics-json = 'radon raw -s -j src/' -## 📊 Coverage +############# +# 📊 Coverage +############# + unit-tests-coverage = 'pixi run unit-tests --cov=src/easydiffraction --cov-report=term-missing' integration-tests-coverage = 'pixi run integration-tests --cov=src/easydiffraction --cov-report=term-missing' -docstring-coverage = 'interrogate -c pyproject.toml src/' +docstring-coverage = 'interrogate -c pyproject.toml src/easydiffraction' -cov = { depends-on = ['docstring-coverage', 'integration-tests-coverage'] } +cov = { depends-on = [ + 'docstring-coverage', + 'unit-tests-coverage', + 'integration-tests-coverage', +] } -## 📓 Notebook Management -notebook-convert = 'jupytext tutorials/*.py --from py:percent --to ipynb' -notebook-strip = 'nbstripout tutorials/*.ipynb' +######################## +# 📓 Notebook Management +######################## + +notebook-convert = 'jupytext docs/docs/tutorials/*.py --from py:percent --to ipynb' +notebook-strip = 'nbstripout docs/docs/tutorials/*.ipynb' notebook-tweak = 'python tools/tweak_notebooks.py tutorials/' -notebook-clean = 'rm -f tutorials/*.ipynb' -notebook-exec = 'python -m pytest --nbmake tutorials/ --nbmake-timeout=600 --overwrite --color=yes -n auto -v' +notebook-exec = 'python -m pytest --nbmake docs/docs/tutorials/ --nbmake-timeout=600 --overwrite --color=yes -n auto -v' notebook-prepare = { depends-on = [ - 'notebook-convert', + #'notebook-convert', 'notebook-strip', - 'notebook-tweak', + #'notebook-tweak', +] } + +######################## +# 📚 Documentation Tasks +######################## + +docs-vars = "JUPYTER_PLATFORM_DIRS=1 PYTHONWARNINGS='ignore::RuntimeWarning'" +docs-pre = 'pixi run docs-vars python -m mkdocs' +docs-serve = 'pixi run docs-pre serve -f docs/mkdocs.yml' +docs-serve-dirty = 'pixi run docs-serve --dirty' +docs-build = 'pixi run docs-pre build -f docs/mkdocs.yml' +docs-build-local = 'pixi run docs-build --no-directory-urls' + +docs-deploy-pre = 'mike deploy -F docs/mkdocs.yml --push --branch gh-pages --update-aliases --alias-type redirect' +docs-set-default-pre = 'mike set-default -F docs/mkdocs.yml --push --branch gh-pages' + +docs-update-assets = 'python tools/update_docs_assets.py' + +############################## +# 📦 Template Management Tasks +############################## + +copier-copy = 'copier copy gh:easyscience/templates . --data-file ../diffraction/.copier-answers.yml --data template_type=lib' +copier-recopy = 'copier recopy --data-file ../diffraction/.copier-answers.yml --data template_type=lib' +copier-update = 'copier update --data-file ../diffraction/.copier-answers.yml --data template_type=lib' + +##################### +# 🪝 Pre-commit Hooks +##################### + +pre-commit-clean = 'pre-commit clean' +pre-commit-install = 'pre-commit install --hook-type pre-commit --hook-type pre-push --overwrite' +pre-commit-uninstall = 'pre-commit uninstall --hook-type pre-commit --hook-type pre-push' +pre-commit-setup = { depends-on = [ + 'pre-commit-clean', + 'pre-commit-uninstall', + 'pre-commit-install', ] } -## 📚 Documentation Tasks -docs-assets = 'tools/add_assets_to_docs.sh' -docs-notebooks = 'mv tutorials/*.* docs/tutorials/' # *.py, *.ipynb, index.json -docs-config = 'python tools/create_mkdocs_yml.py' -docs-deploy = 'mike deploy --push --branch gh-pages --update-aliases --alias-type redirect' -docs-set-default = 'mike set-default --push --branch gh-pages' -docs-serve = "JUPYTER_PLATFORM_DIRS=1 PYTHONWARNINGS='ignore::RuntimeWarning' python -m mkdocs serve --dirty" -docs-build = "JUPYTER_PLATFORM_DIRS=1 PYTHONWARNINGS='ignore::RuntimeWarning' python -m mkdocs build" -docs-local = "pixi run docs-build --no-directory-urls" -docs-clean = 'tools/cleanup_docs.sh' -docs-setup = { depends-on = [ - 'docs-config', - 'docs-assets', - 'notebook-prepare', - 'docs-notebooks', +################# +# 🐙️ GitHub Tasks +################# + +repo-wiki = 'gh api -X PATCH repos/easyscience/diffraction-lib -f has_wiki=false' +repo-discussions = 'gh api -X PATCH repos/easyscience/diffraction-lib -f has_discussions=true' +repo-description = "gh api -X PATCH repos/easyscience/diffraction-lib -f description='Diffraction data analysis'" +repo-homepage = "gh api -X PATCH repos/easyscience/diffraction-lib -f homepage='https://easyscience.github.io/diffraction-lib'" +repo-config = { depends-on = [ + 'repo-wiki', + 'repo-discussions', + 'repo-description', + 'repo-homepage', ] } -## 🚀 Development & Build Tasks +master-protection = 'gh api -X POST repos/easyscience/diffraction-lib/rulesets --input .github/configs/rulesets-master.json' +develop-protection = 'gh api -X POST repos/easyscience/diffraction-lib/rulesets --input .github/configs/rulesets-develop.json' +gh-pages-protection = 'gh api -X POST repos/easyscience/diffraction-lib/rulesets --input .github/configs/rulesets-gh-pages.json' +branch-protection = { depends-on = [ + 'master-protection', + 'develop-protection', + 'gh-pages-protection', +] } + +pages-deployment = 'gh api -X POST repos/easyscience/diffraction-lib/pages --input .github/configs/pages-deployment.json' + +github-labels = 'python tools/update_github_labels.py' + +######################### +# ⚖️ SPDX License Headers +######################### + +license-remove = 'python tools/license_headers.py remove src/ tests/ --exclude-from-pyproject-toml tool.ruff.exclude' +license-add = 'python tools/license_headers.py add src/ tests/ --exclude-from-pyproject-toml tool.ruff.exclude' +license-check = 'python tools/license_headers.py check src/ tests/ --exclude-from-pyproject-toml tool.ruff.exclude' + +#################################### +# 🚀 Other Development & Build Tasks +#################################### + +default-build = 'python -m build' dist-build = 'python -m build --wheel --outdir dist' -spdx-update = 'python tools/update_spdx.py' -#dev-install = 'uv pip install --requirements pyproject.toml --extra all' -#dev-install = "python -m uv pip install --force-reinstall --editable '.[all]'" + npm-config = 'npm config set registry https://registry.npmjs.org/' prettier-install = 'npm install --no-save --no-audit --no-fund prettier prettier-plugin-toml' -pre-commit-setup = 'pre-commit clean && pre-commit uninstall && pre-commit install --hook-type pre-commit --hook-type pre-push --overwrite' -pre-commit-update = 'pre-commit autoupdate' + clean-pycache = "find . -type d -name '__pycache__' -prune -exec rm -rf '{}' +" -dev = { depends-on = [ - #'dev-install', +post-install = { depends-on = [ 'npm-config', 'prettier-install', #'pre-commit-setup', ] } -wheel = { depends-on = ['npm-config', 'prettier-install'] } - -## 🔗 Main Package Shortcut +########################## +# 🔗 Main Package Shortcut +########################## easydiffraction = 'python -m easydiffraction' diff --git a/prettierrc.toml b/prettierrc.toml index 6874d28d..b98c86eb 100644 --- a/prettierrc.toml +++ b/prettierrc.toml @@ -1,11 +1,22 @@ +plugins = [ + "prettier-plugin-toml", # use the TOML plugin +] + endOfLine = 'lf' # change line endings to LF -printWidth = 80 # wrap Markdown files at 80 characters proseWrap = 'always' # change wrapping in Markdown files semi = false # remove semicolons singleQuote = true # use single quotes instead of double quotes tabWidth = 2 # change tab width to 2 spaces useTabs = false # use spaces instead of tabs -plugins = [ - 'prettier-plugin-toml', # use the TOML plugin -] +printWidth = 79 # wrap lines at 79 characters + +[[overrides]] +files = ["*.md"] +[overrides.options] +printWidth = 72 # wrap Markdown files at 72 characters + +[[overrides]] +files = ["*.yml", "*.yaml"] +[overrides.options] +printWidth = 88 # wrap YAML files at 88 characters diff --git a/pycrysfml.md b/pycrysfml.md index af681478..de9bc4d0 100644 --- a/pycrysfml.md +++ b/pycrysfml.md @@ -12,7 +12,8 @@ ```bash otool -L .venv/lib/python3.12/site-packages/pycrysfml/crysfml08lib.so ``` -- If the library is linked to the wrong Python version, you can fix it with: +- If the library is linked to the wrong Python version, you can fix it + with: ```bash install_name_tool -change `python3-config --prefix`/Python `python3-config --prefix`/lib/libpython3.12.dylib .venv/lib/python3.12/site-packages/pycrysfml/crysfml08lib.so ``` diff --git a/pyproject.toml b/pyproject.toml index dd645c71..16d18621 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,9 +6,10 @@ name = 'easydiffraction' dynamic = ['version'] # Use versioningit to manage the version description = 'Diffraction data analysis' -authors = [{ name = 'EasyDiffraction contributors' }] +authors = [{ name = 'EasyScience contributors' }] readme = 'README.md' -license = { file = 'LICENSE' } +license = 'BSD-3-Clause' +license-files = ['LICENSE'] classifiers = [ 'Intended Audience :: Science/Research', 'Topic :: Scientific/Engineering', @@ -20,7 +21,7 @@ classifiers = [ 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', ] -requires-python = '>=3.11,<3.14' +requires-python = '>=3.11' dependencies = [ 'essdiffraction', # ESS-specific diffraction library 'numpy', # Numerical computing library @@ -43,29 +44,36 @@ dependencies = [ 'diffpy.utils', # Utilities for PDF calculations 'uncertainties', # Propagation of uncertainties 'typeguard', # Runtime type checking + 'darkdetect', # Detecting dark mode (system-level) + 'pandas', # Displaying tables in Jupyter notebooks + 'plotly', # Interactive plots + 'py3Dmol', # Visualisation of crystal structures + 'jupyterlab', # Jupyter notebooks + 'pixi-kernel', # Pixi Jupyter kernel ] [project.optional-dependencies] dev = [ - 'build', # Building the package - 'pre-commit', # Pre-commit hooks - 'jinja2', # Templating - 'nbmake', # Building notebooks - 'nbstripout', # Strip output from notebooks - 'nbqa', # Linting and formatting notebooks - 'pytest', # Testing - 'pytest-cov', # Test coverage - 'pytest-xdist', # Enable parallel testing - 'ruff', # Linting and formatting code - 'radon', # Code complexity and maintainability - 'validate-pyproject[all]', # Validate pyproject.toml - 'versioningit', # Automatic versioning from git tags - 'jupytext', # Jupyter notebook text format support - 'jupyterquiz', # Quizzes in Jupyter notebooks - 'docformatter', # Code formatter for docstrings - 'interrogate', # Check for missing docstrings -] -docs = [ + 'GitPython', # Interact with Git repositories + 'build', # Building the package + 'pre-commit', # Pre-commit hooks + 'jinja2', # Templating + 'nbmake', # Building notebooks + 'nbstripout', # Strip output from notebooks + 'nbqa', # Linting and formatting notebooks + 'pytest', # Testing + 'pytest-cov', # Test coverage + 'pytest-xdist', # Enable parallel testing + 'ruff', # Linting and formatting code + 'radon', # Code complexity and maintainability + 'validate-pyproject[all]', # Validate pyproject.toml + 'versioningit', # Automatic versioning from git tags + 'jupytext', # Jupyter notebook text format support + 'jupyterquiz', # Quizzes in Jupyter notebooks + 'pydoclint', # Docstring linter + 'format-docstring', # Docstring formatter + 'interrogate', # Docstring coverage checker + 'copier', # Template management 'mike', # MkDocs: Versioned documentation support 'mkdocs', # Static site generator 'mkdocs-material', # Documentation framework on top of MkDocs @@ -75,35 +83,20 @@ docs = [ 'mkdocs-markdownextradata-plugin', # MkDocs: Markdown extra data support, such as global variables 'mkdocstrings-python', # MkDocs: Python docstring support 'pyyaml', # YAML parser -] -visualization = [ - 'darkdetect', # Detecting dark mode (system-level) - 'pandas', # Displaying tables in Jupyter notebooks - 'plotly', # Interactive plots - 'py3Dmol', # Visualisation of crystal structures -] -all = [ - 'easydiffraction[dev]', - 'easydiffraction[docs]', - 'easydiffraction[visualization]', + 'spdx-headers', # SPDX license header validation ] [project.urls] -homepage = 'https://easydiffraction.org' -documentation = 'https://docs.easydiffraction.org/lib' -source = 'https://github.com/easyscience/diffraction-lib' -tracker = 'https://github.com/easyscience/diffraction-lib/issues' +Homepage = 'https://easydiffraction.org' +Documentation = 'https://easyscience.github.io/diffraction-lib' +'Release Notes' = 'https://github.com/easyscience/diffraction-lib/releases' +'Source Code' = 'https://github.com/easyscience/diffraction-lib' +'Issue Tracker' = 'https://github.com/easyscience/diffraction-lib/issues' ############################ # Build system configuration ############################ -# Build system 'hatch' -- Python project manager -# https://hatch.pypa.io/ - -# Versioning system 'versioningit' -- Versioning from git tags -# https://versioningit.readthedocs.io/ - [build-system] build-backend = 'hatchling.build' requires = ['hatchling', 'versioningit'] @@ -112,6 +105,9 @@ requires = ['hatchling', 'versioningit'] # Configuration for hatchling ############################# +# 'hatch' -- Build system for Python +# https://hatch.pypa.io/ + [tool.hatch.build.targets.wheel] packages = ['src/easydiffraction'] @@ -125,6 +121,9 @@ source = 'versioningit' # Use versioningit to manage the version # Configuration for versioningit ################################ +# 'versioningit' -- Versioning from git tags +# https://versioningit.readthedocs.io/ + # Versioningit generates versions from git tags, so we don't need to # either specify them statically in pyproject.toml or save them in the # source code. Do not use {distance} in the version format, as it @@ -144,43 +143,45 @@ method = 'git' match = ['v*'] default-tag = 'v999.0.0' -################################ -# Configuration for docformatter -################################ - -# 'docformatter' -- Code formatter for docstrings -# https://docformatter.readthedocs.io/en/latest/ - -[tool.docformatter] -recursive = true -wrap-summaries = 72 -wrap-descriptions = 72 -close-quotes-on-newline = true - ################################ # Configuration for interrogate ################################ -# 'interrogate' -- Check for missing docstrings +# 'interrogate' -- Docstring coverage checker # https://interrogate.readthedocs.io/en/latest/ [tool.interrogate] -fail-under = 35 # Temporarily reduce to allow gradual improvement +fail-under = 35 # Minimum docstring coverage percentage to pass verbose = 1 -exclude = ["src/**/__init__.py"] +#exclude = ['src/**/__init__.py'] ####################################### # Configuration for coverage/pytest-cov ####################################### +# 'coverage' -- Code coverage measurement tool +# https://coverage.readthedocs.io/en/latest/ + [tool.coverage.run] -branch = true # Measure branch coverage as well -source = ['src/easydiffraction'] # Limit coverage to the source code directory +branch = true # Measure branch coverage as well +source = ['src'] # Limit coverage to the source code directory [tool.coverage.report] -show_missing = true # Show missing lines -skip_covered = true # Skip files with 100% coverage in the report -fail_under = 60 # Temporarily reduce to allow gradual improvement +show_missing = true # Show missing lines +skip_covered = false # Skip files with 100% coverage in the report +fail_under = 60 # Minimum coverage percentage to pass + +########################## +# Configuration for pytest +########################## + +# 'pytest' -- Testing framework +# https://docs.pytest.org/en/stable/ + +[tool.pytest.ini_options] +addopts = '--import-mode=importlib' +markers = ['fast: mark test as fast (should be run on every push)'] +testpaths = ['tests'] ######################## # Configuration for ruff @@ -190,107 +191,197 @@ fail_under = 60 # Temporarily reduce to allow gradual improvement # https://docs.astral.sh/ruff/rules/ [tool.ruff] -# Temporarily exclude some directories until we have improved the code quality there -exclude = ['tmp', 'tests/unit'] +exclude = [ + 'tmp', + # Vendored jupyter_dark_detect: keep as-is from upstream for easy updates + # https://github.com/OpenMined/jupyter-dark-detect/tree/main/jupyter_dark_detect + 'src/easydiffraction/utils/_vendored/jupyter_dark_detect', +] indent-width = 4 -line-length = 99 -# Enable new rules that are not yet stable, like DOC -preview = true +line-length = 99 # See also `max-line-length` in [tool.ruff.lint.pycodestyle] +preview = true # Enable new rules that are not yet stable, like DOC + +# Formatting options for Ruff + +[tool.ruff.format] +docstring-code-format = true # Whether to format code snippets in docstrings +docstring-code-line-length = 72 # Line length for code snippets in docstrings +indent-style = 'space' # PEP 8 recommends using spaces over tabs +line-ending = 'lf' # Line endings will be converted to \n +quote-style = 'single' # But double quotes in docstrings (PEP 8, PEP 257) + +# Linting rules to use with Ruff -# https://docs.astral.sh/ruff/rules/ [tool.ruff.lint] select = [ - 'ARG', # Argument-related issues (e.g., unused arguments) - 'B', # Bugbear-specific checks (e.g., likely bugs, bad patterns) - 'C', # Complexity-related issues (e.g., high McCabe complexity) - 'D', # Docstring formatting issues (old rules) - 'DOC', # Docstring formatting issues (new rules) - 'DTZ', # Datetime timezone issues (e.g., inconsistent timezone formats) - 'E', # General PEP 8 style errors - 'F', # Pyflakes-specific checks (e.g., unused variables, imports) - 'FLY', # Flynt-specific checks (e.g., enforcing f-strings) - 'G', # Type annotation issues (e.g., missing or incorrect type hints) - 'I', # Import sorting issues (e.g., unsorted imports) - 'ICN', # Import conventions (e.g., enforce aliasing like import numpy as np) - 'N', # Naming convention issues (e.g., variable names, function names) - 'NPY', # NumPy-specific checks (e.g., array operations, broadcasting) - 'PGH', # Misc text patterns checks - 'PTH', # Encourages using pathlib over os.path - 'S', # Security-related issues (e.g., use of insecure functions or libraries) - 'SIM', # Simplification issues (e.g., redundant code, unnecessary constructs) - 'TCH', # Type checking issues (e.g., incompatible types, missing type annotations) - 'TID252', # Enforces absolute imports over relative imports - 'W', # General PEP 8 warnings (e.g., lines too long, trailing whitespace) - #'ANN', # Missing or incorrect type annotations - #'COM', # Comment formatting issues - #'PERF', # Performance-related issues (e.g., inefficient code patterns) - #'PIE', # Potentially problematic idioms and errors - #'PL', # PyLint-specific checks (e.g., code smells, potential errors) - #'PT', # Pytest-related issues - #'RET', # Return statement issues (e.g., inconsistent returns) - #'RUF', # Ruff-specific checks (e.g., enforcing best practices) - #'SLF', # Self argument-related issues (e.g., missing or misused self) - #'T20', # Flake8-print-specific checks (e.g., print statements left in code) - #'TD', # Type definition issues (e.g., incorrect or missing type definitions) - #'TRY', # Tryceratops Try/Except-related issues (e.g., broad exceptions, empty except blocks) - #'UP', # Pyupgrade-specific checks + # Various rules + #'C90', # https://docs.astral.sh/ruff/rules/#mccabe-c90 + 'D', # https://docs.astral.sh/ruff/rules/#pydocstyle-d + 'F', # https://docs.astral.sh/ruff/rules/#pyflakes-f + 'FLY', # https://docs.astral.sh/ruff/rules/#flynt-fly + #'FURB', # https://docs.astral.sh/ruff/rules/#refurb-furb + 'I', # https://docs.astral.sh/ruff/rules/#isort-i + 'N', # https://docs.astral.sh/ruff/rules/#pep8-naming-n + 'NPY', # https://docs.astral.sh/ruff/rules/#numpy-specific-rules-npy + 'PGH', # https://docs.astral.sh/ruff/rules/#pygrep-hooks-pgh + #'PERF', # https://docs.astral.sh/ruff/rules/#perflint-perf + #'RUF', # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf + #'TRY', # https://docs.astral.sh/ruff/rules/#tryceratops-try + #'UP', # https://docs.astral.sh/ruff/rules/#pyupgrade-up + # pycodestyle (E, W) rules + 'E', # https://docs.astral.sh/ruff/rules/#error-e + 'W', # https://docs.astral.sh/ruff/rules/#warning-w + # Pylint (PL) rules + #'PLC', # https://docs.astral.sh/ruff/rules/#convention-plc + #'PLE', # https://docs.astral.sh/ruff/rules/#error-ple + #'PLR', # https://docs.astral.sh/ruff/rules/#refactor-plr + #'PLW', # https://docs.astral.sh/ruff/rules/#warning-plw + # flake8 rules + #'A', # https://docs.astral.sh/ruff/rules/#flake8-builtins-a + 'ANN', # https://docs.astral.sh/ruff/rules/#flake8-annotations-ann + 'ARG', # https://docs.astral.sh/ruff/rules/#flake8-unused-arguments-arg + #'ASYNC', # https://docs.astral.sh/ruff/rules/#flake8-async-async + 'B', # https://docs.astral.sh/ruff/rules/#flake8-bugbear-b + #'BLE', # https://docs.astral.sh/ruff/rules/#flake8-blind-except-ble + #'C4', # https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 + #'COM', # https://docs.astral.sh/ruff/rules/#flake8-commas-com + 'DTZ', # https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz + #'EM', # https://docs.astral.sh/ruff/rules/#flake8-errmsg-em + #'FA', # https://docs.astral.sh/ruff/rules/#flake8-future-annotations-fa + #'FBT', # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt + #'FIX', # https://docs.astral.sh/ruff/rules/#flake8-fixme-fix + 'G', # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g + 'ICN', # https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn + #'INP', # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp + #'ISC', # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc + #'LOG', # https://docs.astral.sh/ruff/rules/#flake8-logging-log + #'PIE', # https://docs.astral.sh/ruff/rules/#flake8-pie-pie + #'PT', # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt + 'PTH', # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth + #'PYI', # https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi + #'RET', # https://docs.astral.sh/ruff/rules/#flake8-return-ret + #'RSE', # https://docs.astral.sh/ruff/rules/#flake8-raise-rse + 'S', # https://docs.astral.sh/ruff/rules/#flake8-bandit-s + 'SIM', # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim + #'SLF', # https://docs.astral.sh/ruff/rules/#flake8-self-slf + #'SLOT', # https://docs.astral.sh/ruff/rules/#flake8-slots-slot + #'T20', # https://docs.astral.sh/ruff/rules/#flake8-print-t20 + #'TC', # https://docs.astral.sh/ruff/rules/#flake8-type-checking-tc + #'TD', # https://docs.astral.sh/ruff/rules/#flake8-todos-td + #'TID', # https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid ] -# Temporarily disable some docstring checks until we have improved the docstring coverage + +# Exceptions to the linting rules + +# Ignore specific rules globally ignore = [ - 'C408', # Ignore: Unnecessary `dict()` call - 'C416', # Ignore: Unnecessary list comprehension - 'D100', # Ignore: Missing docstring in public module - 'D101', # Ignore: Missing docstring in class - 'D102', # Ignore: Missing docstring in public method - 'D103', # Ignore: Missing docstring in public function - 'D104', # Ignore: Missing docstring in public package - 'D105', # Ignore: Missing docstring in magic method - 'D107', # Ignore: Missing docstring in __init__ - 'D205', # Ignore: 1 blank line required between summary and description - 'DOC201', # Ignore: `return` is not documented in docstring - 'DOC501', # Ignore: Raised exception `ValueError` missing from docstring - 'DOC502', # Ignore: Raised exception is not explicitly raised: `TypeError` - 'DTZ005', # Ignore: `datetime.datetime.now()` called without a `tz` argument + 'COM812', # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ + # The following is replaced by 'D'/[tool.ruff.lint.pydocstyle] and [tool.pydoclint] + 'DOC', # https://docs.astral.sh/ruff/rules/#pydoclint-doc + # Disable, as [tool.format_docstring] split one-line docstrings into the canonical multi-line layout + 'D200', # https://docs.astral.sh/ruff/rules/unnecessary-multiline-docstring/ + # Temporary: + 'D100', # https://docs.astral.sh/ruff/rules/undocumented-public-module/#undocumented-publi-module-d100 + 'D104', # https://docs.astral.sh/ruff/rules/undocumented-public-package/#undocumented-public-package-d104 + 'DTZ005', # https://docs.astral.sh/ruff/rules/call-datetime-now-without-tzinfo/#call-datetime-now-without-tzinfo-dtz005 ] -# Temporarily increase McCabe complexity limit to 19 to allow -# refactoring in smaller steps. -[tool.ruff.lint.mccabe] -max-complexity = 37 # 19 # default is 10 +# Ignore specific rules in certain files or directories +[tool.ruff.lint.per-file-ignores] +'*/__init__.py' = [ + 'F401', # re-exports are intentional in __init__.py +] +'tests/**' = [ + 'ANN', # https://docs.astral.sh/ruff/rules/#flake8-annotations-ann + 'D', # https://docs.astral.sh/ruff/rules/#pydocstyle-d + 'DOC', # https://docs.astral.sh/ruff/rules/#pydoclint-doc + 'INP001', # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ + 'S101', # https://docs.astral.sh/ruff/rules/assert/ + # Temporary: + 'ARG001', + 'ARG002', + 'ARG004', + 'ARG005', + 'B011', + 'B017', + 'B018', + 'E501', + 'E741', + 'F841', + 'I001', + 'N801', + 'N805', + 'N812', + 'PLC', + 'PLE', + 'PLR', + 'PLW', + 'SIM117', + 'W505', +] +'docs/**' = [ + 'INP001', # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ + 'T201', # https://docs.astral.sh/ruff/rules/print/ +] + +# Specific options for certain rules [tool.ruff.lint.flake8-tidy-imports] +# Disallow all relative imports ban-relative-imports = 'all' [tool.ruff.lint.isort] +# Forces all from imports to appear on their own line force-single-line = true -[tool.ruff.lint.per-file-ignores] -'*test_*.py' = ['S101'] # allow asserts in test files -'conftest.py' = ['S101'] # allow asserts in test files -'*/__init__.py' = ['F401'] # re-exports are intentional in __init__.py -# Vendored jupyter_dark_detect: keep as-is from upstream for easy updates -# https://github.com/OpenMined/jupyter-dark-detect/tree/main/jupyter_dark_detect -'src/easydiffraction/utils/_vendored/jupyter_dark_detect/*' = [ - 'S110', # try-except-pass - 'S112', # try-except-continue - 'S404', # subprocess module - 'S607', # partial executable path - 'TID252', # relative imports - 'W291', # trailing whitespace (in JS strings) - 'W505', # doc line too long - 'E501', # line too long (in JS strings) -] +[tool.ruff.lint.mccabe] +# Cyclomatic complexity threshold (default is 10) +max-complexity = 10 [tool.ruff.lint.pycodestyle] -max-line-length = 100 #99# https://peps.python.org/pep-0008/#maximum-line-length -max-doc-length = 72 # https://peps.python.org/pep-0008/#maximum-line-length +# PEP 8 line length guidance: +# https://peps.python.org/pep-0008/#maximum-line-length +# Use 99 characters as the project-wide maximum for regular code lines. +# Use 72 characters for docstrings. +max-line-length = 99 # See also `line-length` in [tool.ruff] +max-doc-length = 72 [tool.ruff.lint.pydocstyle] -convention = 'google' +convention = 'numpy' -[tool.ruff.format] -docstring-code-format = true # Whether to format code snippets in docstrings -docstring-code-line-length = 72 # Line length for code snippets in docstrings -indent-style = 'space' # PEP 8 recommends using spaces over tabs -line-ending = 'lf' # Line endings will be converted to \n -quote-style = 'single' # But double quotes in docstrings (PEP 8, PEP 257) +############################# +# Configuration for pydoclint +############################# + +# 'pydoclint' -- Docstring linter, a faster alternative to +# 'darglint' or 'darglint2'. +# https://pypi.org/project/pydoclint/ + +# This is a more advanced docstring linter compared to Ruff's built-in +# docstring check rules D or DOC. For example, among many other things, +# it can check that arguments in the docstring, which are used by MkDocs +# and IDEs to render parameter documentation, remain synchronized with +# the parameter declarations in the code (in function's signature). + +[tool.pydoclint] +#exclude = '\.' # Temporarily disable pydoclint until we are ready +exclude = 'src/easydiffraction/utils/_vendored/jupyter_dark_detect' # ED only +style = 'numpy' +check-style-mismatch = true +check-arg-defaults = true +allow-init-docstring = true + +#################################### +# Configuration for format-docstring +#################################### + +# 'format-docstring' -- Code formatter for docstrings +# https://github.com/jsh9/format-docstring + +[tool.format_docstring] +#exclude = '\.' # Temporarily disable format-docstring until we are ready +exclude = 'src/easydiffraction/utils/_vendored/jupyter_dark_detect' # ED only +docstring_style = 'numpy' +line_length = 72 +fix_rst_backticks = true +verbose = 'default' diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 907883e8..00000000 --- a/pytest.ini +++ /dev/null @@ -1,13 +0,0 @@ -[pytest] -addopts = --import-mode=importlib -markers = - fast: mark test as fast (should be run on every push) - integration: mark test as integration (slow; opt-in) -testpaths = - tests -filterwarnings = - ignore::DeprecationWarning:cryspy\. - ignore:.*scipy\.misc is deprecated.*:DeprecationWarning - # Suppress expected UserWarnings emitted during tutorial list fetching in tests - ignore:Falling back to latest release info\...:UserWarning:easydiffraction\.utils\.logging - ignore:'tutorials\.zip' not found in the release\.:UserWarning:easydiffraction\.utils\.logging diff --git a/src/easydiffraction/__init__.py b/src/easydiffraction/__init__.py index d15d6682..e2fb3c4e 100644 --- a/src/easydiffraction/__init__.py +++ b/src/easydiffraction/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory diff --git a/src/easydiffraction/__main__.py b/src/easydiffraction/__main__.py index c850b1fa..1a058dd6 100644 --- a/src/easydiffraction/__main__.py +++ b/src/easydiffraction/__main__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import sys @@ -25,7 +25,7 @@ def main( help='Show easydiffraction version and exit.', is_eager=True, ), -): +) -> None: """EasyDiffraction command-line interface.""" if version: ed.show_version() @@ -38,7 +38,7 @@ def main( @app.command('list-tutorials') -def list_tutorials(): +def list_tutorials() -> None: """List available tutorial notebooks.""" ed.list_tutorials() @@ -58,7 +58,7 @@ def download_tutorial( '-o', help='Overwrite existing file if present.', ), -): +) -> None: """Download a specific tutorial notebook by ID.""" ed.download_tutorial(id=id, destination=destination, overwrite=overwrite) @@ -77,7 +77,7 @@ def download_all_tutorials( '-o', help='Overwrite existing files if present.', ), -): +) -> None: """Download all available tutorial notebooks.""" ed.download_all_tutorials(destination=destination, overwrite=overwrite) diff --git a/src/easydiffraction/analysis/__init__.py b/src/easydiffraction/analysis/__init__.py index 429f2648..78150ea5 100644 --- a/src/easydiffraction/analysis/__init__.py +++ b/src/easydiffraction/analysis/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index bf403247..874017cb 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from typing import List @@ -27,31 +27,27 @@ class Analysis: - """High-level orchestration of analysis tasks for a Project. + """ + High-level orchestration of analysis tasks for a Project. This class wires calculators and minimizers, exposes a compact interface for parameters, constraints and results, and coordinates computations across the project's structures and experiments. Typical usage: - - Display or filter parameters to fit. - Select a calculator/minimizer implementation. - Calculate patterns and run single or joint fits. - - Attributes: - project: The parent Project object. - aliases: A registry of human-friendly aliases for parameters. - constraints: Symbolic constraints between parameters. - calculator: Active calculator used for computations. - fitter: Active fitter/minimizer driver. """ - def __init__(self, project) -> None: - """Create a new Analysis instance bound to a project. + def __init__(self, project: object) -> None: + """ + Create a new Analysis instance bound to a project. - Args: - project: The project that owns models and experiments. + Parameters + ---------- + project : object + The project that owns models and experiments. """ self.project = project self._aliases_type: str = AliasesFactory.default_tag() @@ -136,10 +132,13 @@ def aliases_type(self) -> str: @aliases_type.setter def aliases_type(self, new_type: str) -> None: - """Switch to a different aliases collection type. + """ + Switch to a different aliases collection type. - Args: - new_type: Aliases tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Aliases tag (e.g. ``'default'``). """ supported_tags = AliasesFactory.supported_tags() if new_type not in supported_tags: @@ -174,10 +173,13 @@ def constraints_type(self) -> str: @constraints_type.setter def constraints_type(self, new_type: str) -> None: - """Switch to a different constraints collection type. + """ + Switch to a different constraints collection type. - Args: - new_type: Constraints tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Constraints tag (e.g. ``'default'``). """ supported_tags = ConstraintsFactory.supported_tags() if new_type not in supported_tags: @@ -205,12 +207,17 @@ def _get_params_as_dataframe( self, params: List[Union[NumericDescriptor, Parameter]], ) -> pd.DataFrame: - """Convert a list of parameters to a DataFrame. + """ + Convert a list of parameters to a DataFrame. - Args: - params: List of DescriptorFloat or Parameter objects. + Parameters + ---------- + params : List[Union[NumericDescriptor, Parameter]] + List of DescriptorFloat or Parameter objects. - Returns: + Returns + ------- + pd.DataFrame A pandas DataFrame containing parameter information. """ records = [] @@ -246,9 +253,7 @@ def _get_params_as_dataframe( return df def show_all_params(self) -> None: - """Print a table with all parameters for structures and - experiments. - """ + """Print all parameters for structures and experiments.""" structures_params = self.project.structures.parameters experiments_params = self.project.experiments.parameters @@ -278,9 +283,7 @@ def show_all_params(self) -> None: tabler.render(filtered_df) def show_fittable_params(self) -> None: - """Print a table with parameters that can be included in - fitting. - """ + """Print all fittable parameters.""" structures_params = self.project.structures.fittable_parameters experiments_params = self.project.experiments.fittable_parameters @@ -312,9 +315,7 @@ def show_fittable_params(self) -> None: tabler.render(filtered_df) def show_free_params(self) -> None: - """Print a table with only currently-free (varying) - parameters. - """ + """Print only currently free (varying) parameters.""" structures_params = self.project.structures.free_parameters experiments_params = self.project.experiments.free_parameters free_params = structures_params + experiments_params @@ -345,7 +346,8 @@ def show_free_params(self) -> None: tabler.render(filtered_df) def how_to_access_parameters(self) -> None: - """Show Python access paths for all parameters. + """ + Show Python access paths for all parameters. The output explains how to reference specific parameters in code. @@ -409,7 +411,8 @@ def how_to_access_parameters(self) -> None: ) def show_parameter_cif_uids(self) -> None: - """Show CIF unique IDs for all parameters. + """ + Show CIF unique IDs for all parameters. The output explains which unique identifiers are used when creating CIF-based constraints. @@ -472,9 +475,7 @@ def show_current_minimizer(self) -> None: @staticmethod def show_available_minimizers() -> None: - """Print a table of available minimizer drivers on this - system. - """ + """Print available minimizer drivers on this system.""" MinimizerFactory.show_supported() @property @@ -484,10 +485,13 @@ def current_minimizer(self) -> Optional[str]: @current_minimizer.setter def current_minimizer(self, selection: str) -> None: - """Switch to a different minimizer implementation. + """ + Switch to a different minimizer implementation. - Args: - selection: Minimizer selection string, e.g. 'lmfit'. + Parameters + ---------- + selection : str + Minimizer selection string, e.g. 'lmfit'. """ self.fitter = Fitter(selection) console.paragraph('Current minimizer changed to') @@ -498,7 +502,7 @@ def current_minimizer(self, selection: str) -> None: # ------------------------------------------------------------------ @property - def fit_mode(self): + def fit_mode(self) -> object: """Fit-mode category item holding the active strategy.""" return self._fit_mode @@ -509,10 +513,13 @@ def fit_mode_type(self) -> str: @fit_mode_type.setter def fit_mode_type(self, new_type: str) -> None: - """Switch to a different fit-mode category type. + """ + Switch to a different fit-mode category type. - Args: - new_type: Fit-mode tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Fit-mode tag (e.g. ``'default'``). """ supported_tags = FitModeFactory.supported_tags() if new_type not in supported_tags: @@ -541,7 +548,7 @@ def show_current_fit_mode_type(self) -> None: # ------------------------------------------------------------------ @property - def joint_fit_experiments(self): + def joint_fit_experiments(self) -> object: """Per-experiment weight collection for joint fitting.""" return self._joint_fit_experiments @@ -573,10 +580,8 @@ def show_constraints(self) -> None: columns_data=rows, ) - def apply_constraints(self): - """Apply the currently defined constraints to the active - project. - """ + def apply_constraints(self) -> None: + """Apply currently defined constraints to the project.""" if not self.constraints._items: log.warning('No constraints defined.') return @@ -585,27 +590,27 @@ def apply_constraints(self): self.constraints_handler.set_constraints(self.constraints) self.constraints_handler.apply() - def fit(self): - """Execute fitting using the selected mode, calculator and - minimizer. + def fit(self) -> None: + """ + Execute fitting for all experiments. This method performs the optimization but does not display results automatically. Call :meth:`show_fit_results` after fitting to see a summary of the fit quality and parameter values. - In 'single' mode, fits each experiment independently. In - 'joint' mode, performs a simultaneous fit across experiments - with weights. + In 'single' mode, fits each experiment independently. In 'joint' + mode, performs a simultaneous fit across experiments with + weights. Sets :attr:`fit_results` on success, which can be accessed - programmatically - (e.g., ``analysis.fit_results.reduced_chi_square``). + programmatically (e.g., + ``analysis.fit_results.reduced_chi_square``). Example:: - project.analysis.fit() - project.analysis.show_fit_results() # Display results + project.analysis.fit() project.analysis.show_fit_results() # + Display results """ structures = self.project.structures if not structures: @@ -659,7 +664,8 @@ def fit(self): self.fit_results = self.fitter.results def show_fit_results(self) -> None: - """Display a summary of the fit results. + """ + Display a summary of the fit results. Renders the fit quality metrics (reduced χ², R-factors) and a table of fitted parameters with their starting values, final @@ -670,8 +676,7 @@ def show_fit_results(self) -> None: Example:: - project.analysis.fit() - project.analysis.show_fit_results() + project.analysis.fit() project.analysis.show_fit_results() """ if not hasattr(self, 'fit_results') or self.fit_results is None: log.warning('No fit results available. Run fit() first.') @@ -682,14 +687,17 @@ def show_fit_results(self) -> None: self.fitter._process_fit_results(structures, experiments) - def _update_categories(self, called_by_minimizer=False) -> None: - """Update all categories owned by Analysis. + def _update_categories(self, called_by_minimizer: bool = False) -> None: + """ + Update all categories owned by Analysis. This ensures aliases and constraints are up-to-date before serialization or after parameter changes. - Args: - called_by_minimizer: Whether this is called during fitting. + Parameters + ---------- + called_by_minimizer : bool, default=False + Whether this is called during fitting. """ # Apply constraints to sync dependent parameters if self.constraints._items: @@ -701,10 +709,13 @@ def _update_categories(self, called_by_minimizer=False) -> None: if hasattr(category, '_update'): category._update(called_by_minimizer=called_by_minimizer) - def as_cif(self): - """Serialize the analysis section to a CIF string. + def as_cif(self) -> str: + """ + Serialize the analysis section to a CIF string. - Returns: + Returns + ------- + str The analysis section represented as a CIF document string. """ from easydiffraction.io.cif.serialize import analysis_to_cif @@ -713,9 +724,7 @@ def as_cif(self): return analysis_to_cif(self) def show_as_cif(self) -> None: - """Render the analysis section as CIF in a formatted console - view. - """ + """Render the analysis section as CIF in console.""" cif_text: str = self.as_cif() paragraph_title: str = 'Analysis 🧮 info as cif' console.paragraph(paragraph_title) diff --git a/src/easydiffraction/analysis/calculators/__init__.py b/src/easydiffraction/analysis/calculators/__init__.py index 5874b1a4..38bd1aa0 100644 --- a/src/easydiffraction/analysis/calculators/__init__.py +++ b/src/easydiffraction/analysis/calculators/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.calculators.crysfml import CrysfmlCalculator diff --git a/src/easydiffraction/analysis/calculators/base.py b/src/easydiffraction/analysis/calculators/base.py index 5ebd3086..bd667ac8 100644 --- a/src/easydiffraction/analysis/calculators/base.py +++ b/src/easydiffraction/analysis/calculators/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from abc import ABC @@ -17,11 +17,13 @@ class CalculatorBase(ABC): @property @abstractmethod def name(self) -> str: + """Short identifier of the calculation engine.""" pass @property @abstractmethod def engine_imported(self) -> bool: + """True if the underlying calculation library is available.""" pass @abstractmethod @@ -31,9 +33,7 @@ def calculate_structure_factors( experiment: ExperimentBase, called_by_minimizer: bool, ) -> None: - """Calculate structure factors for a single structure and - experiment. - """ + """Calculate structure factors for one experiment.""" pass @abstractmethod @@ -43,16 +43,22 @@ def calculate_pattern( experiment: ExperimentBase, called_by_minimizer: bool, ) -> np.ndarray: - """Calculate the diffraction pattern for a single structure and - experiment. + """ + Calculate diffraction pattern for one structure-experiment pair. - Args: - structure: The structure object. - experiment: The experiment object. - called_by_minimizer: Whether the calculation is called by a - minimizer. + Parameters + ---------- + structure : Structures + The structure object. + experiment : ExperimentBase + The experiment object. + called_by_minimizer : bool + Whether the calculation is called by a minimizer. Default is + False. - Returns: + Returns + ------- + np.ndarray The calculated diffraction pattern as a NumPy array. """ pass diff --git a/src/easydiffraction/analysis/calculators/crysfml.py b/src/easydiffraction/analysis/calculators/crysfml.py index 5d47300e..34624e2b 100644 --- a/src/easydiffraction/analysis/calculators/crysfml.py +++ b/src/easydiffraction/analysis/calculators/crysfml.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from typing import Any @@ -41,6 +41,7 @@ class CrysfmlCalculator(CalculatorBase): @property def name(self) -> str: + """Short identifier of this calculator engine.""" return 'crysfml' def calculate_structure_factors( @@ -48,13 +49,20 @@ def calculate_structure_factors( structures: Structures, experiments: Experiments, ) -> None: - """Call Crysfml to calculate structure factors. - - Args: - structures: The structures to calculate structure - factors for. - experiments: The experiments associated with the sample - models. + """ + Call Crysfml to calculate structure factors. + + Parameters + ---------- + structures : Structures + The structures to calculate structure factors for. + experiments : Experiments + The experiments associated with the sample models. + + Raises + ------ + NotImplementedError + HKL calculation is not implemented for CrysfmlCalculator. """ raise NotImplementedError('HKL calculation is not implemented for CrysfmlCalculator.') @@ -64,18 +72,23 @@ def calculate_pattern( experiment: ExperimentBase, called_by_minimizer: bool = False, ) -> Union[np.ndarray, List[float]]: - """Calculates the diffraction pattern using Crysfml for the - given structure and experiment. - - Args: - structure: The structure to calculate the pattern for. - experiment: The experiment associated with the structure. - called_by_minimizer: Whether the calculation is called by a - minimizer. - - Returns: + """ + Calculate the diffraction pattern using Crysfml. + + Parameters + ---------- + structure : Structures + The structure to calculate the pattern for. + experiment : ExperimentBase + The experiment associated with the structure. + called_by_minimizer : bool, default=False + Whether the calculation is called by a minimizer. + + Returns + ------- + Union[np.ndarray, List[float]] The calculated diffraction pattern as a NumPy array or a - list of floats. + list of floats. """ # Intentionally unused, required by public API/signature del called_by_minimizer @@ -94,13 +107,19 @@ def _adjust_pattern_length( pattern: List[float], target_length: int, ) -> List[float]: - """Adjusts the length of the pattern to match the target length. - - Args: - pattern: The pattern to adjust. - target_length: The desired length of the pattern. - - Returns: + """ + Adjust the pattern length to match the target length. + + Parameters + ---------- + pattern : List[float] + The pattern to adjust. + target_length : int + The desired length of the pattern. + + Returns + ------- + List[float] The adjusted pattern. """ # TODO: Check the origin of this discrepancy coming from @@ -114,16 +133,20 @@ def _crysfml_dict( structure: Structures, experiment: ExperimentBase, ) -> Dict[str, Union[ExperimentBase, Structure]]: - """Converts the structure and experiment into a dictionary - format for Crysfml. - - Args: - structure: The structure to convert. - experiment: The experiment to convert. - - Returns: - A dictionary representation of the structure and - experiment. + """ + Convert structure and experiment into a Crysfml dictionary. + + Parameters + ---------- + structure : Structures + The structure to convert. + experiment : ExperimentBase + The experiment to convert. + + Returns + ------- + Dict[str, Union[ExperimentBase, Structure]] + A dictionary representation of the structure and experiment. """ structure_dict = self._convert_structure_to_dict(structure) experiment_dict = self._convert_experiment_to_dict(experiment) @@ -136,12 +159,17 @@ def _convert_structure_to_dict( self, structure: Structure, ) -> Dict[str, Any]: - """Converts a structure into a dictionary format. + """ + Convert a structure into a dictionary format. - Args: - structure: The structure to convert. + Parameters + ---------- + structure : Structure + The structure to convert. - Returns: + Returns + ------- + Dict[str, Any] A dictionary representation of the structure. """ structure_dict = { @@ -176,12 +204,17 @@ def _convert_experiment_to_dict( self, experiment: ExperimentBase, ) -> Dict[str, Any]: - """Converts an experiment into a dictionary format. + """ + Convert an experiment into a dictionary format. - Args: - experiment: The experiment to convert. + Parameters + ---------- + experiment : ExperimentBase + The experiment to convert. - Returns: + Returns + ------- + Dict[str, Any] A dictionary representation of the experiment. """ expt_type = getattr(experiment, 'type', None) diff --git a/src/easydiffraction/analysis/calculators/cryspy.py b/src/easydiffraction/analysis/calculators/cryspy.py index ad09dc2a..b6a0e4d6 100644 --- a/src/easydiffraction/analysis/calculators/cryspy.py +++ b/src/easydiffraction/analysis/calculators/cryspy.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import contextlib @@ -35,7 +35,8 @@ @CalculatorFactory.register class CryspyCalculator(CalculatorBase): - """Cryspy-based diffraction calculator. + """ + Cryspy-based diffraction calculator. Converts EasyDiffraction models into Cryspy objects and computes patterns. @@ -49,6 +50,7 @@ class CryspyCalculator(CalculatorBase): @property def name(self) -> str: + """Short identifier of this calculator engine.""" return 'cryspy' def __init__(self) -> None: @@ -60,17 +62,18 @@ def calculate_structure_factors( structure: Structure, experiment: ExperimentBase, called_by_minimizer: bool = False, - ): - """Raises a NotImplementedError as HKL calculation is not - implemented. - - Args: - structure: The structure to calculate structure - factors for. - experiment: The experiment associated with the sample - models. - called_by_minimizer: Whether the calculation is called by a - minimizer. + ) -> None: + """ + Raise NotImplementedError as HKL calculation is not implemented. + + Parameters + ---------- + structure : Structure + The structure to calculate structure factors for. + experiment : ExperimentBase + The experiment associated with the sample models. + called_by_minimizer : bool, default=False + Whether the calculation is called by a minimizer. """ combined_name = f'{structure.name}_{experiment.name}' @@ -119,24 +122,28 @@ def calculate_pattern( experiment: ExperimentBase, called_by_minimizer: bool = False, ) -> Union[np.ndarray, List[float]]: - """Calculates the diffraction pattern using Cryspy for the given - structure and experiment. - - We only recreate the cryspy_obj if this method is - - NOT called by the minimizer, or - - the cryspy_dict is NOT yet created. - In other cases, we are modifying the existing cryspy_dict - This allows significantly speeding up the calculation - - Args: - structure: The structure to calculate the pattern for. - experiment: The experiment associated with the structure. - called_by_minimizer: Whether the calculation is called by a - minimizer. - - Returns: + """ + Calculate the diffraction pattern using Cryspy. + + We only recreate the cryspy_obj if this method is - NOT called + by the minimizer, or - the cryspy_dict is NOT yet created. In + other cases, we are modifying the existing cryspy_dict This + allows significantly speeding up the calculation + + Parameters + ---------- + structure : Structure + The structure to calculate the pattern for. + experiment : ExperimentBase + The experiment associated with the structure. + called_by_minimizer : bool, default=False + Whether the calculation is called by a minimizer. + + Returns + ------- + Union[np.ndarray, List[float]] The calculated diffraction pattern as a NumPy array or a - list of floats. + list of floats. """ combined_name = f'{structure.name}_{experiment.name}' @@ -194,14 +201,19 @@ def _recreate_cryspy_dict( structure: Structure, experiment: ExperimentBase, ) -> Dict[str, Any]: - """Recreates the Cryspy dictionary for the given structure and - experiment. - - Args: - structure: The structure to update. - experiment: The experiment to update. - - Returns: + """ + Recreate the Cryspy dictionary for structure and experiment. + + Parameters + ---------- + structure : Structure + The structure to update. + experiment : ExperimentBase + The experiment to update. + + Returns + ------- + Dict[str, Any] The updated Cryspy dictionary. """ combined_name = f'{structure.name}_{experiment.name}' @@ -307,15 +319,20 @@ def _recreate_cryspy_obj( self, structure: Structure, experiment: ExperimentBase, - ) -> Any: - """Recreates the Cryspy object for the given structure and - experiment. - - Args: - structure: The structure to recreate. - experiment: The experiment to recreate. - - Returns: + ) -> object: + """ + Recreate the Cryspy object for structure and experiment. + + Parameters + ---------- + structure : Structure + The structure to recreate. + experiment : ExperimentBase + The experiment to recreate. + + Returns + ------- + object The recreated Cryspy object. """ cryspy_obj = str_to_globaln('') @@ -339,12 +356,17 @@ def _convert_structure_to_cryspy_cif( self, structure: Structure, ) -> str: - """Converts a structure to a Cryspy CIF string. + """ + Convert a structure to a Cryspy CIF string. - Args: - structure: The structure to convert. + Parameters + ---------- + structure : Structure + The structure to convert. - Returns: + Returns + ------- + str The Cryspy CIF string representation of the structure. """ return structure.as_cif @@ -352,16 +374,21 @@ def _convert_structure_to_cryspy_cif( def _convert_experiment_to_cryspy_cif( self, experiment: ExperimentBase, - linked_structure: Any, + linked_structure: object, ) -> str: - """Converts an experiment to a Cryspy CIF string. - - Args: - experiment: The experiment to convert. - linked_structure: The structure linked to the - experiment. - - Returns: + """ + Convert an experiment to a Cryspy CIF string. + + Parameters + ---------- + experiment : ExperimentBase + The experiment to convert. + linked_structure : object + The structure linked to the experiment. + + Returns + ------- + str The Cryspy CIF string representation of the experiment. """ # Try to get experiment attributes diff --git a/src/easydiffraction/analysis/calculators/factory.py b/src/easydiffraction/analysis/calculators/factory.py index fa3812da..f9860f81 100644 --- a/src/easydiffraction/analysis/calculators/factory.py +++ b/src/easydiffraction/analysis/calculators/factory.py @@ -1,9 +1,10 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Calculator factory — delegates to ``FactoryBase``. +""" +Calculator factory — delegates to ``FactoryBase``. -Overrides ``_supported_map`` to filter out calculators whose engines -are not importable in the current environment. +Overrides ``_supported_map`` to filter out calculators whose engines are +not importable in the current environment. """ from __future__ import annotations @@ -17,7 +18,8 @@ class CalculatorFactory(FactoryBase): - """Factory for creating calculation engine instances. + """ + Factory for creating calculation engine instances. Only calculators whose ``engine_imported`` flag is ``True`` are available for creation. diff --git a/src/easydiffraction/analysis/calculators/pdffit.py b/src/easydiffraction/analysis/calculators/pdffit.py index debd90de..4ce20276 100644 --- a/src/easydiffraction/analysis/calculators/pdffit.py +++ b/src/easydiffraction/analysis/calculators/pdffit.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""PDF calculation backend using diffpy.pdffit2 if available. +""" +PDF calculation backend using diffpy.pdffit2 if available. The class adapts the engine to EasyDiffraction calculator interface and silences stdio on import to avoid noisy output in notebooks and logs. @@ -38,6 +39,9 @@ # print("⚠️ 'pdffit' module not found. This calculation engine will # not be available.") PdfFit = None + redirect_stdout = None + pdffit_cif_parser = None + _pdffit_devnull = None @CalculatorFactory.register @@ -51,10 +55,30 @@ class PdffitCalculator(CalculatorBase): engine_imported: bool = PdfFit is not None @property - def name(self): + def name(self) -> str: + """Short identifier of this calculator engine.""" return 'pdffit' - def calculate_structure_factors(self, structures, experiments): + def calculate_structure_factors( + self, + structures: object, + experiments: object, + ) -> list: + """ + Return an empty list; PDF does not compute structure factors. + + Parameters + ---------- + structures : object + Unused; kept for interface consistency. + experiments : object + Unused; kept for interface consistency. + + Returns + ------- + list + An empty list. + """ # PDF doesn't compute HKL but we keep interface consistent # Intentionally unused, required by public API/signature del structures, experiments @@ -66,7 +90,21 @@ def calculate_pattern( structure: Structure, experiment: ExperimentBase, called_by_minimizer: bool = False, - ): + ) -> None: + """ + Calculate the PDF pattern using PDFfit2. + + Parameters + ---------- + structure : Structure + The structure object supplying atom sites and cell + parameters. + experiment : ExperimentBase + The experiment object supplying instrument and peak + parameters. + called_by_minimizer : bool, default=False + Unused; kept for interface consistency. + """ # Intentionally unused, required by public API/signature del called_by_minimizer diff --git a/src/easydiffraction/analysis/categories/__init__.py b/src/easydiffraction/analysis/categories/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/analysis/categories/__init__.py +++ b/src/easydiffraction/analysis/categories/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/analysis/categories/aliases/__init__.py b/src/easydiffraction/analysis/categories/aliases/__init__.py index aa72de6b..6ca3a859 100644 --- a/src/easydiffraction/analysis/categories/aliases/__init__.py +++ b/src/easydiffraction/analysis/categories/aliases/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.categories.aliases.default import Alias diff --git a/src/easydiffraction/analysis/categories/aliases/default.py b/src/easydiffraction/analysis/categories/aliases/default.py index 49b263f4..63cc5f28 100644 --- a/src/easydiffraction/analysis/categories/aliases/default.py +++ b/src/easydiffraction/analysis/categories/aliases/default.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Alias category for mapping friendly names to parameter UIDs. +""" +Alias category for mapping friendly names to parameter UIDs. Defines a small record type used by analysis configuration to refer to parameters via readable labels instead of raw unique identifiers. @@ -19,15 +20,11 @@ class Alias(CategoryItem): - """Single alias entry. + """ + Single alias entry. Maps a human-readable ``label`` to a concrete ``param_uid`` used by the engine. - - Args: - label: Alias label. Must match ``^[A-Za-z_][A-Za-z0-9_]*$``. - param_uid: Target parameter uid. Same identifier pattern as - ``label``. """ def __init__(self) -> None: @@ -60,19 +57,33 @@ def __init__(self) -> None: # ------------------------------------------------------------------ @property - def label(self): + def label(self) -> StringDescriptor: + """ + ... + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._label @label.setter - def label(self, value): + def label(self, value: str) -> None: self._label.value = value @property - def param_uid(self): + def param_uid(self) -> StringDescriptor: + """ + ... + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._param_uid @param_uid.setter - def param_uid(self, value): + def param_uid(self, value: str) -> None: self._param_uid.value = value @@ -85,6 +96,6 @@ class Aliases(CategoryCollection): description='Parameter alias mappings', ) - def __init__(self): + def __init__(self) -> None: """Create an empty collection of aliases.""" super().__init__(item_type=Alias) diff --git a/src/easydiffraction/analysis/categories/aliases/factory.py b/src/easydiffraction/analysis/categories/aliases/factory.py index 07e1fe38..f2bebe43 100644 --- a/src/easydiffraction/analysis/categories/aliases/factory.py +++ b/src/easydiffraction/analysis/categories/aliases/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Aliases factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/analysis/categories/constraints/__init__.py b/src/easydiffraction/analysis/categories/constraints/__init__.py index ded70ca6..97d8c03c 100644 --- a/src/easydiffraction/analysis/categories/constraints/__init__.py +++ b/src/easydiffraction/analysis/categories/constraints/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.categories.constraints.default import Constraint diff --git a/src/easydiffraction/analysis/categories/constraints/default.py b/src/easydiffraction/analysis/categories/constraints/default.py index 647dd273..71d61d95 100644 --- a/src/easydiffraction/analysis/categories/constraints/default.py +++ b/src/easydiffraction/analysis/categories/constraints/default.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Simple symbolic constraint between parameters. +""" +Simple symbolic constraint between parameters. Represents an equation of the form ``lhs_alias = rhs_expr`` where ``rhs_expr`` is evaluated elsewhere by the analysis engine. @@ -20,12 +21,7 @@ class Constraint(CategoryItem): - """Single constraint item. - - Args: - lhs_alias: Left-hand side alias name being constrained. - rhs_expr: Right-hand side expression as a string. - """ + """Single constraint item.""" def __init__(self) -> None: super().__init__() @@ -57,19 +53,33 @@ def __init__(self) -> None: # ------------------------------------------------------------------ @property - def lhs_alias(self): + def lhs_alias(self) -> StringDescriptor: + """ + Left-hand side of the equation. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._lhs_alias @lhs_alias.setter - def lhs_alias(self, value): + def lhs_alias(self, value: str) -> None: self._lhs_alias.value = value @property - def rhs_expr(self): + def rhs_expr(self) -> StringDescriptor: + """ + Right-hand side expression. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._rhs_expr @rhs_expr.setter - def rhs_expr(self, value): + def rhs_expr(self, value: str) -> None: self._rhs_expr.value = value @@ -84,11 +94,11 @@ class Constraints(CategoryCollection): _update_priority = 90 # After most others, but before data categories - def __init__(self): + def __init__(self) -> None: """Create an empty constraints collection.""" super().__init__(item_type=Constraint) - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: del called_by_minimizer constraints = ConstraintsHandler.get() diff --git a/src/easydiffraction/analysis/categories/constraints/factory.py b/src/easydiffraction/analysis/categories/constraints/factory.py index 829260f4..682c9684 100644 --- a/src/easydiffraction/analysis/categories/constraints/factory.py +++ b/src/easydiffraction/analysis/categories/constraints/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Constraints factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/analysis/categories/fit_mode/__init__.py b/src/easydiffraction/analysis/categories/fit_mode/__init__.py index 45267810..058d054c 100644 --- a/src/easydiffraction/analysis/categories/fit_mode/__init__.py +++ b/src/easydiffraction/analysis/categories/fit_mode/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.categories.fit_mode.enums import FitModeEnum diff --git a/src/easydiffraction/analysis/categories/fit_mode/enums.py b/src/easydiffraction/analysis/categories/fit_mode/enums.py index 156e9c30..5e904eae 100644 --- a/src/easydiffraction/analysis/categories/fit_mode/enums.py +++ b/src/easydiffraction/analysis/categories/fit_mode/enums.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Enumeration for fit-mode values.""" @@ -15,9 +15,11 @@ class FitModeEnum(str, Enum): @classmethod def default(cls) -> FitModeEnum: + """Return the default fit mode (SINGLE).""" return cls.SINGLE def description(self) -> str: + """Return a human-readable description of this fit mode.""" if self is FitModeEnum.SINGLE: return 'Independent fitting of each experiment; no shared parameters' elif self is FitModeEnum.JOINT: diff --git a/src/easydiffraction/analysis/categories/fit_mode/factory.py b/src/easydiffraction/analysis/categories/fit_mode/factory.py index 48edef66..f10485f8 100644 --- a/src/easydiffraction/analysis/categories/fit_mode/factory.py +++ b/src/easydiffraction/analysis/categories/fit_mode/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Fit-mode factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/analysis/categories/fit_mode/fit_mode.py b/src/easydiffraction/analysis/categories/fit_mode/fit_mode.py index 8a5bd24b..b0589a2e 100644 --- a/src/easydiffraction/analysis/categories/fit_mode/fit_mode.py +++ b/src/easydiffraction/analysis/categories/fit_mode/fit_mode.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Fit-mode category item. +""" +Fit-mode category item. Stores the active fitting strategy as a CIF-serializable descriptor validated by ``FitModeEnum``. @@ -20,7 +21,8 @@ @FitModeFactory.register class FitMode(CategoryItem): - """Fitting strategy selector. + """ + Fitting strategy selector. Holds a single ``mode`` descriptor whose value is one of ``FitModeEnum`` members (``'single'`` or ``'joint'``). @@ -47,15 +49,16 @@ def __init__(self) -> None: self._identity.category_code = 'fit_mode' @property - def mode(self): - """Active fitting strategy descriptor.""" + def mode(self) -> StringDescriptor: + """ + Fitting strategy. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._mode @mode.setter def mode(self, value: str) -> None: - """Set the fitting strategy value. - - Args: - value: ``'single'`` or ``'joint'``. - """ self._mode.value = value diff --git a/src/easydiffraction/analysis/categories/joint_fit_experiments/__init__.py b/src/easydiffraction/analysis/categories/joint_fit_experiments/__init__.py index 2857b28d..1aa8f0ae 100644 --- a/src/easydiffraction/analysis/categories/joint_fit_experiments/__init__.py +++ b/src/easydiffraction/analysis/categories/joint_fit_experiments/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.categories.joint_fit_experiments.default import JointFitExperiment diff --git a/src/easydiffraction/analysis/categories/joint_fit_experiments/default.py b/src/easydiffraction/analysis/categories/joint_fit_experiments/default.py index 6acf4f44..b94bd7de 100644 --- a/src/easydiffraction/analysis/categories/joint_fit_experiments/default.py +++ b/src/easydiffraction/analysis/categories/joint_fit_experiments/default.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Joint-fit experiment weighting configuration. +""" +Joint-fit experiment weighting configuration. Stores per-experiment weights to be used when multiple experiments are fitted simultaneously. @@ -23,12 +24,7 @@ class JointFitExperiment(CategoryItem): - """A single joint-fit entry. - - Args: - id: Experiment identifier used in the fit session. - weight: Relative weight factor in the combined objective. - """ + """A single joint-fit entry.""" def __init__(self) -> None: super().__init__() @@ -60,19 +56,33 @@ def __init__(self) -> None: # ------------------------------------------------------------------ @property - def id(self): + def id(self) -> StringDescriptor: + """ + Experiment identifier. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._id @id.setter - def id(self, value): + def id(self, value: str) -> None: self._id.value = value @property - def weight(self): + def weight(self) -> NumericDescriptor: + """ + Weight factor. + + Reading this property returns the underlying + ``NumericDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._weight @weight.setter - def weight(self, value): + def weight(self, value: float) -> None: self._weight.value = value @@ -85,6 +95,6 @@ class JointFitExperiments(CategoryCollection): description='Joint-fit experiment weights', ) - def __init__(self): + def __init__(self) -> None: """Create an empty joint-fit experiments collection.""" super().__init__(item_type=JointFitExperiment) diff --git a/src/easydiffraction/analysis/categories/joint_fit_experiments/factory.py b/src/easydiffraction/analysis/categories/joint_fit_experiments/factory.py index 2919c741..57666098 100644 --- a/src/easydiffraction/analysis/categories/joint_fit_experiments/factory.py +++ b/src/easydiffraction/analysis/categories/joint_fit_experiments/factory.py @@ -1,8 +1,6 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Joint-fit-experiments factory — delegates entirely to -``FactoryBase``. -""" +"""Joint-fit-experiments factory — delegates to ``FactoryBase``.""" from __future__ import annotations diff --git a/src/easydiffraction/analysis/fit_helpers/__init__.py b/src/easydiffraction/analysis/fit_helpers/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/analysis/fit_helpers/__init__.py +++ b/src/easydiffraction/analysis/fit_helpers/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/analysis/fit_helpers/metrics.py b/src/easydiffraction/analysis/fit_helpers/metrics.py index 97c715d8..dee08f86 100644 --- a/src/easydiffraction/analysis/fit_helpers/metrics.py +++ b/src/easydiffraction/analysis/fit_helpers/metrics.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from typing import Optional @@ -14,14 +14,19 @@ def calculate_r_factor( y_obs: np.ndarray, y_calc: np.ndarray, ) -> float: - """Calculate the R-factor (reliability factor) between observed and - calculated data. - - Args: - y_obs: Observed data points. - y_calc: Calculated data points. - - Returns: + """ + Calculate the R-factor between observed and calculated data. + + Parameters + ---------- + y_obs : np.ndarray + Observed data points. + y_calc : np.ndarray + Calculated data points. + + Returns + ------- + float R-factor value. """ y_obs = np.asarray(y_obs) @@ -36,15 +41,21 @@ def calculate_weighted_r_factor( y_calc: np.ndarray, weights: np.ndarray, ) -> float: - """Calculate the weighted R-factor between observed and calculated - data. - - Args: - y_obs: Observed data points. - y_calc: Calculated data points. - weights: Weights for each data point. - - Returns: + """ + Calculate weighted R-factor between observed and calculated data. + + Parameters + ---------- + y_obs : np.ndarray + Observed data points. + y_calc : np.ndarray + Calculated data points. + weights : np.ndarray + Weights for each data point. + + Returns + ------- + float Weighted R-factor value. """ y_obs = np.asarray(y_obs) @@ -59,14 +70,19 @@ def calculate_rb_factor( y_obs: np.ndarray, y_calc: np.ndarray, ) -> float: - """Calculate the Bragg R-factor between observed and calculated - data. - - Args: - y_obs: Observed data points. - y_calc: Calculated data points. - - Returns: + """ + Calculate the Bragg R-factor between observed and calculated data. + + Parameters + ---------- + y_obs : np.ndarray + Observed data points. + y_calc : np.ndarray + Calculated data points. + + Returns + ------- + float Bragg R-factor value. """ y_obs = np.asarray(y_obs) @@ -80,14 +96,19 @@ def calculate_r_factor_squared( y_obs: np.ndarray, y_calc: np.ndarray, ) -> float: - """Calculate the R-factor squared between observed and calculated - data. - - Args: - y_obs: Observed data points. - y_calc: Calculated data points. - - Returns: + """ + Calculate the R-factor squared between observed and calculated data. + + Parameters + ---------- + y_obs : np.ndarray + Observed data points. + y_calc : np.ndarray + Calculated data points. + + Returns + ------- + float R-factor squared value. """ y_obs = np.asarray(y_obs) @@ -101,13 +122,19 @@ def calculate_reduced_chi_square( residuals: np.ndarray, num_parameters: int, ) -> float: - """Calculate the reduced chi-square statistic. - - Args: - residuals: Residuals between observed and calculated data. - num_parameters: Number of free parameters used in the model. - - Returns: + """ + Calculate the reduced chi-square statistic. + + Parameters + ---------- + residuals : np.ndarray + Residuals between observed and calculated data. + num_parameters : int + Number of free parameters used in the model. + + Returns + ------- + float Reduced chi-square value. """ residuals = np.asarray(residuals) @@ -124,16 +151,24 @@ def get_reliability_inputs( structures: Structures, experiments: Experiments, ) -> Tuple[np.ndarray, np.ndarray, Optional[np.ndarray]]: - """Collect observed and calculated data points for reliability - calculations. - - Args: - structures: Collection of structures. - experiments: Collection of experiments. - - Returns: - Tuple containing arrays of (observed values, calculated values, - error values) + """ + Collect observed and calculated data for reliability calculations. + + Parameters + ---------- + structures : Structures + Collection of structures. + experiments : Experiments + Collection of experiments. + + Returns + ------- + np.ndarray + Observed values. + np.ndarray + Calculated values. + Optional[np.ndarray] + Error values, or None if not available. """ y_obs_all = [] y_calc_all = [] diff --git a/src/easydiffraction/analysis/fit_helpers/reporting.py b/src/easydiffraction/analysis/fit_helpers/reporting.py index d9dbc40e..a1468aa5 100644 --- a/src/easydiffraction/analysis/fit_helpers/reporting.py +++ b/src/easydiffraction/analysis/fit_helpers/reporting.py @@ -1,7 +1,6 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -from typing import Any from typing import List from typing import Optional @@ -14,7 +13,8 @@ class FitResults: - """Container for results of a single optimization run. + """ + Container for results of a single optimization run. Holds success flag, chi-square metrics, iteration counts, timing, and parameter objects. Provides a printer to summarize key @@ -24,41 +24,53 @@ class FitResults: def __init__( self, success: bool = False, - parameters: Optional[List[Any]] = None, + parameters: Optional[List[object]] = None, chi_square: Optional[float] = None, reduced_chi_square: Optional[float] = None, message: str = '', iterations: int = 0, - engine_result: Optional[Any] = None, - starting_parameters: Optional[List[Any]] = None, + engine_result: Optional[object] = None, + starting_parameters: Optional[List[object]] = None, fitting_time: Optional[float] = None, - **kwargs: Any, + **kwargs: object, ) -> None: - """Initialize FitResults with the given parameters. - - Args: - success: Indicates if the fit was successful. - parameters: List of parameters used in the fit. - chi_square: Chi-square value of the fit. - reduced_chi_square: Reduced chi-square value of the fit. - message: Message related to the fit. - iterations: Number of iterations performed. - engine_result: Result from the fitting engine. - starting_parameters: Initial parameters for the fit. - fitting_time: Time taken for the fitting process. - **kwargs: Additional engine-specific fields. If ``redchi`` - is provided and ``reduced_chi_square`` is not set, it is - used as the reduced chi-square value. + """ + Initialize FitResults with the given parameters. + + Parameters + ---------- + success : bool, default=False + Indicates if the fit was successful. + parameters : Optional[List[object]], default=None + List of parameters used in the fit. + chi_square : Optional[float], default=None + Chi-square value of the fit. + reduced_chi_square : Optional[float], default=None + Reduced chi-square value of the fit. + message : str, default='' + Message related to the fit. + iterations : int, default=0 + Number of iterations performed. + engine_result : Optional[object], default=None + Result from the fitting engine. + starting_parameters : Optional[List[object]], default=None + Initial parameters for the fit. + fitting_time : Optional[float], default=None + Time taken for the fitting process. + **kwargs : object + Additional engine-specific fields. If ``redchi`` is provided + and ``reduced_chi_square`` is not set, it is used as the + reduced chi-square value. """ self.success: bool = success - self.parameters: List[Any] = parameters if parameters is not None else [] + self.parameters: List[object] = parameters if parameters is not None else [] self.chi_square: Optional[float] = chi_square self.reduced_chi_square: Optional[float] = reduced_chi_square self.message: str = message self.iterations: int = iterations - self.engine_result: Optional[Any] = engine_result - self.result: Optional[Any] = None - self.starting_parameters: List[Any] = ( + self.engine_result: Optional[object] = engine_result + self.result: Optional[object] = None + self.starting_parameters: List[object] = ( starting_parameters if starting_parameters is not None else [] ) self.fitting_time: Optional[float] = fitting_time @@ -77,14 +89,21 @@ def display_results( f_obs: Optional[List[float]] = None, f_calc: Optional[List[float]] = None, ) -> None: - """Render a human-readable summary of the fit. - - Args: - y_obs: Observed intensities for pattern R-factor metrics. - y_calc: Calculated intensities for pattern R-factor metrics. - y_err: Standard deviations of observed intensities for wR. - f_obs: Observed structure-factor magnitudes for Bragg R. - f_calc: Calculated structure-factor magnitudes for Bragg R. + """ + Render a human-readable summary of the fit. + + Parameters + ---------- + y_obs : Optional[List[float]], default=None + Observed intensities for pattern R-factor metrics. + y_calc : Optional[List[float]], default=None + Calculated intensities for pattern R-factor metrics. + y_err : Optional[List[float]], default=None + Standard deviations of observed intensities for wR. + f_obs : Optional[List[float]], default=None + Observed structure-factor magnitudes for Bragg R. + f_calc : Optional[List[float]], default=None + Calculated structure-factor magnitudes for Bragg R. """ status_icon = '✅' if self.success else '❌' rf = rf2 = wr = br = None diff --git a/src/easydiffraction/analysis/fit_helpers/tracking.py b/src/easydiffraction/analysis/fit_helpers/tracking.py index 438fd46d..eb32f3ea 100644 --- a/src/easydiffraction/analysis/fit_helpers/tracking.py +++ b/src/easydiffraction/analysis/fit_helpers/tracking.py @@ -1,9 +1,8 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import time from contextlib import suppress -from typing import Any from typing import List from typing import Optional @@ -36,31 +35,40 @@ class _TerminalLiveHandle: - """Adapter that exposes update()/close() for terminal live updates. + """ + Adapter that exposes update()/close() for terminal live updates. Wraps a rich.live.Live instance but keeps the tracker decoupled from the underlying UI mechanism. """ - def __init__(self, live) -> None: + def __init__(self, live: object) -> None: self._live = live - def update(self, renderable) -> None: + def update(self, renderable: object) -> None: + """ + Refresh the live display with a new renderable. + + Parameters + ---------- + renderable : object + A Rich-compatible renderable to display. + """ self._live.update(renderable, refresh=True) def close(self) -> None: + """Stop the live display, suppressing any errors.""" with suppress(Exception): self._live.stop() -def _make_display_handle() -> Any | None: - """Create and initialize a display/update handle for the - environment. +def _make_display_handle() -> object | None: + """ + Create and initialize a display/update handle for the environment. - In Jupyter, returns an IPython DisplayHandle and creates a - placeholder. - - In terminal, returns a _TerminalLiveHandle backed by rich Live. - - If neither applies, returns None. + placeholder. - In terminal, returns a _TerminalLiveHandle backed by + rich Live. - If neither applies, returns None. """ if in_jupyter() and display is not None and HTML is not None: h = DisplayHandle() @@ -77,7 +85,8 @@ def _make_display_handle() -> Any | None: class FitProgressTracker: - """Track and report reduced chi-square during optimization. + """ + Track and report reduced chi-square during optimization. The tracker keeps iteration counters, remembers the best observed reduced chi-square and when it occurred, and can display progress as @@ -94,8 +103,8 @@ def __init__(self) -> None: self._fitting_time: Optional[float] = None self._df_rows: List[List[str]] = [] - self._display_handle: Optional[Any] = None - self._live: Optional[Any] = None + self._display_handle: Optional[object] = None + self._live: Optional[object] = None def reset(self) -> None: """Reset internal state before a new optimization run.""" @@ -112,13 +121,19 @@ def track( residuals: np.ndarray, parameters: List[float], ) -> np.ndarray: - """Update progress with current residuals and parameters. - - Args: - residuals: Residuals between measured and calculated data. - parameters: Current free parameters being fitted. - - Returns: + """ + Update progress with current residuals and parameters. + + Parameters + ---------- + residuals : np.ndarray + Residuals between measured and calculated data. + parameters : List[float] + Current free parameters being fitted. + + Returns + ------- + np.ndarray Residuals unchanged, for optimizer consumption. """ self._iteration += 1 @@ -200,10 +215,13 @@ def stop_timer(self) -> None: self._fitting_time = self._end_time - self._start_time def start_tracking(self, minimizer_name: str) -> None: - """Initialize display and headers and announce the minimizer. + """ + Initialize display and headers and announce the minimizer. - Args: - minimizer_name: Name of the minimizer used for the run. + Parameters + ---------- + minimizer_name : str + Name of the minimizer used for the run. """ console.print(f"🚀 Starting fit process with '{minimizer_name}'...") console.print('📈 Goodness-of-fit (reduced χ²) change:') @@ -221,10 +239,13 @@ def start_tracking(self, minimizer_name: str) -> None: ) def add_tracking_info(self, row: List[str]) -> None: - """Append a formatted row to the progress display. + """ + Append a formatted row to the progress display. - Args: - row: Columns corresponding to DEFAULT_HEADERS. + Parameters + ---------- + row : List[str] + Columns corresponding to DEFAULT_HEADERS. """ # Append and update via the active handle (Jupyter or # terminal live) diff --git a/src/easydiffraction/analysis/fitting.py b/src/easydiffraction/analysis/fitting.py index 453aaf1a..fa6c0368 100644 --- a/src/easydiffraction/analysis/fitting.py +++ b/src/easydiffraction/analysis/fitting.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from typing import TYPE_CHECKING @@ -33,20 +33,26 @@ def fit( structures: Structures, experiments: Experiments, weights: Optional[np.array] = None, - analysis=None, + analysis: object = None, ) -> None: - """Run the fitting process. + """ + Run the fitting process. This method performs the optimization but does not display - results. Use :meth:`show_fit_results` on the Analysis object - to display the fit results after fitting is complete. - - Args: - structures: Collection of structures. - experiments: Collection of experiments. - weights: Optional weights for joint fitting. - analysis: Optional Analysis object to update its categories - during fitting. + results. Use :meth:`show_fit_results` on the Analysis object to + display the fit results after fitting is complete. + + Parameters + ---------- + structures : Structures + Collection of structures. + experiments : Experiments + Collection of experiments. + weights : Optional[np.array], default=None + Optional weights for joint fitting. + analysis : object, default=None + Optional Analysis object to update its categories during + fitting. """ params = structures.free_parameters + experiments.free_parameters @@ -58,6 +64,19 @@ def fit( param._fit_start_value = param.value def objective_function(engine_params: Dict[str, Any]) -> np.ndarray: + """ + Evaluate the residual for the current minimizer parameters. + + Parameters + ---------- + engine_params : Dict[str, Any] + Parameter values provided by the minimizer engine. + + Returns + ------- + np.ndarray + Residual array passed back to the minimizer. + """ return self._residual_function( engine_params=engine_params, parameters=params, @@ -75,16 +94,20 @@ def _process_fit_results( structures: Structures, experiments: Experiments, ) -> None: - """Collect reliability inputs and display fit results. + """ + Collect reliability inputs and display fit results. This method is typically called by :meth:`Analysis.show_fit_results` rather than directly. It - calculates R-factors and other metrics, then renders them to - the console. - - Args: - structures: Collection of structures. - experiments: Collection of experiments. + calculates R-factors and other metrics, then renders them to the + console. + + Parameters + ---------- + structures : Structures + Collection of structures. + experiments : Experiments + Collection of experiments. """ y_obs, y_calc, y_err = get_reliability_inputs( structures, @@ -108,13 +131,19 @@ def _collect_free_parameters( structures: Structures, experiments: Experiments, ) -> List[Parameter]: - """Collect free parameters from structures and experiments. - - Args: - structures: Collection of structures. - experiments: Collection of experiments. - - Returns: + """ + Collect free parameters from structures and experiments. + + Parameters + ---------- + structures : Structures + Collection of structures. + experiments : Experiments + Collection of experiments. + + Returns + ------- + List[Parameter] List of free parameters. """ free_params: List[Parameter] = structures.free_parameters + experiments.free_parameters @@ -127,22 +156,33 @@ def _residual_function( structures: Structures, experiments: Experiments, weights: Optional[np.array] = None, - analysis=None, + analysis: object = None, ) -> np.ndarray: - """Residual function computes the difference between measured - and calculated patterns. It updates the parameter values - according to the optimizer-provided engine_params. - - Args: - engine_params: Engine-specific parameter dict. - parameters: List of parameters being optimized. - structures: Collection of structures. - experiments: Collection of experiments. - weights: Optional weights for joint fitting. - analysis: Optional Analysis object to update its categories - during fitting. - - Returns: + """ + Compute residuals between measured and calculated patterns. + + It updates the parameter values according to the + optimizer-provided engine_params. + + Parameters + ---------- + engine_params : Dict[str, Any] + Engine-specific parameter dict. + parameters : List[Parameter] + List of parameters being optimized. + structures : Structures + Collection of structures. + experiments : Experiments + Collection of experiments. + weights : Optional[np.array], default=None + Optional weights for joint fitting. + analysis : object, default=None + Optional Analysis object to update its categories during + fitting. + + Returns + ------- + np.ndarray Array of weighted residuals. """ # Sync parameters back to objects diff --git a/src/easydiffraction/analysis/minimizers/__init__.py b/src/easydiffraction/analysis/minimizers/__init__.py index 8a41a6f2..01fbc136 100644 --- a/src/easydiffraction/analysis/minimizers/__init__.py +++ b/src/easydiffraction/analysis/minimizers/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.minimizers.dfols import DfolsMinimizer diff --git a/src/easydiffraction/analysis/minimizers/base.py b/src/easydiffraction/analysis/minimizers/base.py index 069601ed..f8499dad 100644 --- a/src/easydiffraction/analysis/minimizers/base.py +++ b/src/easydiffraction/analysis/minimizers/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from abc import ABC @@ -16,14 +16,13 @@ class MinimizerBase(ABC): - """Abstract base for concrete minimizers. - - Contract: - - Subclasses must implement ``_prepare_solver_args``, - ``_run_solver``, ``_sync_result_to_parameters`` and - ``_check_success``. - - The ``fit`` method orchestrates the full workflow and returns - :class:`FitResults`. + """ + Abstract base for concrete minimizers. + + Contract: - Subclasses must implement ``_prepare_solver_args``, + ``_run_solver``, ``_sync_result_to_parameters`` and + ``_check_success``. - The ``fit`` method orchestrates the full + workflow and returns :class:`FitResults`. """ def __init__( @@ -44,10 +43,13 @@ def __init__( self.tracker: FitProgressTracker = FitProgressTracker() def _start_tracking(self, minimizer_name: str) -> None: - """Initialize progress tracking and timer. + """ + Initialize progress tracking and timer. - Args: - minimizer_name: Human-readable name shown in progress. + Parameters + ---------- + minimizer_name : str + Human-readable name shown in progress. """ self.tracker.reset() self.tracker.start_tracking(minimizer_name) @@ -60,12 +62,17 @@ def _stop_tracking(self) -> None: @abstractmethod def _prepare_solver_args(self, parameters: List[Any]) -> Dict[str, Any]: - """Prepare keyword-arguments for the underlying solver. + """ + Prepare keyword-arguments for the underlying solver. - Args: - parameters: List of free parameters to be fitted. + Parameters + ---------- + parameters : List[Any] + List of free parameters to be fitted. - Returns: + Returns + ------- + Dict[str, Any] Mapping of keyword arguments to pass into ``_run_solver``. """ pass @@ -73,36 +80,40 @@ def _prepare_solver_args(self, parameters: List[Any]) -> Dict[str, Any]: @abstractmethod def _run_solver( self, - objective_function: Callable[..., Any], - engine_parameters: Dict[str, Any], - ) -> Any: + objective_function: Callable[..., object], + engine_parameters: Dict[str, object], + ) -> object: """Execute the concrete solver and return its raw result.""" pass @abstractmethod def _sync_result_to_parameters( self, - raw_result: Any, - parameters: List[Any], + raw_result: object, + parameters: List[object], ) -> None: - """Copy values from ``raw_result`` back to ``parameters`` in- - place. - """ + """Copy raw_result values back to parameters in-place.""" pass def _finalize_fit( self, - parameters: List[Any], - raw_result: Any, + parameters: List[object], + raw_result: object, ) -> FitResults: - """Build :class:`FitResults` and store it on ``self.result``. - - Args: - parameters: Parameters after the solver finished. - raw_result: Backend-specific solver output object. - - Returns: - FitResults: Aggregated outcome of the fit. + """ + Build :class:`FitResults` and store it on ``self.result``. + + Parameters + ---------- + parameters : List[object] + Parameters after the solver finished. + raw_result : object + Backend-specific solver output object. + + Returns + ------- + FitResults + Aggregated outcome of the fit. """ self._sync_result_to_parameters(parameters, raw_result) success = self._check_success(raw_result) @@ -117,23 +128,29 @@ def _finalize_fit( return self.result @abstractmethod - def _check_success(self, raw_result: Any) -> bool: + def _check_success(self, raw_result: object) -> bool: """Determine whether the fit was successful.""" pass def fit( self, - parameters: List[Any], - objective_function: Callable[..., Any], + parameters: List[object], + objective_function: Callable[..., object], ) -> FitResults: - """Run the full minimization workflow. - - Args: - parameters: Free parameters to optimize. - objective_function: Callable returning residuals for a given - set of engine arguments. - - Returns: + """ + Run the full minimization workflow. + + Parameters + ---------- + parameters : List[object] + Free parameters to optimize. + objective_function : Callable[..., object] + Callable returning residuals for a given set of engine + arguments. + + Returns + ------- + FitResults FitResults with success flag, best chi2 and timing. """ minimizer_name = self.name or 'Unnamed Minimizer' @@ -153,11 +170,11 @@ def fit( def _objective_function( self, - engine_params: Dict[str, Any], - parameters: List[Any], - structures: Any, - experiments: Any, - calculator: Any, + engine_params: Dict[str, object], + parameters: List[object], + structures: object, + experiments: object, + calculator: object, ) -> np.ndarray: """Default objective helper computing residuals array.""" return self._compute_residuals( @@ -170,11 +187,11 @@ def _objective_function( def _create_objective_function( self, - parameters: List[Any], - structures: Any, - experiments: Any, - calculator: Any, - ) -> Callable[[Dict[str, Any]], np.ndarray]: + parameters: List[object], + structures: object, + experiments: object, + calculator: object, + ) -> Callable[[Dict[str, object]], np.ndarray]: """Return a closure capturing problem context for the solver.""" return lambda engine_params: self._objective_function( engine_params, diff --git a/src/easydiffraction/analysis/minimizers/dfols.py b/src/easydiffraction/analysis/minimizers/dfols.py index b68a034a..fad9ad06 100644 --- a/src/easydiffraction/analysis/minimizers/dfols.py +++ b/src/easydiffraction/analysis/minimizers/dfols.py @@ -1,7 +1,6 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -from typing import Any from typing import Dict from typing import List @@ -17,9 +16,7 @@ @MinimizerFactory.register class DfolsMinimizer(MinimizerBase): - """Minimizer using the DFO-LS package (Derivative-Free Optimization - for Least-Squares). - """ + """Minimizer using DFO-LS (derivative-free least-squares).""" type_info = TypeInfo( tag='dfols', @@ -30,13 +27,13 @@ def __init__( self, name: str = 'dfols', max_iterations: int = DEFAULT_MAX_ITERATIONS, - **kwargs: Any, + **kwargs: object, ) -> None: super().__init__(name=name, method=None, max_iterations=max_iterations) # Intentionally unused, accepted for API compatibility del kwargs - def _prepare_solver_args(self, parameters: List[Any]) -> Dict[str, Any]: + def _prepare_solver_args(self, parameters: List[object]) -> Dict[str, object]: x0 = [] bounds_lower = [] bounds_upper = [] @@ -47,21 +44,25 @@ def _prepare_solver_args(self, parameters: List[Any]) -> Dict[str, Any]: bounds = (np.array(bounds_lower), np.array(bounds_upper)) return {'x0': np.array(x0), 'bounds': bounds} - def _run_solver(self, objective_function: Any, **kwargs: Any) -> Any: + def _run_solver(self, objective_function: object, **kwargs: object) -> object: x0 = kwargs.get('x0') bounds = kwargs.get('bounds') return solve(objective_function, x0=x0, bounds=bounds, maxfun=self.max_iterations) def _sync_result_to_parameters( self, - parameters: List[Any], - raw_result: Any, + parameters: List[object], + raw_result: object, ) -> None: - """Synchronizes the result from the solver to the parameters. - - Args: - parameters: List of parameters being optimized. - raw_result: The result object returned by the solver. + """ + Synchronize the solver result back to the parameters. + + Parameters + ---------- + parameters : List[object] + List of parameters being optimized. + raw_result : object + The result object returned by the solver. """ # Ensure compatibility with raw_result coming from dfols.solve() result_values = raw_result.x if hasattr(raw_result, 'x') else raw_result @@ -74,13 +75,18 @@ def _sync_result_to_parameters( # calculate later if needed param.uncertainty = None - def _check_success(self, raw_result: Any) -> bool: - """Determines success from DFO-LS result dictionary. + def _check_success(self, raw_result: object) -> bool: + """ + Determine success from DFO-LS result dictionary. - Args: - raw_result: The result object returned by the solver. + Parameters + ---------- + raw_result : object + The result object returned by the solver. - Returns: + Returns + ------- + bool True if the optimization was successful, False otherwise. """ return raw_result.flag == raw_result.EXIT_SUCCESS diff --git a/src/easydiffraction/analysis/minimizers/factory.py b/src/easydiffraction/analysis/minimizers/factory.py index e12a9533..18f67cc6 100644 --- a/src/easydiffraction/analysis/minimizers/factory.py +++ b/src/easydiffraction/analysis/minimizers/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Minimizer factory — delegates to ``FactoryBase``.""" diff --git a/src/easydiffraction/analysis/minimizers/lmfit.py b/src/easydiffraction/analysis/minimizers/lmfit.py index cb0a59fd..771bacad 100644 --- a/src/easydiffraction/analysis/minimizers/lmfit.py +++ b/src/easydiffraction/analysis/minimizers/lmfit.py @@ -1,7 +1,6 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -from typing import Any from typing import Dict from typing import List @@ -38,16 +37,21 @@ def __init__( def _prepare_solver_args( self, - parameters: List[Any], - ) -> Dict[str, Any]: - """Prepares the solver arguments for the lmfit minimizer. + parameters: List[object], + ) -> Dict[str, object]: + """ + Prepare the solver arguments for the lmfit minimizer. - Args: - parameters: List of parameters to be optimized. + Parameters + ---------- + parameters : List[object] + List of parameters to be optimized. - Returns: + Returns + ------- + Dict[str, object] A dictionary containing the prepared lmfit. Parameters - object. + object. """ engine_parameters = lmfit.Parameters() for param in parameters: @@ -60,14 +64,20 @@ def _prepare_solver_args( ) return {'engine_parameters': engine_parameters} - def _run_solver(self, objective_function: Any, **kwargs: Any) -> Any: - """Runs the lmfit solver. - - Args: - objective_function: The objective function to minimize. - **kwargs: Additional arguments for the solver. - - Returns: + def _run_solver(self, objective_function: object, **kwargs: object) -> object: + """ + Run the lmfit solver. + + Parameters + ---------- + objective_function : object + The objective function to minimize. + **kwargs : object + Additional arguments for the solver. + + Returns + ------- + object The result of the lmfit minimization. """ engine_parameters = kwargs.get('engine_parameters') @@ -82,14 +92,18 @@ def _run_solver(self, objective_function: Any, **kwargs: Any) -> Any: def _sync_result_to_parameters( self, - parameters: List[Any], - raw_result: Any, + parameters: List[object], + raw_result: object, ) -> None: - """Synchronizes the result from the solver to the parameters. - - Args: - parameters: List of parameters being optimized. - raw_result: The result object returned by the solver. + """ + Synchronize the result from the solver to the parameters. + + Parameters + ---------- + parameters : List[object] + List of parameters being optimized. + raw_result : object + The result object returned by the solver. """ param_values = raw_result.params if hasattr(raw_result, 'params') else raw_result @@ -101,13 +115,18 @@ def _sync_result_to_parameters( param._set_value_from_minimizer(param_result.value) param.uncertainty = getattr(param_result, 'stderr', None) - def _check_success(self, raw_result: Any) -> bool: - """Determines success from lmfit MinimizerResult. + def _check_success(self, raw_result: object) -> bool: + """ + Determine success from lmfit MinimizerResult. - Args: - raw_result: The result object returned by the solver. + Parameters + ---------- + raw_result : object + The result object returned by the solver. - Returns: + Returns + ------- + bool True if the optimization was successful, False otherwise. """ return getattr(raw_result, 'success', False) @@ -116,18 +135,25 @@ def _iteration_callback( self, params: lmfit.Parameters, iter: int, - resid: Any, - *args: Any, - **kwargs: Any, + resid: object, + *args: object, + **kwargs: object, ) -> None: - """Callback function for each iteration of the minimizer. - - Args: - params: The current parameters. - iter: The current iteration number. - resid: The residuals. - *args: Additional positional arguments. - **kwargs: Additional keyword arguments. + """ + Handle each iteration callback of the minimizer. + + Parameters + ---------- + params : lmfit.Parameters + The current parameters. + iter : int + The current iteration number. + resid : object + The residuals. + *args : object + Additional positional arguments. + **kwargs : object + Additional keyword arguments. """ # Intentionally unused, required by callback signature del params, resid, args, kwargs diff --git a/src/easydiffraction/core/__init__.py b/src/easydiffraction/core/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/core/__init__.py +++ b/src/easydiffraction/core/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/core/category.py b/src/easydiffraction/core/category.py index d4b57dc3..25e81894 100644 --- a/src/easydiffraction/core/category.py +++ b/src/easydiffraction/core/category.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -30,12 +30,13 @@ def __str__(self) -> str: return f'<{name} ({params})>' # TODO: Common for all categories - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: del called_by_minimizer pass @property - def unique_name(self): + def unique_name(self) -> str: + """Fully qualified name: datablock, category, entry.""" parts = [ self._identity.datablock_entry_name, self._identity.category_code, @@ -46,7 +47,8 @@ def unique_name(self): return '.'.join(str_parts) @property - def parameters(self): + def parameters(self) -> list: + """All GenericDescriptorBase instances on this item.""" return [v for v in vars(self).values() if isinstance(v, GenericDescriptorBase)] @property @@ -54,7 +56,7 @@ def as_cif(self) -> str: """Return CIF representation of this object.""" return category_item_to_cif(self) - def from_cif(self, block, idx=0): + def from_cif(self, block: object, idx: int = 0) -> None: """Populate this item from a CIF block.""" category_item_from_cif(self, block, idx) @@ -160,7 +162,8 @@ def help(self) -> None: class CategoryCollection(CollectionBase): - """Handles loop-style category containers (e.g. AtomSites). + """ + Handles loop-style category containers (e.g. AtomSites). Each item is a CategoryItem (component). """ @@ -168,16 +171,17 @@ class CategoryCollection(CollectionBase): # TODO: Common for all categories _update_priority = 10 # Default. Lower values run first. - def _key_for(self, item): + def _key_for(self, item: object) -> str | None: """Return the category-level identity key for *item*.""" return item._identity.category_entry_name def _mark_parent_dirty(self) -> None: - """Set ``_need_categories_update`` on the parent datablock. + """ + Set ``_need_categories_update`` on the parent datablock. Called whenever the collection content changes (items added or - removed) so that subsequent ``_update_categories()`` calls - re-run all category updates. + removed) so that subsequent ``_update_categories()`` calls re- + run all category updates. """ parent = getattr(self, '_parent', None) if parent is not None and hasattr(parent, '_need_categories_update'): @@ -190,16 +194,17 @@ def __str__(self) -> str: return f'<{name} collection ({size} items)>' # TODO: Common for all categories - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: del called_by_minimizer pass @property - def unique_name(self): + def unique_name(self) -> str | None: + """Return None; collections have no unique name.""" return None @property - def parameters(self): + def parameters(self) -> list: """All parameters from all items in this collection.""" params = [] for item in self._items: @@ -211,27 +216,33 @@ def as_cif(self) -> str: """Return CIF representation of this object.""" return category_collection_to_cif(self) - def from_cif(self, block): + def from_cif(self, block: object) -> None: """Populate this collection from a CIF block.""" category_collection_from_cif(self, block) - def add(self, item) -> None: - """Insert or replace a pre-built item into the collection. + def add(self, item: object) -> None: + """ + Insert or replace a pre-built item into the collection. - Args: - item: A ``CategoryItem`` instance to add. + Parameters + ---------- + item : object + A ``CategoryItem`` instance to add. """ self[item._identity.category_entry_name] = item self._mark_parent_dirty() - def create(self, **kwargs) -> None: - """Create a new item with the given attributes and add it. + def create(self, **kwargs: object) -> None: + """ + Create a new item with the given attributes and add it. A default instance of the collection's item type is created, then each keyword argument is applied via ``setattr``. - Args: - **kwargs: Attribute names and values for the new item. + Parameters + ---------- + **kwargs : object + Attribute names and values for the new item. """ child_obj = self._item_type() diff --git a/src/easydiffraction/core/collection.py b/src/easydiffraction/core/collection.py index 164f3d77..0560a37e 100644 --- a/src/easydiffraction/core/collection.py +++ b/src/easydiffraction/core/collection.py @@ -1,33 +1,41 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Lightweight container for guarded items with name-based indexing. +""" +Lightweight container for guarded items with name-based indexing. -`CollectionBase` maintains an ordered list of items and a lazily rebuilt -index by the item's identity key. It supports dict-like access for get, -set and delete, along with iteration over the items. +``CollectionBase`` maintains an ordered list of items and a lazily +rebuilt index by the item's identity key. It supports dict-like access +for get, set and delete, along with iteration over the items. """ from __future__ import annotations +from typing import Generator +from typing import Iterator + from easydiffraction.core.guard import GuardedBase class CollectionBase(GuardedBase): - """A minimal collection with stable iteration and name indexing. + """ + A minimal collection with stable iteration and name indexing. - Args: - item_type: Type of items accepted by the collection. Used for - validation and tooling; not enforced at runtime here. + Parameters + ---------- + item_type : type + Type of items accepted by the collection. Used for validation + and tooling; not enforced at runtime here. """ - def __init__(self, item_type) -> None: + def __init__(self, item_type: type) -> None: super().__init__() self._items: list = [] self._index: dict = {} self._item_type = item_type - def __getitem__(self, name: str): - """Return an item by its identity key. + def __getitem__(self, name: str) -> GuardedBase: + """ + Return an item by its identity key. Rebuilds the internal index on a cache miss to stay consistent with recent mutations. @@ -38,7 +46,7 @@ def __getitem__(self, name: str): self._rebuild_index() return self._index[name] - def __setitem__(self, name: str, item) -> None: + def __setitem__(self, name: str, item: GuardedBase) -> None: """Insert or replace an item under the given identity key.""" # Check if item with same key exists; if so, replace it for i, existing_item in enumerate(self._items): @@ -66,7 +74,7 @@ def __contains__(self, name: str) -> bool: self._rebuild_index() return name in self._index - def __iter__(self): + def __iter__(self) -> Iterator[GuardedBase]: """Iterate over items in insertion order.""" return iter(self._items) @@ -75,18 +83,27 @@ def __len__(self) -> int: return len(self._items) def remove(self, name: str) -> None: - """Remove an item by its key. + """ + Remove an item by its key. - Args: - name: Identity key of the item to remove. + Parameters + ---------- + name : str + Identity key of the item to remove. - Raises: - KeyError: If no item with the given key exists. + Raises + ------ + KeyError + If no item with the given key exists. """ - del self[name] + try: + del self[name] + except KeyError: + raise - def _key_for(self, item): - """Return the identity key for *item*. + def _key_for(self, item: GuardedBase) -> str | None: + """ + Return the identity key for *item*. Subclasses must override to return the appropriate key (``category_entry_name`` or ``datablock_entry_name``). @@ -101,20 +118,20 @@ def _rebuild_index(self) -> None: if key: self._index[key] = item - def keys(self): + def keys(self) -> Generator[str | None, None, None]: """Yield keys for all items in insertion order.""" return (self._key_for(item) for item in self._items) - def values(self): + def values(self) -> Generator[GuardedBase, None, None]: """Yield items in insertion order.""" return (item for item in self._items) - def items(self): + def items(self) -> Generator[tuple[str | None, GuardedBase], None, None]: """Yield ``(key, item)`` pairs in insertion order.""" return ((self._key_for(item), item) for item in self._items) @property - def names(self): + def names(self) -> list[str | None]: """List of all item keys in the collection.""" return list(self.keys()) diff --git a/src/easydiffraction/core/datablock.py b/src/easydiffraction/core/datablock.py index 221845b6..6a1ef289 100644 --- a/src/easydiffraction/core/datablock.py +++ b/src/easydiffraction/core/datablock.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -13,7 +13,7 @@ class DatablockItem(GuardedBase): """Base class for items in a datablock collection.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._need_categories_update = True @@ -33,7 +33,7 @@ def __repr__(self) -> str: def _update_categories( self, - called_by_minimizer=False, + called_by_minimizer: bool = False, ) -> None: # TODO: Make abstract method and implement in subclasses. # This should call apply_symmetry and apply_constraints in the @@ -62,11 +62,13 @@ def _update_categories( self._need_categories_update = False @property - def unique_name(self): + def unique_name(self) -> str | None: + """Unique name of this datablock item (from identity).""" return self._identity.datablock_entry_name @property - def categories(self): + def categories(self) -> list: + """All category objects in this datablock by priority.""" cats = [ v for v in vars(self).values() if isinstance(v, (CategoryItem, CategoryCollection)) ] @@ -74,10 +76,8 @@ def categories(self): return sorted(cats, key=lambda c: type(c)._update_priority) @property - def parameters(self): - """All parameters from all categories contained in this - datablock. - """ + def parameters(self) -> list: + """All parameters from all categories in this datablock.""" params = [] for v in self.categories: params.extend(v.parameters) @@ -118,26 +118,29 @@ def help(self) -> None: class DatablockCollection(CollectionBase): - """Handles top-level category collections (e.g. Structures, - Experiments). + """ + Collection of top-level datablocks (e.g. Structures, Experiments). Each item is a DatablockItem. - Subclasses provide explicit ``add_from_*`` convenience methods - that delegate to the corresponding factory classmethods, then - call :meth:`add` with the resulting item. + Subclasses provide explicit ``add_from_*`` convenience methods that + delegate to the corresponding factory classmethods, then call + :meth:`add` with the resulting item. """ - def _key_for(self, item): + def _key_for(self, item: object) -> str | None: """Return the datablock-level identity key for *item*.""" return item._identity.datablock_entry_name - def add(self, item) -> None: - """Add a pre-built item to the collection. + def add(self, item: object) -> None: + """ + Add a pre-built item to the collection. - Args: - item: A ``DatablockItem`` instance (e.g. a ``Structure`` - or ``ExperimentBase`` subclass). + Parameters + ---------- + item : object + A ``DatablockItem`` instance (e.g. a ``Structure`` or + ``ExperimentBase`` subclass). """ self[item._identity.datablock_entry_name] = item @@ -148,25 +151,26 @@ def __str__(self) -> str: return f'<{name} collection ({size} items)>' @property - def unique_name(self): + def unique_name(self) -> str | None: + """Return None; collections have no unique name.""" return None @property - def parameters(self): + def parameters(self) -> list: """All parameters from all datablocks in this collection.""" params = [] for db in self._items: params.extend(db.parameters) return params - # was in class AbstractDatablock(ABC): @property def fittable_parameters(self) -> list: + """All non-constrained Parameters in this collection.""" return [p for p in self.parameters if isinstance(p, Parameter) and not p.constrained] - # was in class AbstractDatablock(ABC): @property def free_parameters(self) -> list: + """All fittable parameters that are currently marked as free.""" return [p for p in self.fittable_parameters if p.free] @property diff --git a/src/easydiffraction/core/diagnostic.py b/src/easydiffraction/core/diagnostic.py index 74c2ab3b..dc5ea4c4 100644 --- a/src/easydiffraction/core/diagnostic.py +++ b/src/easydiffraction/core/diagnostic.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Diagnostics helpers for logging validation messages. +""" +Diagnostics helpers for logging validation messages. This module centralizes human-friendly error and debug logs for attribute validation and configuration checks. @@ -19,8 +20,9 @@ class Diagnostics: # ============================================================== @staticmethod - def type_override_error(cls_name: str, expected, got): - """Report an invalid DataTypes override. + def type_override_error(cls_name: str, expected: object, got: object) -> None: + """ + Report an invalid DataTypes override. Used when descriptor and AttributeSpec types conflict. """ @@ -41,7 +43,7 @@ def type_override_error(cls_name: str, expected, got): def readonly_error( name: str, key: str | None = None, - ): + ) -> None: """Log an attempt to change a read-only attribute.""" Diagnostics._log_error( f"Cannot modify read-only attribute '{key}' of <{name}>.", @@ -53,11 +55,9 @@ def attr_error( name: str, key: str, allowed: set[str], - label='Allowed', - ): - """Log access to an unknown attribute and suggest closest - key. - """ + label: str = 'Allowed', + ) -> None: + """Log unknown attribute access and suggest closest key.""" suggestion = Diagnostics._build_suggestion(key, allowed) # Use consistent (label) logic for allowed hint = suggestion or Diagnostics._build_allowed(allowed, label=label) @@ -73,11 +73,11 @@ def attr_error( @staticmethod def type_mismatch( name: str, - value, - expected_type, - current=None, - default=None, - ): + value: object, + expected_type: object, + current: object = None, + default: object = None, + ) -> None: """Log a type mismatch and keep current or default value.""" got_type = type(value).__name__ msg = ( @@ -91,12 +91,12 @@ def type_mismatch( @staticmethod def range_mismatch( name: str, - value, - ge, - le, - current=None, - default=None, - ): + value: object, + ge: float, + le: float, + current: object = None, + default: object = None, + ) -> None: """Log range violation for a numeric value.""" msg = f'Value mismatch for <{name}>. Provided {value!r} outside [{ge}, {le}].' Diagnostics._log_error_with_fallback( @@ -106,11 +106,11 @@ def range_mismatch( @staticmethod def choice_mismatch( name: str, - value, - allowed, - current=None, - default=None, - ): + value: object, + allowed: object, + current: object = None, + default: object = None, + ) -> None: """Log an invalid choice against allowed values.""" msg = f'Value mismatch for <{name}>. Provided {value!r} is unknown.' if allowed is not None: @@ -122,11 +122,11 @@ def choice_mismatch( @staticmethod def regex_mismatch( name: str, - value, - pattern, - current=None, - default=None, - ): + value: object, + pattern: str, + current: object = None, + default: object = None, + ) -> None: """Log a regex mismatch with the expected pattern.""" msg = ( f"Value mismatch for <{name}>. Provided {value!r} does not match pattern '{pattern}'." @@ -136,24 +136,24 @@ def regex_mismatch( ) @staticmethod - def no_value(name, default): + def no_value(name: str, default: object) -> None: """Log that default will be used due to missing value.""" Diagnostics._log_debug(f'No value provided for <{name}>. Using default {default!r}.') @staticmethod - def none_value(name): + def none_value(name: str) -> None: """Log explicit None provided by a user.""" Diagnostics._log_debug(f'Using `None` explicitly provided for <{name}>.') @staticmethod - def none_value_skip_range(name): + def none_value_skip_range(name: str) -> None: """Log that range validation is skipped due to None.""" Diagnostics._log_debug( f'Skipping range validation as `None` is explicitly provided for <{name}>.' ) @staticmethod - def validated(name, value, stage: str | None = None): + def validated(name: str, value: object, stage: str | None = None) -> None: """Log that a value passed a validation stage.""" stage_info = f' {stage}' if stage else '' Diagnostics._log_debug(f'Value {value!r} for <{name}> passed{stage_info} validation.') @@ -163,17 +163,17 @@ def validated(name, value, stage: str | None = None): # ============================================================== @staticmethod - def _log_error(msg, exc_type=Exception): + def _log_error(msg: str, exc_type: type[Exception] = Exception) -> None: """Emit an error-level message via shared logger.""" log.error(msg, exc_type=exc_type) @staticmethod def _log_error_with_fallback( - msg, - current=None, - default=None, - exc_type=Exception, - ): + msg: str, + current: object = None, + default: object = None, + exc_type: type[Exception] = Exception, + ) -> None: """Emit an error message and mention kept or default value.""" if current is not None: msg += f' Keeping current {current!r}.' @@ -182,7 +182,7 @@ def _log_error_with_fallback( log.error(msg, exc_type=exc_type) @staticmethod - def _log_debug(msg): + def _log_debug(msg: str) -> None: """Emit a debug-level message via shared logger.""" log.debug(msg) @@ -191,7 +191,7 @@ def _log_debug(msg): # ============================================================== @staticmethod - def _suggest(key: str, allowed: set[str]): + def _suggest(key: str, allowed: set[str]) -> str | None: """Suggest closest allowed key using string similarity.""" if not allowed: return None @@ -200,12 +200,12 @@ def _suggest(key: str, allowed: set[str]): return matches[0] if matches else None @staticmethod - def _build_suggestion(key: str, allowed: set[str]): + def _build_suggestion(key: str, allowed: set[str]) -> str: s = Diagnostics._suggest(key, allowed) return f" Did you mean '{s}'?" if s else '' @staticmethod - def _build_allowed(allowed, label='Allowed attributes'): + def _build_allowed(allowed: object, label: str = 'Allowed attributes') -> str: # allowed may be a set, list, or other iterable if allowed: allowed_list = list(allowed) diff --git a/src/easydiffraction/core/factory.py b/src/easydiffraction/core/factory.py index 8e699085..af99217a 100644 --- a/src/easydiffraction/core/factory.py +++ b/src/easydiffraction/core/factory.py @@ -1,10 +1,10 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Base factory with registration, lookup, and context-dependent -defaults. +""" +Base factory with registration, lookup, and context-dependent defaults. -Concrete factories inherit from ``FactoryBase`` and only need to -define ``_default_rules``. +Concrete factories inherit from ``FactoryBase`` and only need to define +``_default_rules``. """ from __future__ import annotations @@ -21,12 +21,13 @@ class FactoryBase: - """Shared base for all factories. + """ + Shared base for all factories. Subclasses must set: * ``_default_rules`` -- mapping of ``frozenset`` conditions to tag - strings. Use ``frozenset(): 'tag'`` for a universal default. + strings. Use ``frozenset(): 'tag'`` for a universal default. The ``__init_subclass__`` hook ensures every subclass gets its own independent ``_registry`` list. @@ -35,7 +36,7 @@ class FactoryBase: _registry: List[Type] = [] _default_rules: Dict[FrozenSet[Tuple[str, Any]], str] = {} - def __init_subclass__(cls, **kwargs): + def __init_subclass__(cls, **kwargs: object) -> None: """Give each subclass its own independent registry and rules.""" super().__init_subclass__(**kwargs) cls._registry = [] @@ -47,14 +48,14 @@ def __init_subclass__(cls, **kwargs): # ------------------------------------------------------------------ @classmethod - def register(cls, klass): - """Class decorator to register a concrete class. + def register(cls, klass: type) -> type: + """ + Class decorator to register a concrete class. Usage:: - @SomeFactory.register - class MyClass(SomeBase): - type_info = TypeInfo(...) + @SomeFactory.register class MyClass(SomeBase): type_info = + TypeInfo(...) Returns the class unmodified. """ @@ -80,22 +81,29 @@ def supported_tags(cls) -> List[str]: # ------------------------------------------------------------------ @classmethod - def default_tag(cls, **conditions) -> str: - """Resolve the default tag for a given experimental context. + def default_tag(cls, **conditions: object) -> str: + """ + Resolve the default tag for a given experimental context. Uses *largest-subset matching*: the rule whose key is the - biggest subset of the given conditions wins. A rule with an + biggest subset of the given conditions wins. A rule with an empty key (``frozenset()``) acts as a universal fallback. - Args: - **conditions: Experimental-axis values, e.g. - ``scattering_type=ScatteringTypeEnum.BRAGG``. + Parameters + ---------- + **conditions : object + Experimental-axis values, e.g. + ``scattering_type=ScatteringTypeEnum.BRAGG``. - Returns: + Returns + ------- + str The resolved default tag string. - Raises: - ValueError: If no rule matches the given conditions. + Raises + ------ + ValueError + If no rule matches the given conditions. """ condition_set = frozenset(conditions.items()) best_match_tag: str | None = None @@ -118,15 +126,26 @@ def default_tag(cls, **conditions) -> str: # ------------------------------------------------------------------ @classmethod - def create(cls, tag: str, **kwargs) -> Any: - """Instantiate a registered class by *tag*. - - Args: - tag: ``type_info.tag`` value. - **kwargs: Forwarded to the class constructor. - - Raises: - ValueError: If *tag* is not in the registry. + def create(cls, tag: str, **kwargs: object) -> object: + """ + Instantiate a registered class by *tag*. + + Parameters + ---------- + tag : str + ``type_info.tag`` value. + **kwargs : object + Forwarded to the class constructor. + + Returns + ------- + object + A new instance of the registered class. + + Raises + ------ + ValueError + If *tag* is not in the registry. """ supported = cls._supported_map() if tag not in supported: @@ -134,13 +153,21 @@ def create(cls, tag: str, **kwargs) -> Any: return supported[tag](**kwargs) @classmethod - def create_default_for(cls, **conditions) -> Any: - """Instantiate the default class for a given context. + def create_default_for(cls, **conditions: object) -> object: + """ + Instantiate the default class for a given context. Combines ``default_tag(**conditions)`` with ``create(tag)``. - Args: - **conditions: Experimental-axis values. + Parameters + ---------- + **conditions : object + Experimental-axis values. + + Returns + ------- + object + A new instance of the default class. """ tag = cls.default_tag(**conditions) return cls.create(tag) @@ -153,20 +180,32 @@ def create_default_for(cls, **conditions) -> Any: def supported_for( cls, *, - calculator=None, - sample_form=None, - scattering_type=None, - beam_mode=None, - radiation_probe=None, + calculator: object = None, + sample_form: object = None, + scattering_type: object = None, + beam_mode: object = None, + radiation_probe: object = None, ) -> List[Type]: - """Return classes matching conditions and/or calculator. - - Args: - calculator: Optional ``CalculatorEnum`` value. - sample_form: Optional ``SampleFormEnum`` value. - scattering_type: Optional ``ScatteringTypeEnum`` value. - beam_mode: Optional ``BeamModeEnum`` value. - radiation_probe: Optional ``RadiationProbeEnum`` value. + """ + Return classes matching conditions and/or calculator. + + Parameters + ---------- + calculator : object, default=None + Optional ``CalculatorEnum`` value. + sample_form : object, default=None + Optional ``SampleFormEnum`` value. + scattering_type : object, default=None + Optional ``ScatteringTypeEnum`` value. + beam_mode : object, default=None + Optional ``BeamModeEnum`` value. + radiation_probe : object, default=None + Optional ``RadiationProbeEnum`` value. + + Returns + ------- + List[Type] + Classes matching the given conditions. """ result = [] for klass in cls._supported_map().values(): @@ -192,20 +231,27 @@ def supported_for( def show_supported( cls, *, - calculator=None, - sample_form=None, - scattering_type=None, - beam_mode=None, - radiation_probe=None, + calculator: object = None, + sample_form: object = None, + scattering_type: object = None, + beam_mode: object = None, + radiation_probe: object = None, ) -> None: - """Pretty-print a table of supported types. - - Args: - calculator: Optional ``CalculatorEnum`` filter. - sample_form: Optional ``SampleFormEnum`` filter. - scattering_type: Optional ``ScatteringTypeEnum`` filter. - beam_mode: Optional ``BeamModeEnum`` filter. - radiation_probe: Optional ``RadiationProbeEnum`` filter. + """ + Pretty-print a table of supported types. + + Parameters + ---------- + calculator : object, default=None + Optional ``CalculatorEnum`` filter. + sample_form : object, default=None + Optional ``SampleFormEnum`` filter. + scattering_type : object, default=None + Optional ``ScatteringTypeEnum`` filter. + beam_mode : object, default=None + Optional ``BeamModeEnum`` filter. + radiation_probe : object, default=None + Optional ``RadiationProbeEnum`` filter. """ matching = cls.supported_for( calculator=calculator, diff --git a/src/easydiffraction/core/guard.py b/src/easydiffraction/core/guard.py index a0033d14..17b60fb5 100644 --- a/src/easydiffraction/core/guard.py +++ b/src/easydiffraction/core/guard.py @@ -1,33 +1,35 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations from abc import ABC from abc import abstractmethod +from typing import Generator from easydiffraction.core.diagnostic import Diagnostics from easydiffraction.core.identity import Identity class GuardedBase(ABC): - """Base class enforcing controlled attribute access and parent - linkage. - """ + """Base class enforcing controlled attribute access and linkage.""" _diagnoser = Diagnostics() - def __init__(self): + def __init__(self) -> None: super().__init__() self._identity = Identity(owner=self) def __str__(self) -> str: + """Return the string representation of this object.""" return f'<{self.unique_name}>' def __repr__(self) -> str: + """Return the developer representation of this object.""" return self.__str__() - def __getattr__(self, key: str): + def __getattr__(self, key: str) -> None: + """Raise a descriptive error for unknown attribute access.""" cls = type(self) allowed = cls._public_attrs() if key not in allowed: @@ -38,7 +40,8 @@ def __getattr__(self, key: str): label='Allowed readable/writable', ) - def __setattr__(self, key: str, value): + def __setattr__(self, key: str, value: object) -> None: + """Set an attribute with access-control diagnostics.""" # Always allow private or special attributes without diagnostics if key.startswith('_'): object.__setattr__(self, key, value) @@ -70,20 +73,21 @@ def __setattr__(self, key: str, value): self._assign_attr(key, value) - def _assign_attr(self, key, value): + def _assign_attr(self, key: str, value: object) -> None: """Low-level assignment with parent linkage.""" object.__setattr__(self, key, value) if key != '_parent' and isinstance(value, GuardedBase): object.__setattr__(value, '_parent', self) @classmethod - def _iter_properties(cls): - """Iterate over all public properties defined in the class - hierarchy. + def _iter_properties(cls) -> Generator[tuple[str, property], None, None]: + """ + Iterate over all public properties in the class hierarchy. - Yields: - tuple[str, property]: Each (key, property) pair for public - attributes. + Yields + ------ + tuple[str, property] + Each (key, property) pair for public attributes. """ for base in cls.mro(): for key, attr in base.__dict__.items(): @@ -92,12 +96,12 @@ def _iter_properties(cls): yield key, attr @classmethod - def _public_attrs(cls): + def _public_attrs(cls) -> set[str]: """All public properties (read-only + writable).""" return {key for key, _ in cls._iter_properties()} @classmethod - def _public_readonly_attrs(cls): + def _public_readonly_attrs(cls) -> set[str]: """Public properties without a setter.""" return {key for key, prop in cls._iter_properties() if prop.fset is None} @@ -106,18 +110,19 @@ def _public_writable_attrs(cls) -> set[str]: """Public properties with a setter.""" return {key for key, prop in cls._iter_properties() if prop.fset is not None} - def _allowed_attrs(self, writable_only=False): + def _allowed_attrs(self, writable_only: bool = False) -> set[str]: cls = type(self) if writable_only: return cls._public_writable_attrs() return cls._public_attrs() @property - def _log_name(self): + def _log_name(self) -> str: return self.unique_name or type(self).__name__ @property - def unique_name(self): + def unique_name(self) -> str: + """Fallback unique name: the class name.""" return type(self).__name__ # @property @@ -131,23 +136,20 @@ def unique_name(self): @property @abstractmethod - def parameters(self): - """Return a list of parameter objects (to be implemented by - subclasses). - """ + def parameters(self) -> list: + """Return a list of parameters (implemented by subclasses).""" raise NotImplementedError @property @abstractmethod def as_cif(self) -> str: - """Return CIF representation of this object (to be implemented - by subclasses). - """ + """Return CIF representation (implemented by subclasses).""" raise NotImplementedError @staticmethod def _first_sentence(docstring: str | None) -> str: - """Extract the first paragraph from a docstring. + """ + Extract the first paragraph from a docstring. Returns text before the first blank line, with continuation lines joined into a single string. @@ -158,11 +160,14 @@ def _first_sentence(docstring: str | None) -> str: return ' '.join(line.strip() for line in first_para.splitlines()) @classmethod - def _iter_methods(cls): - """Iterate over public methods in the class hierarchy. + def _iter_methods(cls) -> Generator[tuple[str, object], None, None]: + """ + Iterate over public methods in the class hierarchy. - Yields: - tuple[str, callable]: Each (name, function) pair. + Yields + ------ + tuple[str, object] + Each (name, function) pair. """ seen: set = set() for base in cls.mro(): diff --git a/src/easydiffraction/core/identity.py b/src/easydiffraction/core/identity.py index 5848bf18..d64fce81 100644 --- a/src/easydiffraction/core/identity.py +++ b/src/easydiffraction/core/identity.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Identity helpers to build CIF-like hierarchical names. +""" +Identity helpers to build CIF-like hierarchical names. Used by containers and items to expose datablock/category/entry names without tight coupling. @@ -19,13 +20,13 @@ def __init__( datablock_entry: Callable | None = None, category_code: str | None = None, category_entry: Callable | None = None, - ): + ) -> None: self._owner = owner self._datablock_entry = datablock_entry self._category_code = category_code self._category_entry = category_entry - def _resolve_up(self, attr: str, visited=None): + def _resolve_up(self, attr: str, visited: set[int] | None = None) -> str | None: """Resolve attribute by walking up parent chain safely.""" if visited is None: visited = set() @@ -47,31 +48,31 @@ def _resolve_up(self, attr: str, visited=None): return None @property - def datablock_entry_name(self): + def datablock_entry_name(self) -> str | None: """Datablock entry name or None if not set.""" return self._resolve_up('datablock_entry') @datablock_entry_name.setter - def datablock_entry_name(self, func: callable): + def datablock_entry_name(self, func: callable) -> None: """Set callable returning datablock entry name.""" self._datablock_entry = func @property - def category_code(self): + def category_code(self) -> str | None: """Category code like 'atom_site' or 'background'.""" return self._resolve_up('category_code') @category_code.setter - def category_code(self, value: str): + def category_code(self, value: str) -> None: """Set category code value.""" self._category_code = value @property - def category_entry_name(self): + def category_entry_name(self) -> str | None: """Category entry name or None if not set.""" return self._resolve_up('category_entry') @category_entry_name.setter - def category_entry_name(self, func: callable): + def category_entry_name(self, func: callable) -> None: """Set callable returning category entry name.""" self._category_entry = func diff --git a/src/easydiffraction/core/metadata.py b/src/easydiffraction/core/metadata.py index 4a820515..318d64bb 100644 --- a/src/easydiffraction/core/metadata.py +++ b/src/easydiffraction/core/metadata.py @@ -1,12 +1,13 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Metadata dataclasses for factory-created classes. +""" +Metadata dataclasses for factory-created classes. Three frozen dataclasses describe a concrete class: -- ``TypeInfo`` — stable tag and human-readable description. -- ``Compatibility`` — experimental conditions (multiple fields). -- ``CalculatorSupport`` — which calculation engines can handle it. +- ``TypeInfo`` — stable tag and human-readable description. - +``Compatibility`` — experimental conditions (multiple fields). - +``CalculatorSupport`` — which calculation engines can handle it. """ from __future__ import annotations @@ -17,16 +18,19 @@ @dataclass(frozen=True) class TypeInfo: - """Stable identity and human-readable description for a factory- - created class. - - Attributes: - tag: Short, stable string identifier used for serialization, - user-facing selection, and factory lookup. Must be unique - within a factory's registry. Examples: ``'line-segment'``, - ``'pseudo-voigt'``, ``'cryspy'``. - description: One-line human-readable explanation. Used in - ``show_supported()`` tables and documentation. + """ + Stable identity and description for a factory-created class. + + Attributes + ---------- + tag : str + Short, stable string identifier used for serialization, + user-facing selection, and factory lookup. Must be unique within + a factory's registry. Examples: ``'line-segment'``, + ``'pseudo-voigt'``, ``'cryspy'``. + description : str, default='' + One-line human-readable explanation. Used in + ``show_supported()`` tables and documentation. """ tag: str @@ -35,7 +39,8 @@ class TypeInfo: @dataclass(frozen=True) class Compatibility: - """Experimental conditions under which a class can be used. + """ + Experimental conditions under which a class can be used. Each field is a frozenset of enum values representing the set of supported values for that axis. An empty frozenset means @@ -49,12 +54,13 @@ class Compatibility: def supports( self, - sample_form=None, - scattering_type=None, - beam_mode=None, - radiation_probe=None, + sample_form: object = None, + scattering_type: object = None, + beam_mode: object = None, + radiation_probe: object = None, ) -> bool: - """Check if this compatibility matches the given conditions. + """ + Check if this compatibility matches the given conditions. Each argument is an optional enum member. Returns ``True`` if every provided value is in the corresponding frozenset (or the @@ -62,10 +68,8 @@ def supports( Example:: - compat.supports( - scattering_type=ScatteringTypeEnum.BRAGG, - beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH, - ) + compat.supports( scattering_type=ScatteringTypeEnum.BRAGG, + beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH, ) """ for axis, value in ( ('sample_form', sample_form), @@ -83,22 +87,30 @@ def supports( @dataclass(frozen=True) class CalculatorSupport: - """Which calculation engines can handle this class. + """ + Which calculation engines can handle this class. - Attributes: - calculators: Frozenset of ``CalculatorEnum`` values. Empty - means "any calculator" (no restriction). + Attributes + ---------- + calculators : FrozenSet, default=frozenset() + Frozenset of ``CalculatorEnum`` values. Empty means "any + calculator" (no restriction). """ calculators: FrozenSet = frozenset() - def supports(self, calculator) -> bool: - """Check if a specific calculator can handle this class. + def supports(self, calculator: object) -> bool: + """ + Check if a specific calculator can handle this class. - Args: - calculator: A ``CalculatorEnum`` value. + Parameters + ---------- + calculator : object + A ``CalculatorEnum`` value. - Returns: + Returns + ------- + bool ``True`` if the calculator is in the set, or if the set is empty (meaning any calculator is accepted). """ diff --git a/src/easydiffraction/core/singleton.py b/src/easydiffraction/core/singleton.py index 10af4667..9033822a 100644 --- a/src/easydiffraction/core/singleton.py +++ b/src/easydiffraction/core/singleton.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from typing import Any @@ -16,7 +16,8 @@ class SingletonBase: - """Base class to implement Singleton pattern. + """ + Base class to implement Singleton pattern. Ensures only one shared instance of a class is ever created. Useful for managing shared state across the library. @@ -26,7 +27,7 @@ class SingletonBase: @classmethod def get(cls: Type[T]) -> T: - """Returns the shared instance, creating it if needed.""" + """Return the shared instance, creating it if needed.""" if cls._instance is None: cls._instance = cls() return cls._instance @@ -43,11 +44,12 @@ def __init__(self) -> None: self._uid_map: Dict[str, Any] = {} def get_uid_map(self) -> Dict[str, Any]: - """Returns the current UID-to-Parameter map.""" + """Return the current UID-to-Parameter map.""" return self._uid_map - def add_to_uid_map(self, parameter): - """Adds a single Parameter or Descriptor object to the UID map. + def add_to_uid_map(self, parameter: object) -> None: + """ + Add a single Parameter or Descriptor object to the UID map. Only Descriptor or Parameter instances are allowed (not Components or others). @@ -61,8 +63,9 @@ def add_to_uid_map(self, parameter): ) self._uid_map[parameter.uid] = parameter - def replace_uid(self, old_uid, new_uid): - """Replaces an existing UID key in the UID map with a new UID. + def replace_uid(self, old_uid: str, new_uid: str) -> None: + """ + Replace an existing UID key in the UID map with a new UID. Moves the associated parameter from old_uid to new_uid. Raises a KeyError if the old_uid doesn't exist. @@ -82,8 +85,8 @@ def replace_uid(self, old_uid, new_uid): # TODO: Implement changing atrr '.constrained' back to False # when removing constraints class ConstraintsHandler(SingletonBase): - """Manages user-defined parameter constraints using aliases and - expressions. + """ + Manage parameter constraints using aliases and expressions. Uses the asteval interpreter for safe evaluation of mathematical expressions. Constraints are defined as: lhs_alias = @@ -102,29 +105,27 @@ def __init__(self) -> None: # Internally parsed constraints as (lhs_alias, rhs_expr) tuples self._parsed_constraints: List[Tuple[str, str]] = [] - def set_aliases(self, aliases): - """Sets the alias map (name → parameter wrapper). + def set_aliases(self, aliases: object) -> None: + """ + Set the alias map (name → parameter wrapper). Called when user registers parameter aliases like: - alias='biso_La', param=model.atom_sites['La'].b_iso + alias='biso_La', param=model.atom_sites['La'].b_iso """ self._alias_to_param = dict(aliases.items()) - def set_constraints(self, constraints): - """Sets the constraints and triggers parsing into internal - format. + def set_constraints(self, constraints: object) -> None: + """ + Set the constraints and triggers parsing into internal format. - Called when user registers expressions like: - lhs_alias='occ_Ba', rhs_expr='1 - occ_La' + Called when user registers expressions like: lhs_alias='occ_Ba', + rhs_expr='1 - occ_La' """ self._constraints = constraints._items self._parse_constraints() def _parse_constraints(self) -> None: - """Converts raw expression input into a normalized internal list - of (lhs_alias, rhs_expr) pairs, stripping whitespace and - skipping invalid entries. - """ + """Parse raw expressions into (lhs_alias, rhs_expr) pairs.""" self._parsed_constraints = [] for expr_obj in self._constraints: @@ -136,12 +137,11 @@ def _parse_constraints(self) -> None: self._parsed_constraints.append(constraint) def apply(self) -> None: - """Evaluates constraints and applies them to dependent - parameters. + """ + Evaluate constraints and applies them to dependent parameters. - For each constraint: - - Evaluate RHS using current values of aliases - - Locate the dependent parameter by alias → uid → param + For each constraint: - Evaluate RHS using current values of + aliases - Locate the dependent parameter by alias → uid → param - Update its value and mark it as constrained """ if not self._parsed_constraints: diff --git a/src/easydiffraction/core/validation.py b/src/easydiffraction/core/validation.py index 1fd268d9..d3c411af 100644 --- a/src/easydiffraction/core/validation.py +++ b/src/easydiffraction/core/validation.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Lightweight runtime validation utilities. +""" +Lightweight runtime validation utilities. Provides DataTypes, type/content validators, and AttributeSpec used by descriptors and parameters. Only documentation was added here. @@ -23,6 +24,8 @@ # TODO: MkDocs doesn't unpack types class DataTypeHints: + """Type hint aliases for numeric, string, and boolean types.""" + Numeric = int | float | np.integer | np.floating String = str Bool = bool @@ -32,16 +35,19 @@ class DataTypeHints: class DataTypes(Enum): + """Enumeration of supported data types for descriptors.""" + NUMERIC = (int, float, np.integer, np.floating) STRING = (str,) BOOL = (bool,) ANY = (object,) # fallback for unconstrained - def __str__(self): + def __str__(self) -> str: + """Return the lowercase name of the data type.""" return self.name.lower() @property - def expected_type(self): + def expected_type(self) -> tuple: """Convenience alias for tuple of allowed Python types.""" return self.value @@ -59,7 +65,8 @@ class ValidationStage(Enum): MEMBERSHIP = auto() REGEX = auto() - def __str__(self): + def __str__(self) -> str: + """Return the lowercase name of the validation stage.""" return self.name.lower() @@ -72,8 +79,15 @@ class ValidatorBase(ABC): """Abstract base class for all validators.""" @abstractmethod - def validated(self, value, name, default=None, current=None): - """Return a validated value or fallback. + def validated( + self, + value: object, + name: str, + default: object = None, + current: object = None, + ) -> object: + """ + Return a validated value or fallback. Subclasses must implement this method. """ @@ -81,9 +95,9 @@ def validated(self, value, name, default=None, current=None): def _fallback( self, - current=None, - default=None, - ): + current: object = None, + default: object = None, + ) -> object: """Return current if set, else default.""" return current if current is not None else default @@ -94,7 +108,7 @@ def _fallback( class TypeValidator(ValidatorBase): """Ensure a value is of the expected data type.""" - def __init__(self, expected_type: DataTypes): + def __init__(self, expected_type: DataTypes) -> None: if isinstance(expected_type, DataTypes): self.expected_type = expected_type self.expected_label = str(expected_type) @@ -103,13 +117,14 @@ def __init__(self, expected_type: DataTypes): def validated( self, - value, - name, - default=None, - current=None, - allow_none=False, - ): - """Validate type and return value or fallback. + value: object, + name: str, + default: object = None, + current: object = None, + allow_none: bool = False, + ) -> object: + """ + Validate type and return value or fallback. If allow_none is True, None bypasses content checks. """ @@ -151,18 +166,18 @@ class RangeValidator(ValidatorBase): def __init__( self, *, - ge=-np.inf, - le=np.inf, - ): + ge: float = -np.inf, + le: float = np.inf, + ) -> None: self.ge, self.le = ge, le def validated( self, - value, - name, - default=None, - current=None, - ): + value: object, + name: str, + default: object = None, + current: object = None, + ) -> object: """Validate range and return value or fallback.""" if not (self.ge <= value <= self.le): Diagnostics.range_mismatch( @@ -187,22 +202,23 @@ def validated( class MembershipValidator(ValidatorBase): - """Ensure that a value is among allowed choices. + """ + Ensure that a value is among allowed choices. - `allowed` may be an iterable or a callable returning a collection. + ``allowed`` may be an iterable or a callable returning a collection. """ - def __init__(self, allowed): + def __init__(self, allowed: object) -> None: # Do not convert immediately to list — may be callable self.allowed = allowed def validated( self, - value, - name, - default=None, - current=None, - ): + value: object, + name: str, + default: object = None, + current: object = None, + ) -> object: """Validate membership and return value or fallback.""" # Dynamically evaluate allowed if callable (e.g. lambda) allowed_values = self.allowed() if callable(self.allowed) else self.allowed @@ -231,16 +247,16 @@ def validated( class RegexValidator(ValidatorBase): """Ensure that a string matches a given regular expression.""" - def __init__(self, pattern): + def __init__(self, pattern: str) -> None: self.pattern = re.compile(pattern) def validated( self, - value, - name, - default=None, - current=None, - ): + value: object, + name: str, + default: object = None, + current: object = None, + ) -> object: """Validate regex and return value or fallback.""" if not self.pattern.fullmatch(value): Diagnostics.regex_mismatch( @@ -271,11 +287,11 @@ class AttributeSpec: def __init__( self, *, - default=None, - data_type=None, - validator=None, + default: object = None, + data_type: DataTypes | None = None, + validator: ValidatorBase | None = None, allow_none: bool = False, - ): + ) -> None: self.default = default self.allow_none = allow_none self._data_type_validator = TypeValidator(data_type) if data_type else None @@ -283,11 +299,12 @@ def __init__( def validated( self, - value, - name, - current=None, - ): - """Validate through type and content validators. + value: object, + name: str, + current: object = None, + ) -> object: + """ + Validate through type and content validators. Returns validated value, possibly default or current if errors occur. None may short-circuit further checks when allowed. diff --git a/src/easydiffraction/core/variable.py b/src/easydiffraction/core/variable.py index 6b33a39e..8e06b2a9 100644 --- a/src/easydiffraction/core/variable.py +++ b/src/easydiffraction/core/variable.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -6,7 +6,6 @@ import secrets import string from typing import TYPE_CHECKING -from typing import Any import numpy as np @@ -27,18 +26,14 @@ class GenericDescriptorBase(GuardedBase): - """Base class for all parameter-like descriptors. + """ + Base class for all parameter-like descriptors. A descriptor encapsulates a typed value with validation, human-readable name/description and a globally unique identifier that is stable across the session. Concrete subclasses specialize - the expected data type and can extend the public API with - additional behavior (e.g. units). - - Attributes: - name: Local parameter name (e.g. 'a', 'b_iso'). - description: Optional human-readable description. - uid: Stable random identifier for external references. + the expected data type and can extend the public API with additional + behavior (e.g. units). """ _BOOL_SPEC_TEMPLATE = AttributeSpec( @@ -52,13 +47,18 @@ def __init__( value_spec: AttributeSpec, name: str, description: str = None, - ): - """Initialize the descriptor with validation and identity. - - Args: - value_spec: Validation specification for the value. - name: Local name of the descriptor within its category. - description: Optional human-readable description. + ) -> None: + """ + Initialize the descriptor with validation and identity. + + Parameters + ---------- + value_spec : AttributeSpec + Validation specification for the value. + name : str + Local name of the descriptor within its category. + description : str, default=None + Optional human-readable description. """ super().__init__() @@ -98,6 +98,7 @@ def __init__( self._value = default() if callable(default) else default def __str__(self) -> str: + """Return the string representation of this descriptor.""" return f'<{self.unique_name} = {self.value!r}>' @property @@ -106,11 +107,8 @@ def name(self) -> str: return self._name @property - def unique_name(self): - """Fully qualified name including datablock, category and entry - name. - """ - # 7c: Use filter(None, [...]) + def unique_name(self) -> str: + """Fully qualified name: datablock, category and entry.""" parts = [ self._identity.datablock_entry_name, self._identity.category_code, @@ -119,10 +117,8 @@ def unique_name(self): ] return '.'.join(filter(None, parts)) - def _parent_of_type(self, cls): - """Walk up the parent chain and return the first parent of type - `cls`. - """ + def _parent_of_type(self, cls: type) -> object | None: + """Traverse parents and return the first of type cls.""" obj = getattr(self, '_parent', None) visited = set() while obj is not None and id(obj) not in visited: @@ -132,19 +128,19 @@ def _parent_of_type(self, cls): obj = getattr(obj, '_parent', None) return None - def _datablock_item(self): + def _datablock_item(self) -> object | None: """Return the DatablockItem ancestor, if any.""" from easydiffraction.core.datablock import DatablockItem return self._parent_of_type(DatablockItem) @property - def value(self): + def value(self) -> object: """Current validated value.""" return self._value @value.setter - def value(self, v): + def value(self, v: object) -> None: """Set a new value after validating against the spec.""" # Do nothing if the value is unchanged if self._value == v: @@ -163,19 +159,20 @@ def value(self, v): if parent_datablock is not None: parent_datablock._need_categories_update = True - def _set_value_from_minimizer(self, v) -> None: - """Set the value from a minimizer, bypassing validation. + def _set_value_from_minimizer(self, v: object) -> None: + """ + Set the value from a minimizer, bypassing validation. - Writes ``_value`` directly — no type or range checks — but - still marks the owning :class:`DatablockItem` dirty so that + Writes ``_value`` directly — no type or range checks — but still + marks the owning :class:`DatablockItem` dirty so that ``_update_categories()`` knows work is needed. This exists because: 1. Physical-range validators (e.g. intensity ≥ 0) would reject - trial values the minimizer needs to explore. - 2. Validation overhead is measurable over thousands of - objective-function evaluations. + trial values the minimizer needs to explore. 2. Validation + overhead is measurable over thousands of objective-function + evaluations. """ self._value = v parent_datablock = self._datablock_item() @@ -183,13 +180,14 @@ def _set_value_from_minimizer(self, v) -> None: parent_datablock._need_categories_update = True @property - def description(self): + def description(self) -> str | None: """Optional human-readable description.""" return self._description @property - def parameters(self): - """Return a flat list of parameters contained by this object. + def parameters(self) -> list[GenericDescriptorBase]: + """ + Return a flat list of parameters contained by this object. For a single descriptor, it returns a one-element list with itself. Composite objects override this to flatten nested @@ -202,7 +200,7 @@ def as_cif(self) -> str: """Serialize this descriptor to a CIF-formatted string.""" return param_to_cif(self) - def from_cif(self, block, idx=0): + def from_cif(self, block: object, idx: int = 0) -> None: """Populate this parameter from a CIF block.""" param_from_cif(self, block, idx) @@ -211,11 +209,13 @@ def from_cif(self, block, idx=0): class GenericStringDescriptor(GenericDescriptorBase): + """Base descriptor that constrains values to strings.""" + _value_type = DataTypes.STRING def __init__( self, - **kwargs: Any, + **kwargs: object, ) -> None: super().__init__(**kwargs) @@ -224,18 +224,21 @@ def __init__( class GenericNumericDescriptor(GenericDescriptorBase): + """Base descriptor that constrains values to numbers.""" + _value_type = DataTypes.NUMERIC def __init__( self, *, units: str = '', - **kwargs: Any, + **kwargs: object, ) -> None: super().__init__(**kwargs) self._units: str = units def __str__(self) -> str: + """Return the string representation including units.""" s: str = super().__str__() s = s[1:-1] # strip <> if self.units: @@ -252,7 +255,8 @@ def units(self) -> str: class GenericParameter(GenericNumericDescriptor): - """Numeric descriptor extended with fitting-related attributes. + """ + Numeric descriptor extended with fitting-related attributes. Adds standard attributes used by minimizers: "free" flag, uncertainty, bounds and an optional starting value. Subclasses can @@ -261,8 +265,8 @@ class GenericParameter(GenericNumericDescriptor): def __init__( self, - **kwargs: Any, - ): + **kwargs: object, + ) -> None: super().__init__(**kwargs) # Initial validated states @@ -287,6 +291,7 @@ def __init__( UidMapHandler.get().add_to_uid_map(self) def __str__(self) -> str: + """Return string representation with uncertainty and free.""" s = GenericDescriptorBase.__str__(self) s = s[1:-1] # strip <> if self.uncertainty is not None: @@ -302,23 +307,24 @@ def _generate_uid(length: int = 16) -> str: return ''.join(secrets.choice(letters) for _ in range(length)) @property - def uid(self): + def uid(self) -> str: """Stable random identifier for this descriptor.""" return self._uid @property - def _minimizer_uid(self): + def _minimizer_uid(self) -> str: """Variant of uid that is safe for minimizer engines.""" # return self.unique_name.replace('.', '__') return self.uid @property - def constrained(self): + def constrained(self) -> bool: """Whether this parameter is part of a constraint expression.""" return self._constrained - def _set_value_constrained(self, v) -> None: - """Set the value from a constraint expression. + def _set_value_constrained(self, v: object) -> None: + """ + Set the value from a constraint expression. Validates against the spec, marks the parent datablock dirty, and flags the parameter as constrained. Used exclusively by @@ -328,50 +334,48 @@ def _set_value_constrained(self, v) -> None: self._constrained = True @property - def free(self): + def free(self) -> bool: """Whether this parameter is currently varied during fitting.""" return self._free @free.setter - def free(self, v): + def free(self, v: bool) -> None: """Set the "free" flag after validation.""" self._free = self._free_spec.validated( v, name=f'{self.unique_name}.free', current=self._free ) @property - def uncertainty(self): - """Estimated standard uncertainty of the fitted value, if - available. - """ + def uncertainty(self) -> float | None: + """Estimated standard uncertainty of the fitted value.""" return self._uncertainty @uncertainty.setter - def uncertainty(self, v): + def uncertainty(self, v: float | None) -> None: """Set the uncertainty value (must be non-negative or None).""" self._uncertainty = self._uncertainty_spec.validated( v, name=f'{self.unique_name}.uncertainty', current=self._uncertainty ) @property - def fit_min(self): + def fit_min(self) -> float: """Lower fitting bound.""" return self._fit_min @fit_min.setter - def fit_min(self, v): + def fit_min(self, v: float) -> None: """Set the lower bound for the parameter value.""" self._fit_min = self._fit_min_spec.validated( v, name=f'{self.unique_name}.fit_min', current=self._fit_min ) @property - def fit_max(self): + def fit_max(self) -> float: """Upper fitting bound.""" return self._fit_max @fit_max.setter - def fit_max(self, v): + def fit_max(self, v: float) -> None: """Set the upper bound for the parameter value.""" self._fit_max = self._fit_max_spec.validated( v, name=f'{self.unique_name}.fit_max', current=self._fit_max @@ -382,17 +386,23 @@ def fit_max(self, v): class StringDescriptor(GenericStringDescriptor): + """String descriptor bound to a CIF handler.""" + def __init__( self, *, cif_handler: CifHandler, - **kwargs: Any, + **kwargs: object, ) -> None: - """String descriptor bound to a CIF handler. - - Args: - cif_handler: Object that tracks CIF identifiers. - **kwargs: Forwarded to GenericStringDescriptor. + """ + Initialize a string descriptor bound to a CIF handler. + + Parameters + ---------- + cif_handler : CifHandler + Object that tracks CIF identifiers. + **kwargs : object + Forwarded to GenericStringDescriptor. """ super().__init__(**kwargs) self._cif_handler = cif_handler @@ -403,17 +413,23 @@ def __init__( class NumericDescriptor(GenericNumericDescriptor): + """Numeric descriptor bound to a CIF handler.""" + def __init__( self, *, cif_handler: CifHandler, - **kwargs: Any, + **kwargs: object, ) -> None: - """Numeric descriptor bound to a CIF handler. - - Args: - cif_handler: Object that tracks CIF identifiers. - **kwargs: Forwarded to GenericNumericDescriptor. + """ + Numeric descriptor bound to a CIF handler. + + Parameters + ---------- + cif_handler : CifHandler + Object that tracks CIF identifiers. + **kwargs : object + Forwarded to GenericNumericDescriptor. """ super().__init__(**kwargs) self._cif_handler = cif_handler @@ -424,17 +440,23 @@ def __init__( class Parameter(GenericParameter): + """Fittable parameter bound to a CIF handler.""" + def __init__( self, *, cif_handler: CifHandler, - **kwargs: Any, + **kwargs: object, ) -> None: - """Fittable parameter bound to a CIF handler. - - Args: - cif_handler: Object that tracks CIF identifiers. - **kwargs: Forwarded to GenericParameter. + """ + Fittable parameter bound to a CIF handler. + + Parameters + ---------- + cif_handler : CifHandler + Object that tracks CIF identifiers. + **kwargs : object + Forwarded to GenericParameter. """ super().__init__(**kwargs) self._cif_handler = cif_handler diff --git a/src/easydiffraction/crystallography/__init__.py b/src/easydiffraction/crystallography/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/crystallography/__init__.py +++ b/src/easydiffraction/crystallography/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/crystallography/crystallography.py b/src/easydiffraction/crystallography/crystallography.py index c7ff6203..bc90383f 100644 --- a/src/easydiffraction/crystallography/crystallography.py +++ b/src/easydiffraction/crystallography/crystallography.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from typing import Any @@ -21,14 +21,19 @@ def apply_cell_symmetry_constraints( cell: Dict[str, float], name_hm: str, ) -> Dict[str, float]: - """Apply symmetry constraints to unit cell parameters based on space - group. - - Args: - cell: Dictionary containing lattice parameters. - name_hm: Hermann-Mauguin symbol of the space group. - - Returns: + """ + Apply symmetry constraints to unit cell parameters. + + Parameters + ---------- + cell : Dict[str, float] + Dictionary containing lattice parameters. + name_hm : str + Hermann-Mauguin symbol of the space group. + + Returns + ------- + Dict[str, float] The cell dictionary with applied symmetry constraints. """ it_number = get_it_number_by_name_hm_short(name_hm) @@ -90,16 +95,23 @@ def apply_atom_site_symmetry_constraints( coord_code: int, wyckoff_letter: str, ) -> Dict[str, Any]: - """Apply symmetry constraints to atomic coordinates based on site - symmetry. - - Args: - atom_site: Dictionary containing atom position data. - name_hm: Hermann-Mauguin symbol of the space group. - coord_code: Coordinate system code. - wyckoff_letter: Wyckoff position letter. - - Returns: + """ + Apply symmetry constraints to atom site coordinates. + + Parameters + ---------- + atom_site : Dict[str, Any] + Dictionary containing atom position data. + name_hm : str + Hermann-Mauguin symbol of the space group. + coord_code : int + Coordinate system code. + wyckoff_letter : str + Wyckoff position letter. + + Returns + ------- + Dict[str, Any] The atom_site dictionary with applied symmetry constraints. """ it_number = get_it_number_by_name_hm_short(name_hm) diff --git a/src/easydiffraction/crystallography/space_groups.py b/src/easydiffraction/crystallography/space_groups.py index 114b3467..4047b8c5 100644 --- a/src/easydiffraction/crystallography/space_groups.py +++ b/src/easydiffraction/crystallography/space_groups.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Space group reference data. +""" +Space group reference data. Loads a gzipped, packaged pickle with crystallographic space-group information. The file is part of the distribution; user input is not @@ -10,11 +11,11 @@ import gzip import pickle # noqa: S403 - trusted internal pickle file (package data only) from pathlib import Path -from typing import Any -def _restricted_pickle_load(file_obj) -> Any: - """Load pickle data from an internal gz file (trusted boundary). +def _restricted_pickle_load(file_obj: object) -> object: + """ + Load pickle data from an internal gz file (trusted boundary). The archive lives in the package; no user-controlled input enters this function. If distribution process changes, revisit. @@ -23,7 +24,7 @@ def _restricted_pickle_load(file_obj) -> Any: return data -def _load(): +def _load() -> object: """Load space-group data from the packaged archive.""" path = Path(__file__).with_name('space_groups.pkl.gz') with gzip.open(path, 'rb') as f: diff --git a/src/easydiffraction/datablocks/__init__.py b/src/easydiffraction/datablocks/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/datablocks/__init__.py +++ b/src/easydiffraction/datablocks/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/datablocks/experiment/__init__.py b/src/easydiffraction/datablocks/experiment/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/datablocks/experiment/__init__.py +++ b/src/easydiffraction/datablocks/experiment/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/datablocks/experiment/categories/__init__.py b/src/easydiffraction/datablocks/experiment/categories/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/datablocks/experiment/categories/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/datablocks/experiment/categories/background/__init__.py b/src/easydiffraction/datablocks/experiment/categories/background/__init__.py index b7b3b47d..7ffe8f22 100644 --- a/src/easydiffraction/datablocks/experiment/categories/background/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/background/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.background.chebyshev import ( diff --git a/src/easydiffraction/datablocks/experiment/categories/background/base.py b/src/easydiffraction/datablocks/experiment/categories/background/base.py index 78cc5ef1..913cb764 100644 --- a/src/easydiffraction/datablocks/experiment/categories/background/base.py +++ b/src/easydiffraction/datablocks/experiment/categories/background/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -9,7 +9,8 @@ class BackgroundBase(CategoryCollection): - """Abstract base for background subcategories in experiments. + """ + Abstract base for background subcategories in experiments. Concrete implementations provide parameterized background models and compute background intensities on the experiment grid. diff --git a/src/easydiffraction/datablocks/experiment/categories/background/chebyshev.py b/src/easydiffraction/datablocks/experiment/categories/background/chebyshev.py index 4a6a714d..098c4268 100644 --- a/src/easydiffraction/datablocks/experiment/categories/background/chebyshev.py +++ b/src/easydiffraction/datablocks/experiment/categories/background/chebyshev.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Chebyshev polynomial background model. +""" +Chebyshev polynomial background model. Provides a collection of polynomial terms and evaluation helpers. """ @@ -34,7 +35,8 @@ class PolynomialTerm(CategoryItem): - """Chebyshev polynomial term. + """ + Chebyshev polynomial term. New public attribute names: ``order`` and ``coef`` replacing the longer ``chebyshev_order`` / ``chebyshev_coef``. Backward-compatible @@ -47,7 +49,7 @@ def __init__(self) -> None: self._id = StringDescriptor( name='id', - description='Identifier for this background polynomial term.', + description='Identifier for this background polynomial term', value_spec=AttributeSpec( default='0', # TODO: the following pattern is valid for dict key @@ -84,32 +86,54 @@ def __init__(self) -> None: # ------------------------------------------------------------------ @property - def id(self): + def id(self) -> StringDescriptor: + """ + Identifier for this background polynomial term. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._id @id.setter - def id(self, value): + def id(self, value: str) -> None: self._id.value = value @property - def order(self): + def order(self) -> NumericDescriptor: + """ + Order used in a Chebyshev polynomial background term. + + Reading this property returns the underlying + ``NumericDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._order @order.setter - def order(self, value): + def order(self, value: float) -> None: self._order.value = value @property - def coef(self): + def coef(self) -> Parameter: + """ + Coefficient used in a Chebyshev polynomial background term. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._coef @coef.setter - def coef(self, value): + def coef(self, value: float) -> None: self._coef.value = value @BackgroundFactory.register class ChebyshevPolynomialBackground(BackgroundBase): + """Chebyshev polynomial background model.""" + type_info = TypeInfo( tag='chebyshev', description='Chebyshev polynomial background', @@ -127,10 +151,10 @@ class ChebyshevPolynomialBackground(BackgroundBase): }), ) - def __init__(self): + def __init__(self) -> None: super().__init__(item_type=PolynomialTerm) - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: """Evaluate polynomial background over x data.""" del called_by_minimizer diff --git a/src/easydiffraction/datablocks/experiment/categories/background/enums.py b/src/easydiffraction/datablocks/experiment/categories/background/enums.py index 2356702a..9e78effc 100644 --- a/src/easydiffraction/datablocks/experiment/categories/background/enums.py +++ b/src/easydiffraction/datablocks/experiment/categories/background/enums.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Enumerations for background model types.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/background/factory.py b/src/easydiffraction/datablocks/experiment/categories/background/factory.py index c52b7dd7..c4d300c8 100644 --- a/src/easydiffraction/datablocks/experiment/categories/background/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/background/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Background factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/background/line_segment.py b/src/easydiffraction/datablocks/experiment/categories/background/line_segment.py index 822f6e0d..2f0a5496 100644 --- a/src/easydiffraction/datablocks/experiment/categories/background/line_segment.py +++ b/src/easydiffraction/datablocks/experiment/categories/background/line_segment.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Line-segment background model. +""" +Line-segment background model. Interpolate user-specified points to form a background curve. """ @@ -40,7 +41,7 @@ def __init__(self) -> None: self._id = StringDescriptor( name='id', - description='Identifier for this background line segment.', + description='Identifier for this background line segment', value_spec=AttributeSpec( default='0', # TODO: the following pattern is valid for dict key @@ -52,10 +53,7 @@ def __init__(self) -> None: ) self._x = NumericDescriptor( name='x', - description=( - 'X-coordinates used to create many straight-line segments ' - 'representing the background in a calculated diffractogram.' - ), + description='X-coordinates used to create many straight-line segments', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -69,10 +67,7 @@ def __init__(self) -> None: ) self._y = Parameter( name='y', # TODO: rename to intensity - description=( - 'Intensity used to create many straight-line segments ' - 'representing the background in a calculated diffractogram' - ), + description='Intensity used to create many straight-line segments', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -93,32 +88,54 @@ def __init__(self) -> None: # ------------------------------------------------------------------ @property - def id(self): + def id(self) -> StringDescriptor: + """ + Identifier for this background line segment. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._id @id.setter - def id(self, value): + def id(self, value: str) -> None: self._id.value = value @property - def x(self): + def x(self) -> NumericDescriptor: + """ + X-coordinates used to create many straight-line segments. + + Reading this property returns the underlying + ``NumericDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._x @x.setter - def x(self, value): + def x(self, value: float) -> None: self._x.value = value @property - def y(self): + def y(self) -> Parameter: + """ + Intensity used to create many straight-line segments. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._y @y.setter - def y(self, value): + def y(self, value: float) -> None: self._y.value = value @BackgroundFactory.register class LineSegmentBackground(BackgroundBase): + """Linear-interpolation background between user-defined points.""" + type_info = TypeInfo( tag='line-segment', description='Linear interpolation between points', @@ -130,10 +147,10 @@ class LineSegmentBackground(BackgroundBase): calculators=frozenset({CalculatorEnum.CRYSPY, CalculatorEnum.CRYSFML}), ) - def __init__(self): + def __init__(self) -> None: super().__init__(item_type=LineSegment) - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: """Interpolate background points over x data.""" del called_by_minimizer diff --git a/src/easydiffraction/datablocks/experiment/categories/data/__init__.py b/src/easydiffraction/datablocks/experiment/categories/data/__init__.py index c228ecd8..3599f3b5 100644 --- a/src/easydiffraction/datablocks/experiment/categories/data/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/data/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.data.bragg_pd import PdCwlData diff --git a/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py b/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py index 73402d30..883f7f80 100644 --- a/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py +++ b/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -29,12 +29,12 @@ class PdDataPointBaseMixin: """Single base data point mixin for powder diffraction data.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._point_id = StringDescriptor( name='point_id', - description='Identifier for this data point in the dataset.', + description='Identifier for this data point in the dataset', value_spec=AttributeSpec( default='0', # TODO: the following pattern is valid for dict key @@ -50,7 +50,7 @@ def __init__(self): ) self._d_spacing = NumericDescriptor( name='d_spacing', - description='d-spacing value corresponding to this data point.', + description='d-spacing value corresponding to this data point', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -59,7 +59,7 @@ def __init__(self): ) self._intensity_meas = NumericDescriptor( name='intensity_meas', - description='Intensity recorded at each measurement point as a function of angle/time', + description='Intensity recorded at each measurement point (angle/time)', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -73,7 +73,7 @@ def __init__(self): ) self._intensity_meas_su = NumericDescriptor( name='intensity_meas_su', - description='Standard uncertainty of the measured intensity at this data point.', + description='Standard uncertainty of the measured intensity at this point', value_spec=AttributeSpec( default=1.0, validator=RangeValidator(ge=0), @@ -87,7 +87,7 @@ def __init__(self): ) self._intensity_calc = NumericDescriptor( name='intensity_calc', - description='Intensity value for a computed diffractogram at this data point.', + description='Intensity of a computed diffractogram at this point', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -96,7 +96,7 @@ def __init__(self): ) self._intensity_bkg = NumericDescriptor( name='intensity_bkg', - description='Intensity value for a computed background at this data point.', + description='Intensity of a computed background at this point', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -105,7 +105,7 @@ def __init__(self): ) self._calc_status = StringDescriptor( name='calc_status', - description='Status code of the data point in the calculation process.', + description='Status code of the data point in the calculation process', value_spec=AttributeSpec( default='incl', # TODO: Make Enum validator=MembershipValidator(allowed=['incl', 'excl']), @@ -123,39 +123,79 @@ def __init__(self): @property def point_id(self) -> StringDescriptor: + """ + Identifier for this data point in the dataset. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._point_id @property def d_spacing(self) -> NumericDescriptor: + """ + d-spacing value corresponding to this data point. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._d_spacing @property def intensity_meas(self) -> NumericDescriptor: + """ + Intensity recorded at each measurement point (angle/time). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._intensity_meas @property def intensity_meas_su(self) -> NumericDescriptor: + """ + Standard uncertainty of the measured intensity at this point. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._intensity_meas_su @property def intensity_calc(self) -> NumericDescriptor: + """ + Intensity of a computed diffractogram at this point. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._intensity_calc @property def intensity_bkg(self) -> NumericDescriptor: + """ + Intensity of a computed background at this point. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._intensity_bkg @property def calc_status(self) -> StringDescriptor: + """ + Status code of the data point in the calculation process. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._calc_status class PdCwlDataPointMixin: - """Mixin for powder diffraction data points with constant - wavelength. - """ + """Mixin for CWL powder diffraction data points.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._two_theta = NumericDescriptor( @@ -179,14 +219,20 @@ def __init__(self): # ------------------------------------------------------------------ @property - def two_theta(self): + def two_theta(self) -> NumericDescriptor: + """ + Measured 2θ diffraction angle (deg). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._two_theta class PdTofDataPointMixin: """Mixin for powder diffraction data points with time-of-flight.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._time_of_flight = NumericDescriptor( @@ -205,7 +251,13 @@ def __init__(self): # ------------------------------------------------------------------ @property - def time_of_flight(self): + def time_of_flight(self) -> NumericDescriptor: + """ + Measured time for time-of-flight neutron measurement (µs). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._time_of_flight @@ -222,9 +274,7 @@ class PdCwlDataPoint( # But also says, that in fact, it is just for consistency. And both # orders work. ): - """Powder diffraction data point for constant-wavelength - experiments. - """ + """Powder diffraction data point for CWL experiments.""" def __init__(self) -> None: super().__init__() @@ -246,6 +296,8 @@ def __init__(self) -> None: class PdDataBase(CategoryCollection): + """Base class for powder diffraction data collections.""" + # TODO: ??? # Redefine update priority to ensure data updated after other @@ -260,42 +312,40 @@ class PdDataBase(CategoryCollection): # Should be set only once - def _set_point_id(self, values) -> None: - """Helper method to set point IDs.""" + def _set_point_id(self, values: object) -> None: + """Set point IDs.""" for p, v in zip(self._items, values, strict=True): p.point_id._value = v - def _set_intensity_meas(self, values) -> None: - """Helper method to set measured intensity.""" + def _set_intensity_meas(self, values: object) -> None: + """Set measured intensity.""" for p, v in zip(self._items, values, strict=True): p.intensity_meas._value = v - def _set_intensity_meas_su(self, values) -> None: - """Helper method to set standard uncertainty of measured - intensity. - """ + def _set_intensity_meas_su(self, values: object) -> None: + """Set standard uncertainty of measured intensity values.""" for p, v in zip(self._items, values, strict=True): p.intensity_meas_su._value = v # Can be set multiple times - def _set_d_spacing(self, values) -> None: - """Helper method to set d-spacing values.""" + def _set_d_spacing(self, values: object) -> None: + """Set d-spacing values.""" for p, v in zip(self._calc_items, values, strict=True): p.d_spacing._value = v - def _set_intensity_calc(self, values) -> None: - """Helper method to set calculated intensity.""" + def _set_intensity_calc(self, values: object) -> None: + """Set calculated intensity.""" for p, v in zip(self._calc_items, values, strict=True): p.intensity_calc._value = v - def _set_intensity_bkg(self, values) -> None: - """Helper method to set background intensity.""" + def _set_intensity_bkg(self, values: object) -> None: + """Set background intensity.""" for p, v in zip(self._calc_items, values, strict=True): p.intensity_bkg._value = v - def _set_calc_status(self, values) -> None: - """Helper method to set refinement status.""" + def _set_calc_status(self, values: object) -> None: + """Set refinement status.""" for p, v in zip(self._items, values, strict=True): if v: p.calc_status._value = 'incl' @@ -311,13 +361,13 @@ def _calc_mask(self) -> np.ndarray: return self.calc_status == 'incl' @property - def _calc_items(self): + def _calc_items(self) -> list: """Get only the items included in calculations.""" return [item for item, mask in zip(self._items, self._calc_mask, strict=False) if mask] # Misc - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: experiment = self._parent experiments = experiment._parent project = experiments._parent @@ -353,6 +403,7 @@ def _update(self, called_by_minimizer=False): @property def calc_status(self) -> np.ndarray: + """Refinement-status flags for each data point as an array.""" return np.fromiter( (p.calc_status.value for p in self._items), dtype=object, # TODO: needed? DataTypes.NUMERIC? @@ -360,6 +411,7 @@ def calc_status(self) -> np.ndarray: @property def d_spacing(self) -> np.ndarray: + """D-spacing values for active (non-excluded) data points.""" return np.fromiter( (p.d_spacing.value for p in self._calc_items), dtype=float, # TODO: needed? DataTypes.NUMERIC? @@ -367,6 +419,7 @@ def d_spacing(self) -> np.ndarray: @property def intensity_meas(self) -> np.ndarray: + """Measured intensities for active data points.""" return np.fromiter( (p.intensity_meas.value for p in self._calc_items), dtype=float, # TODO: needed? DataTypes.NUMERIC? @@ -374,6 +427,12 @@ def intensity_meas(self) -> np.ndarray: @property def intensity_meas_su(self) -> np.ndarray: + """ + Standard uncertainties of the measured intensities. + + Values smaller than 0.0001 are replaced with 1.0 to prevent + fitting failures. + """ # TODO: The following is a temporary workaround to handle zero # or near-zero uncertainties in the data, when dats is loaded # from CIF files. This is necessary because zero uncertainties @@ -396,6 +455,7 @@ def intensity_meas_su(self) -> np.ndarray: @property def intensity_calc(self) -> np.ndarray: + """Calculated intensities for active data points.""" return np.fromiter( (p.intensity_calc.value for p in self._calc_items), dtype=float, # TODO: needed? DataTypes.NUMERIC? @@ -403,6 +463,7 @@ def intensity_calc(self) -> np.ndarray: @property def intensity_bkg(self) -> np.ndarray: + """Background intensities for active data points.""" return np.fromiter( (p.intensity_bkg.value for p in self._calc_items), dtype=float, # TODO: needed? DataTypes.NUMERIC? @@ -411,6 +472,8 @@ def intensity_bkg(self) -> np.ndarray: @DataFactory.register class PdCwlData(PdDataBase): + """Bragg powder CWL data collection.""" + # TODO: ??? # _description: str = 'Powder diffraction data points for # constant-wavelength experiments.' @@ -424,7 +487,7 @@ class PdCwlData(PdDataBase): calculators=frozenset({CalculatorEnum.CRYSPY}), ) - def __init__(self): + def __init__(self) -> None: super().__init__(item_type=PdCwlDataPoint) ################# @@ -433,8 +496,8 @@ def __init__(self): # Should be set only once - def _create_items_set_xcoord_and_id(self, values) -> None: - """Helper method to set 2θ values.""" + def _create_items_set_xcoord_and_id(self, values: object) -> None: + """Set 2θ values.""" # TODO: split into multiple methods # Create items @@ -449,7 +512,7 @@ def _create_items_set_xcoord_and_id(self, values) -> None: # Misc - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: super()._update(called_by_minimizer) experiment = self._parent @@ -465,9 +528,7 @@ def _update(self, called_by_minimizer=False): @property def two_theta(self) -> np.ndarray: - """Get the 2θ values for data points included in - calculations. - """ + """Get 2θ values for data points included in calculations.""" return np.fromiter( (p.two_theta.value for p in self._calc_items), dtype=float, # TODO: needed? DataTypes.NUMERIC? @@ -489,6 +550,8 @@ def unfiltered_x(self) -> np.ndarray: @DataFactory.register class PdTofData(PdDataBase): + """Bragg powder TOF data collection.""" + type_info = TypeInfo(tag='bragg-pd-tof', description='Bragg powder TOF data') compatibility = Compatibility( sample_form=frozenset({SampleFormEnum.POWDER}), @@ -499,7 +562,7 @@ class PdTofData(PdDataBase): calculators=frozenset({CalculatorEnum.CRYSPY, CalculatorEnum.CRYSFML}), ) - def __init__(self): + def __init__(self) -> None: super().__init__(item_type=PdTofDataPoint) ################# @@ -508,8 +571,8 @@ def __init__(self): # Should be set only once - def _create_items_set_xcoord_and_id(self, values) -> None: - """Helper method to set time-of-flight values.""" + def _create_items_set_xcoord_and_id(self, values: object) -> None: + """Set time-of-flight values.""" # TODO: split into multiple methods # Create items @@ -524,7 +587,7 @@ def _create_items_set_xcoord_and_id(self, values) -> None: # Misc - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: super()._update(called_by_minimizer) experiment = self._parent @@ -542,9 +605,7 @@ def _update(self, called_by_minimizer=False): @property def time_of_flight(self) -> np.ndarray: - """Get the TOF values for data points included in - calculations. - """ + """Get TOF values for data points included in calculations.""" return np.fromiter( (p.time_of_flight.value for p in self._calc_items), dtype=float, # TODO: needed? DataTypes.NUMERIC? diff --git a/src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py b/src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py index 39552b63..d19eb75f 100644 --- a/src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py +++ b/src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -26,16 +26,14 @@ class Refln(CategoryItem): - """Single reflection for single crystal diffraction data - category. - """ + """Single reflection for single-crystal diffraction data.""" def __init__(self) -> None: super().__init__() self._id = StringDescriptor( name='id', - description='Identifier of the reflection.', + description='Identifier of the reflection', value_spec=AttributeSpec( default='0', # TODO: the following pattern is valid for dict key @@ -47,7 +45,7 @@ def __init__(self) -> None: ) self._d_spacing = NumericDescriptor( name='d_spacing', - description='The distance between lattice planes in the crystal for this reflection.', + description='Distance between lattice planes for this reflection', units='Å', value_spec=AttributeSpec( default=0.0, @@ -57,7 +55,7 @@ def __init__(self) -> None: ) self._sin_theta_over_lambda = NumericDescriptor( name='sin_theta_over_lambda', - description='The sin(θ)/λ value for this reflection.', + description='The sin(θ)/λ value for this reflection', units='Å⁻¹', value_spec=AttributeSpec( default=0.0, @@ -67,7 +65,7 @@ def __init__(self) -> None: ) self._index_h = NumericDescriptor( name='index_h', - description='Miller index h of a measured reflection.', + description='Miller index h of a measured reflection', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -76,7 +74,7 @@ def __init__(self) -> None: ) self._index_k = NumericDescriptor( name='index_k', - description='Miller index k of a measured reflection.', + description='Miller index k of a measured reflection', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -85,7 +83,7 @@ def __init__(self) -> None: ) self._index_l = NumericDescriptor( name='index_l', - description='Miller index l of a measured reflection.', + description='Miller index l of a measured reflection', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -112,7 +110,7 @@ def __init__(self) -> None: ) self._intensity_calc = NumericDescriptor( name='intensity_calc', - description='The intensity of the reflection calculated from the atom site data.', + description='Intensity of the reflection calculated from atom site data', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -121,7 +119,7 @@ def __init__(self) -> None: ) self._wavelength = NumericDescriptor( name='wavelength', - description='The mean wavelength of radiation used to measure this reflection.', + description='Mean wavelength of radiation for this reflection', units='Å', value_spec=AttributeSpec( default=0.0, @@ -139,42 +137,102 @@ def __init__(self) -> None: @property def id(self) -> StringDescriptor: + """ + Identifier of the reflection. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._id @property def d_spacing(self) -> NumericDescriptor: + """ + Distance between lattice planes for this reflection (Å). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._d_spacing @property def sin_theta_over_lambda(self) -> NumericDescriptor: + """ + The sin(θ)/λ value for this reflection (Å⁻¹). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._sin_theta_over_lambda @property def index_h(self) -> NumericDescriptor: + """ + Miller index h of a measured reflection. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._index_h @property def index_k(self) -> NumericDescriptor: + """ + Miller index k of a measured reflection. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._index_k @property def index_l(self) -> NumericDescriptor: + """ + Miller index l of a measured reflection. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._index_l @property def intensity_meas(self) -> NumericDescriptor: + """ + The intensity of the reflection derived from the measurements. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._intensity_meas @property def intensity_meas_su(self) -> NumericDescriptor: + """ + Standard uncertainty of the measured intensity. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._intensity_meas_su @property def intensity_calc(self) -> NumericDescriptor: + """ + Intensity of the reflection calculated from atom site data. + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._intensity_calc @property def wavelength(self) -> NumericDescriptor: + """ + Mean wavelength of radiation for this reflection (Å). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._wavelength @@ -194,7 +252,7 @@ class ReflnData(CategoryCollection): _update_priority = 100 - def __init__(self): + def __init__(self) -> None: super().__init__(item_type=Refln) ################# @@ -203,8 +261,13 @@ def __init__(self): # Should be set only once - def _create_items_set_hkl_and_id(self, indices_h, indices_k, indices_l) -> None: - """Helper method to set Miller indices.""" + def _create_items_set_hkl_and_id( + self, + indices_h: object, + indices_k: object, + indices_l: object, + ) -> None: + """Set Miller indices.""" # TODO: split into multiple methods # Create items @@ -221,48 +284,46 @@ def _create_items_set_hkl_and_id(self, indices_h, indices_k, indices_l) -> None: # Set reflection IDs self._set_id([str(i + 1) for i in range(indices_h.size)]) - def _set_id(self, values) -> None: - """Helper method to set reflection IDs.""" + def _set_id(self, values: object) -> None: + """Set reflection IDs.""" for p, v in zip(self._items, values, strict=True): p.id._value = v - def _set_intensity_meas(self, values) -> None: - """Helper method to set measured intensity.""" + def _set_intensity_meas(self, values: object) -> None: + """Set measured intensity.""" for p, v in zip(self._items, values, strict=True): p.intensity_meas._value = v - def _set_intensity_meas_su(self, values) -> None: - """Helper method to set standard uncertainty of measured - intensity. - """ + def _set_intensity_meas_su(self, values: object) -> None: + """Set standard uncertainty of measured intensity values.""" for p, v in zip(self._items, values, strict=True): p.intensity_meas_su._value = v - def _set_wavelength(self, values) -> None: - """Helper method to set wavelength.""" + def _set_wavelength(self, values: object) -> None: + """Set wavelength.""" for p, v in zip(self._items, values, strict=True): p.wavelength._value = v # Can be set multiple times - def _set_d_spacing(self, values) -> None: - """Helper method to set d-spacing values.""" + def _set_d_spacing(self, values: object) -> None: + """Set d-spacing values.""" for p, v in zip(self._items, values, strict=True): p.d_spacing._value = v - def _set_sin_theta_over_lambda(self, values) -> None: - """Helper method to set sin(theta)/lambda values.""" + def _set_sin_theta_over_lambda(self, values: object) -> None: + """Set sin(theta)/lambda values.""" for p, v in zip(self._items, values, strict=True): p.sin_theta_over_lambda._value = v - def _set_intensity_calc(self, values) -> None: - """Helper method to set calculated intensity.""" + def _set_intensity_calc(self, values: object) -> None: + """Set calculated intensity.""" for p, v in zip(self._items, values, strict=True): p.intensity_calc._value = v # Misc - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: experiment = self._parent experiments = experiment._parent project = experiments._parent @@ -302,63 +363,72 @@ def _update(self, called_by_minimizer=False): @property def d_spacing(self) -> np.ndarray: + """D-spacing values for all reflection data points.""" return np.fromiter( (p.d_spacing.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def sin_theta_over_lambda(self) -> np.ndarray: + """sinθ/λ values for all reflection data points.""" return np.fromiter( (p.sin_theta_over_lambda.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def index_h(self) -> np.ndarray: + """Miller h indices for all reflection data points.""" return np.fromiter( (p.index_h.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def index_k(self) -> np.ndarray: + """Miller k indices for all reflection data points.""" return np.fromiter( (p.index_k.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def index_l(self) -> np.ndarray: + """Miller l indices for all reflection data points.""" return np.fromiter( (p.index_l.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def intensity_meas(self) -> np.ndarray: + """Measured structure-factor intensities for all reflections.""" return np.fromiter( (p.intensity_meas.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def intensity_meas_su(self) -> np.ndarray: + """Standard uncertainties of the measured intensities.""" return np.fromiter( (p.intensity_meas_su.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def intensity_calc(self) -> np.ndarray: + """Calculated intensities for all reflections.""" return np.fromiter( (p.intensity_calc.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def wavelength(self) -> np.ndarray: + """Wavelengths associated with each reflection.""" return np.fromiter( (p.wavelength.value for p in self._items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) diff --git a/src/easydiffraction/datablocks/experiment/categories/data/factory.py b/src/easydiffraction/datablocks/experiment/categories/data/factory.py index 1ef25c0b..d8cdcf12 100644 --- a/src/easydiffraction/datablocks/experiment/categories/data/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/data/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Data collection factory — delegates to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py b/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py index 0aa63be5..c8d0aa9d 100644 --- a/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py +++ b/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Data categories for total scattering (PDF) experiments.""" @@ -26,7 +26,8 @@ class TotalDataPoint(CategoryItem): - """Total scattering (PDF) data point in r-space (real space). + """ + Total scattering (PDF) data point in r-space (real space). Note: PDF data is always in r-space regardless of whether the original measurement was CWL or TOF. @@ -37,7 +38,7 @@ def __init__(self) -> None: self._point_id = StringDescriptor( name='point_id', - description='Identifier for this data point in the dataset.', + description='Identifier for this data point in the dataset', value_spec=AttributeSpec( default='0', validator=RegexValidator(pattern=r'^[A-Za-z0-9_]*$'), @@ -50,7 +51,7 @@ def __init__(self) -> None: ) self._r = NumericDescriptor( name='r', - description='Interatomic distance in real space.', + description='Interatomic distance in real space', units='Å', value_spec=AttributeSpec( default=0.0, @@ -64,7 +65,7 @@ def __init__(self) -> None: ) self._g_r_meas = NumericDescriptor( name='g_r_meas', - description='Measured pair distribution function G(r).', + description='Measured pair distribution function G(r)', value_spec=AttributeSpec( default=0.0, ), @@ -76,7 +77,7 @@ def __init__(self) -> None: ) self._g_r_meas_su = NumericDescriptor( name='g_r_meas_su', - description='Standard uncertainty of measured G(r).', + description='Standard uncertainty of measured G(r)', value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -89,7 +90,7 @@ def __init__(self) -> None: ) self._g_r_calc = NumericDescriptor( name='g_r_calc', - description='Calculated pair distribution function G(r).', + description='Calculated pair distribution function G(r)', value_spec=AttributeSpec( default=0.0, ), @@ -101,7 +102,7 @@ def __init__(self) -> None: ) self._calc_status = StringDescriptor( name='calc_status', - description='Status code of the data point in calculation.', + description='Status code of the data point in calculation', value_spec=AttributeSpec( default='incl', validator=MembershipValidator(allowed=['incl', 'excl']), @@ -122,26 +123,62 @@ def __init__(self) -> None: @property def point_id(self) -> StringDescriptor: + """ + Identifier for this data point in the dataset. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._point_id @property def r(self) -> NumericDescriptor: + """ + Interatomic distance in real space (Å). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._r @property def g_r_meas(self) -> NumericDescriptor: + """ + Measured pair distribution function G(r). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._g_r_meas @property def g_r_meas_su(self) -> NumericDescriptor: + """ + Standard uncertainty of measured G(r). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._g_r_meas_su @property def g_r_calc(self) -> NumericDescriptor: + """ + Calculated pair distribution function G(r). + + Reading this property returns the underlying + ``NumericDescriptor`` object. + """ return self._g_r_calc @property def calc_status(self) -> StringDescriptor: + """ + Status code of the data point in calculation. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._calc_status @@ -156,32 +193,30 @@ class TotalDataBase(CategoryCollection): # Should be set only once - def _set_point_id(self, values) -> None: - """Helper method to set point IDs.""" + def _set_point_id(self, values: object) -> None: + """Set point IDs.""" for p, v in zip(self._items, values, strict=True): p.point_id._value = v - def _set_g_r_meas(self, values) -> None: - """Helper method to set measured G(r).""" + def _set_g_r_meas(self, values: object) -> None: + """Set measured G(r).""" for p, v in zip(self._items, values, strict=True): p.g_r_meas._value = v - def _set_g_r_meas_su(self, values) -> None: - """Helper method to set standard uncertainty of measured - G(r). - """ + def _set_g_r_meas_su(self, values: object) -> None: + """Set standard uncertainty of measured G(r) values.""" for p, v in zip(self._items, values, strict=True): p.g_r_meas_su._value = v # Can be set multiple times - def _set_g_r_calc(self, values) -> None: - """Helper method to set calculated G(r).""" + def _set_g_r_calc(self, values: object) -> None: + """Set calculated G(r).""" for p, v in zip(self._calc_items, values, strict=True): p.g_r_calc._value = v - def _set_calc_status(self, values) -> None: - """Helper method to set calculation status.""" + def _set_calc_status(self, values: object) -> None: + """Set calculation status.""" for p, v in zip(self._items, values, strict=True): if v: p.calc_status._value = 'incl' @@ -197,13 +232,13 @@ def _calc_mask(self) -> np.ndarray: return self.calc_status == 'incl' @property - def _calc_items(self): + def _calc_items(self) -> list: """Get only the items included in calculations.""" return [item for item, mask in zip(self._items, self._calc_mask, strict=False) if mask] # Misc - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: experiment = self._parent experiments = experiment._parent project = experiments._parent @@ -239,30 +274,34 @@ def _update(self, called_by_minimizer=False): @property def calc_status(self) -> np.ndarray: + """Refinement-status flags for each data point as an array.""" return np.fromiter( (p.calc_status.value for p in self._items), - dtype=object, # TODO: needed? DataTypes.NUMERIC? + dtype=object, ) @property def intensity_meas(self) -> np.ndarray: + """Measured G(r) values for active data points.""" return np.fromiter( (p.g_r_meas.value for p in self._calc_items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def intensity_meas_su(self) -> np.ndarray: + """Standard uncertainties of the measured G(r) values.""" return np.fromiter( (p.g_r_meas_su.value for p in self._calc_items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property def intensity_calc(self) -> np.ndarray: + """Calculated G(r) values for active data points.""" return np.fromiter( (p.g_r_calc.value for p in self._calc_items), - dtype=float, # TODO: needed? DataTypes.NUMERIC? + dtype=float, ) @property @@ -273,13 +312,17 @@ def intensity_bkg(self) -> np.ndarray: @DataFactory.register class TotalData(TotalDataBase): - """Total scattering (PDF) data collection in r-space. + """ + Total scattering (PDF) data collection in r-space. - Note: Works for both CWL and TOF measurements as PDF data - is always transformed to r-space. + Note: Works for both CWL and TOF measurements as PDF data is always + transformed to r-space. """ - type_info = TypeInfo(tag='total-pd', description='Total scattering (PDF) data') + type_info = TypeInfo( + tag='total-pd', + description='Total scattering (PDF) data', + ) compatibility = Compatibility( sample_form=frozenset({SampleFormEnum.POWDER}), scattering_type=frozenset({ScatteringTypeEnum.TOTAL}), @@ -289,7 +332,7 @@ class TotalData(TotalDataBase): calculators=frozenset({CalculatorEnum.PDFFIT}), ) - def __init__(self): + def __init__(self) -> None: super().__init__(item_type=TotalDataPoint) ################# @@ -298,8 +341,8 @@ def __init__(self): # Should be set only once - def _create_items_set_xcoord_and_id(self, values) -> None: - """Helper method to set r values.""" + def _create_items_set_xcoord_and_id(self, values: object) -> None: + """Set r values.""" # TODO: split into multiple methods # Create items diff --git a/src/easydiffraction/datablocks/experiment/categories/excluded_regions/__init__.py b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/__init__.py index 3356f4cf..8d232629 100644 --- a/src/easydiffraction/datablocks/experiment/categories/excluded_regions/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.excluded_regions.default import ( diff --git a/src/easydiffraction/datablocks/experiment/categories/excluded_regions/default.py b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/default.py index 2696f25c..fdf130c2 100644 --- a/src/easydiffraction/datablocks/experiment/categories/excluded_regions/default.py +++ b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/default.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Exclude ranges of x from fitting/plotting (masked regions).""" @@ -29,13 +29,13 @@ class ExcludedRegion(CategoryItem): """Closed interval [start, end] to be excluded.""" - def __init__(self): + def __init__(self) -> None: super().__init__() # TODO: Add point_id as for the background self._id = StringDescriptor( name='id', - description='Identifier for this excluded region.', + description='Identifier for this excluded region', value_spec=AttributeSpec( default='0', # TODO: the following pattern is valid for dict key @@ -71,33 +71,55 @@ def __init__(self): # ------------------------------------------------------------------ @property - def id(self): + def id(self) -> StringDescriptor: + """ + Identifier for this excluded region. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._id @id.setter - def id(self, value): + def id(self, value: str) -> None: self._id.value = value @property def start(self) -> NumericDescriptor: + """ + Start of the excluded region. + + Reading this property returns the underlying + ``NumericDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._start @start.setter - def start(self, value: float): + def start(self, value: float) -> None: self._start.value = value @property def end(self) -> NumericDescriptor: + """ + End of the excluded region. + + Reading this property returns the underlying + ``NumericDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._end @end.setter - def end(self, value: float): + def end(self, value: float) -> None: self._end.value = value @ExcludedRegionsFactory.register class ExcludedRegions(CategoryCollection): - """Collection of ExcludedRegion instances. + """ + Collection of ExcludedRegion instances. Excluded regions define closed intervals [start, end] on the x-axis that are to be excluded from calculations and, as a result, from @@ -112,10 +134,10 @@ class ExcludedRegions(CategoryCollection): sample_form=frozenset({SampleFormEnum.POWDER}), ) - def __init__(self): + def __init__(self) -> None: super().__init__(item_type=ExcludedRegion) - def _update(self, called_by_minimizer=False): + def _update(self, called_by_minimizer: bool = False) -> None: del called_by_minimizer data = self._parent.data diff --git a/src/easydiffraction/datablocks/experiment/categories/excluded_regions/factory.py b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/factory.py index 789e25e7..e12fb0c0 100644 --- a/src/easydiffraction/datablocks/experiment/categories/excluded_regions/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/excluded_regions/factory.py @@ -1,6 +1,8 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Excluded-regions factory — delegates entirely to ``FactoryBase``.""" +""" +Excluded-regions factory — delegates entirely to ``FactoryBase``. +""" from __future__ import annotations diff --git a/src/easydiffraction/datablocks/experiment/categories/experiment_type/__init__.py b/src/easydiffraction/datablocks/experiment/categories/experiment_type/__init__.py index 63e6bb0b..197b5510 100644 --- a/src/easydiffraction/datablocks/experiment/categories/experiment_type/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/experiment_type/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.experiment_type.default import ExperimentType diff --git a/src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py b/src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py index 5221e444..1b9d3811 100644 --- a/src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py +++ b/src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py @@ -1,9 +1,10 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Experiment type descriptor (form, beam, probe, scattering). +""" +Experiment type descriptor (form, beam, probe, scattering). -This lightweight container stores the categorical attributes defining -an experiment configuration and handles CIF serialization via +This lightweight container stores the categorical attributes defining an +experiment configuration and handles CIF serialization via ``CifHandler``. """ @@ -26,27 +27,19 @@ @ExperimentTypeFactory.register class ExperimentType(CategoryItem): - """Container of categorical attributes defining experiment flavor. - - Args: - sample_form: Powder or Single crystal. - beam_mode: Constant wavelength (CW) or time-of-flight (TOF). - radiation_probe: Neutrons or X-rays. - scattering_type: Bragg or Total. - """ + """Container of attributes defining the experiment type.""" type_info = TypeInfo( tag='default', description='Experiment type descriptor', ) - def __init__(self): + def __init__(self) -> None: super().__init__() self._sample_form = StringDescriptor( name='sample_form', - description='Specifies whether the diffraction data corresponds to ' - 'powder diffraction or single crystal diffraction', + description='Powder diffraction or single crystal diffraction', value_spec=AttributeSpec( default=SampleFormEnum.default().value, validator=MembershipValidator(allowed=[member.value for member in SampleFormEnum]), @@ -56,8 +49,7 @@ def __init__(self): self._beam_mode = StringDescriptor( name='beam_mode', - description='Defines whether the measurement is performed with a ' - 'constant wavelength (CW) or time-of-flight (TOF) method', + description='Constant wavelength (CW) or time-of-flight (TOF) measurement', value_spec=AttributeSpec( default=BeamModeEnum.default().value, validator=MembershipValidator(allowed=[member.value for member in BeamModeEnum]), @@ -66,7 +58,7 @@ def __init__(self): ) self._radiation_probe = StringDescriptor( name='radiation_probe', - description='Specifies whether the measurement uses neutrons or X-rays', + description='Neutron or X-ray diffraction measurement', value_spec=AttributeSpec( default=RadiationProbeEnum.default().value, validator=MembershipValidator( @@ -77,9 +69,7 @@ def __init__(self): ) self._scattering_type = StringDescriptor( name='scattering_type', - description='Specifies whether the experiment uses Bragg scattering ' - '(for conventional structure refinement) or total scattering ' - '(for pair distribution function analysis - PDF)', + description='Conventional Bragg diffraction or total scattering (PDF)', value_spec=AttributeSpec( default=ScatteringTypeEnum.default().value, validator=MembershipValidator( @@ -112,17 +102,41 @@ def _set_scattering_type(self, value: str) -> None: # ------------------------------------------------------------------ @property - def sample_form(self): + def sample_form(self) -> StringDescriptor: + """ + Powder diffraction or single crystal diffraction. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._sample_form @property - def beam_mode(self): + def beam_mode(self) -> StringDescriptor: + """ + Constant wavelength (CW) or time-of-flight (TOF) measurement. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._beam_mode @property - def radiation_probe(self): + def radiation_probe(self) -> StringDescriptor: + """ + Neutron or X-ray diffraction measurement. + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._radiation_probe @property - def scattering_type(self): + def scattering_type(self) -> StringDescriptor: + """ + Conventional Bragg diffraction or total scattering (PDF). + + Reading this property returns the underlying + ``StringDescriptor`` object. + """ return self._scattering_type diff --git a/src/easydiffraction/datablocks/experiment/categories/experiment_type/factory.py b/src/easydiffraction/datablocks/experiment/categories/experiment_type/factory.py index bf78fb53..05f0d2d9 100644 --- a/src/easydiffraction/datablocks/experiment/categories/experiment_type/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/experiment_type/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Experiment-type factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py b/src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py index f3d62fad..3e6fa39a 100644 --- a/src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.extinction.shelx import ShelxExtinction diff --git a/src/easydiffraction/datablocks/experiment/categories/extinction/factory.py b/src/easydiffraction/datablocks/experiment/categories/extinction/factory.py index fbeb32e7..4e4bd9ed 100644 --- a/src/easydiffraction/datablocks/experiment/categories/extinction/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/extinction/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Extinction factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py b/src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py index 67dfaf94..dd736a1a 100644 --- a/src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py +++ b/src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Shelx-style isotropic extinction correction.""" @@ -17,9 +17,7 @@ @ExtinctionFactory.register class ShelxExtinction(CategoryItem): - """Shelx-style isotropic extinction correction for single - crystals. - """ + """Shelx-style extinction correction for single crystals.""" type_info = TypeInfo( tag='shelx', @@ -34,7 +32,7 @@ def __init__(self) -> None: self._mosaicity = Parameter( name='mosaicity', - description='Mosaicity value for extinction correction.', + description='Mosaicity value for extinction correction', units='deg', value_spec=AttributeSpec( default=1.0, @@ -48,7 +46,7 @@ def __init__(self) -> None: ) self._radius = Parameter( name='radius', - description='Crystal radius for extinction correction.', + description='Crystal radius for extinction correction', units='µm', value_spec=AttributeSpec( default=1.0, @@ -68,17 +66,29 @@ def __init__(self) -> None: # ------------------------------------------------------------------ @property - def mosaicity(self): + def mosaicity(self) -> Parameter: + """ + Mosaicity value for extinction correction (deg). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._mosaicity @mosaicity.setter - def mosaicity(self, value): + def mosaicity(self, value: float) -> None: self._mosaicity.value = value @property - def radius(self): + def radius(self) -> Parameter: + """ + Crystal radius for extinction correction (µm). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._radius @radius.setter - def radius(self, value): + def radius(self, value: float) -> None: self._radius.value = value diff --git a/src/easydiffraction/datablocks/experiment/categories/instrument/__init__.py b/src/easydiffraction/datablocks/experiment/categories/instrument/__init__.py index e4a03696..d12b40c3 100644 --- a/src/easydiffraction/datablocks/experiment/categories/instrument/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/instrument/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.instrument.cwl import CwlPdInstrument diff --git a/src/easydiffraction/datablocks/experiment/categories/instrument/base.py b/src/easydiffraction/datablocks/experiment/categories/instrument/base.py index 0d1c04d5..a2568884 100644 --- a/src/easydiffraction/datablocks/experiment/categories/instrument/base.py +++ b/src/easydiffraction/datablocks/experiment/categories/instrument/base.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Instrument category base definitions for CWL/TOF instruments. +""" +Instrument category base definitions for CWL/TOF instruments. This module provides the shared parent used by concrete instrument implementations under the instrument category. @@ -12,7 +13,8 @@ class InstrumentBase(CategoryItem): - """Base class for instrument category items. + """ + Base class for instrument category items. This class sets the common ``category_code`` and is used as a base for concrete CWL/TOF instrument definitions. diff --git a/src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py b/src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py index 924158a0..3a3628bd 100644 --- a/src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py +++ b/src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.core.metadata import CalculatorSupport @@ -17,6 +17,8 @@ class CwlInstrumentBase(InstrumentBase): + """Base class for constant-wavelength instruments.""" + def __init__(self) -> None: super().__init__() @@ -36,19 +38,28 @@ def __init__(self) -> None: ) @property - def setup_wavelength(self): - """Incident wavelength parameter (Å).""" + def setup_wavelength(self) -> Parameter: + """ + Incident neutron or X-ray wavelength (Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._setup_wavelength @setup_wavelength.setter - def setup_wavelength(self, value): - """Set incident wavelength value (Å).""" + def setup_wavelength(self, value: float) -> None: self._setup_wavelength.value = value @InstrumentFactory.register class CwlScInstrument(CwlInstrumentBase): - type_info = TypeInfo(tag='cwl-sc', description='CW single-crystal diffractometer') + """CW single-crystal diffractometer.""" + + type_info = TypeInfo( + tag='cwl-sc', + description='CW single-crystal diffractometer', + ) compatibility = Compatibility( scattering_type=frozenset({ScatteringTypeEnum.BRAGG}), beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH}), @@ -64,7 +75,12 @@ def __init__(self) -> None: @InstrumentFactory.register class CwlPdInstrument(CwlInstrumentBase): - type_info = TypeInfo(tag='cwl-pd', description='CW powder diffractometer') + """CW powder diffractometer.""" + + type_info = TypeInfo( + tag='cwl-pd', + description='CW powder diffractometer', + ) compatibility = Compatibility( scattering_type=frozenset({ScatteringTypeEnum.BRAGG, ScatteringTypeEnum.TOTAL}), beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH}), @@ -97,11 +113,15 @@ def __init__(self) -> None: ) @property - def calib_twotheta_offset(self): - """Instrument misalignment two-theta offset (deg).""" + def calib_twotheta_offset(self) -> Parameter: + """ + Instrument misalignment offset (deg). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._calib_twotheta_offset @calib_twotheta_offset.setter - def calib_twotheta_offset(self, value): - """Set two-theta offset value (deg).""" + def calib_twotheta_offset(self, value: float) -> None: self._calib_twotheta_offset.value = value diff --git a/src/easydiffraction/datablocks/experiment/categories/instrument/factory.py b/src/easydiffraction/datablocks/experiment/categories/instrument/factory.py index 7d4286af..fce8ad5c 100644 --- a/src/easydiffraction/datablocks/experiment/categories/instrument/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/instrument/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Instrument factory — delegates to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/instrument/tof.py b/src/easydiffraction/datablocks/experiment/categories/instrument/tof.py index efbff40d..7e1db98e 100644 --- a/src/easydiffraction/datablocks/experiment/categories/instrument/tof.py +++ b/src/easydiffraction/datablocks/experiment/categories/instrument/tof.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.core.metadata import CalculatorSupport @@ -18,7 +18,12 @@ @InstrumentFactory.register class TofScInstrument(InstrumentBase): - type_info = TypeInfo(tag='tof-sc', description='TOF single-crystal diffractometer') + """TOF single-crystal diffractometer.""" + + type_info = TypeInfo( + tag='tof-sc', + description='TOF single-crystal diffractometer', + ) compatibility = Compatibility( scattering_type=frozenset({ScatteringTypeEnum.BRAGG}), beam_mode=frozenset({BeamModeEnum.TIME_OF_FLIGHT}), @@ -34,7 +39,12 @@ def __init__(self) -> None: @InstrumentFactory.register class TofPdInstrument(InstrumentBase): - type_info = TypeInfo(tag='tof-pd', description='TOF powder diffractometer') + """TOF powder diffractometer.""" + + type_info = TypeInfo( + tag='tof-pd', + description='TOF powder diffractometer', + ) compatibility = Compatibility( scattering_type=frozenset({ScatteringTypeEnum.BRAGG}), beam_mode=frozenset({BeamModeEnum.TIME_OF_FLIGHT}), @@ -99,41 +109,71 @@ def __init__(self) -> None: ) @property - def setup_twotheta_bank(self): + def setup_twotheta_bank(self) -> Parameter: + """ + Detector bank position (deg). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._setup_twotheta_bank @setup_twotheta_bank.setter - def setup_twotheta_bank(self, value): + def setup_twotheta_bank(self, value: float) -> None: self._setup_twotheta_bank.value = value @property - def calib_d_to_tof_offset(self): + def calib_d_to_tof_offset(self) -> Parameter: + """ + TOF offset (µs). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._calib_d_to_tof_offset @calib_d_to_tof_offset.setter - def calib_d_to_tof_offset(self, value): + def calib_d_to_tof_offset(self, value: float) -> None: self._calib_d_to_tof_offset.value = value @property - def calib_d_to_tof_linear(self): + def calib_d_to_tof_linear(self) -> Parameter: + """ + TOF linear conversion (µs/Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._calib_d_to_tof_linear @calib_d_to_tof_linear.setter - def calib_d_to_tof_linear(self, value): + def calib_d_to_tof_linear(self, value: float) -> None: self._calib_d_to_tof_linear.value = value @property - def calib_d_to_tof_quad(self): + def calib_d_to_tof_quad(self) -> Parameter: + """ + TOF quadratic correction (µs/Ų). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._calib_d_to_tof_quad @calib_d_to_tof_quad.setter - def calib_d_to_tof_quad(self, value): + def calib_d_to_tof_quad(self, value: float) -> None: self._calib_d_to_tof_quad.value = value @property - def calib_d_to_tof_recip(self): + def calib_d_to_tof_recip(self) -> Parameter: + """ + TOF reciprocal velocity correction (µs·Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._calib_d_to_tof_recip @calib_d_to_tof_recip.setter - def calib_d_to_tof_recip(self, value): + def calib_d_to_tof_recip(self, value: float) -> None: self._calib_d_to_tof_recip.value = value diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_crystal/__init__.py b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/__init__.py index 1a6b0b67..4b93121b 100644 --- a/src/easydiffraction/datablocks/experiment/categories/linked_crystal/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.linked_crystal.default import LinkedCrystal diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py index e1fa8b6a..d441aa9c 100644 --- a/src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py +++ b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Default linked-crystal reference (id + scale).""" @@ -21,9 +21,7 @@ @LinkedCrystalFactory.register class LinkedCrystal(CategoryItem): - """Linked crystal category for referencing from the experiment for - single crystal diffraction. - """ + """Linked crystal reference for single-crystal diffraction.""" type_info = TypeInfo( tag='default', @@ -38,7 +36,7 @@ def __init__(self) -> None: self._id = StringDescriptor( name='id', - description='Identifier of the linked crystal.', + description='Identifier of the linked crystal', value_spec=AttributeSpec( default='Si', validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), @@ -47,7 +45,7 @@ def __init__(self) -> None: ) self._scale = Parameter( name='scale', - description='Scale factor of the linked crystal.', + description='Scale factor of the linked crystal', value_spec=AttributeSpec( default=1.0, validator=RangeValidator(), @@ -63,16 +61,29 @@ def __init__(self) -> None: @property def id(self) -> StringDescriptor: + """ + Identifier of the linked crystal. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._id @id.setter - def id(self, value: str): + def id(self, value: str) -> None: self._id.value = value @property def scale(self) -> Parameter: + """ + Scale factor of the linked crystal. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._scale @scale.setter - def scale(self, value: float): + def scale(self, value: float) -> None: self._scale.value = value diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_crystal/factory.py b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/factory.py index 49ac1a64..b34b8073 100644 --- a/src/easydiffraction/datablocks/experiment/categories/linked_crystal/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Linked-crystal factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_phases/__init__.py b/src/easydiffraction/datablocks/experiment/categories/linked_phases/__init__.py index 6dd96b94..dda7d445 100644 --- a/src/easydiffraction/datablocks/experiment/categories/linked_phases/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/linked_phases/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.linked_phases.default import LinkedPhase diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py b/src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py index 067683a9..97d03c66 100644 --- a/src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py +++ b/src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Linked phases allow combining phases with scale factors.""" @@ -23,12 +23,12 @@ class LinkedPhase(CategoryItem): """Link to a phase by id with a scale factor.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._id = StringDescriptor( name='id', - description='Identifier of the linked phase.', + description='Identifier of the linked phase', value_spec=AttributeSpec( default='Si', validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), @@ -54,18 +54,31 @@ def __init__(self): @property def id(self) -> StringDescriptor: + """ + Identifier of the linked phase. + + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. + """ return self._id @id.setter - def id(self, value: str): + def id(self, value: str) -> None: self._id.value = value @property def scale(self) -> Parameter: + """ + Scale factor of the linked phase. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._scale @scale.setter - def scale(self, value: float): + def scale(self, value: float) -> None: self._scale.value = value @@ -81,6 +94,6 @@ class LinkedPhases(CategoryCollection): sample_form=frozenset({SampleFormEnum.POWDER}), ) - def __init__(self): + def __init__(self) -> None: """Create an empty collection of linked phases.""" super().__init__(item_type=LinkedPhase) diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_phases/factory.py b/src/easydiffraction/datablocks/experiment/categories/linked_phases/factory.py index 74f16616..56970ee8 100644 --- a/src/easydiffraction/datablocks/experiment/categories/linked_phases/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/linked_phases/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Linked-phases factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/__init__.py b/src/easydiffraction/datablocks/experiment/categories/peak/__init__.py index b335b9d3..667bafb4 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/__init__.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlPseudoVoigt diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/base.py b/src/easydiffraction/datablocks/experiment/categories/peak/base.py index 5f2654a7..050411b1 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/base.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Base class for peak profile categories.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/cwl.py b/src/easydiffraction/datablocks/experiment/categories/peak/cwl.py index aa03c80c..a2b4f63b 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/cwl.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/cwl.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Constant-wavelength peak profile classes.""" @@ -24,7 +24,10 @@ class CwlPseudoVoigt( ): """Constant-wavelength pseudo-Voigt peak shape.""" - type_info = TypeInfo(tag='pseudo-voigt', description='Pseudo-Voigt profile') + type_info = TypeInfo( + tag='pseudo-voigt', + description='Pseudo-Voigt profile', + ) compatibility = Compatibility( scattering_type=frozenset({ScatteringTypeEnum.BRAGG}), beam_mode=frozenset({BeamModeEnum.CONSTANT_WAVELENGTH}), diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py b/src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py index 2bc9c178..6e4f29c8 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Constant-wavelength (CWL) peak-profile component classes. +""" +Constant-wavelength (CWL) peak-profile component classes. This module provides classes that add broadening and asymmetry parameters. They are composed into concrete peak classes elsewhere via @@ -16,13 +17,12 @@ class CwlBroadeningMixin: """CWL Gaussian and Lorentz broadening parameters.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._broad_gauss_u: Parameter = Parameter( name='broad_gauss_u', - description='Gaussian broadening coefficient (dependent on ' - 'sample size and instrument resolution)', + description='Gaussian broadening from sample size and resolution', units='deg²', value_spec=AttributeSpec( default=0.01, @@ -32,7 +32,7 @@ def __init__(self): ) self._broad_gauss_v: Parameter = Parameter( name='broad_gauss_v', - description='Gaussian broadening coefficient (instrumental broadening contribution)', + description='Gaussian broadening instrumental contribution', units='deg²', value_spec=AttributeSpec( default=-0.01, @@ -42,7 +42,7 @@ def __init__(self): ) self._broad_gauss_w: Parameter = Parameter( name='broad_gauss_w', - description='Gaussian broadening coefficient (instrumental broadening contribution)', + description='Gaussian broadening instrumental contribution', units='deg²', value_spec=AttributeSpec( default=0.02, @@ -52,7 +52,7 @@ def __init__(self): ) self._broad_lorentz_x: Parameter = Parameter( name='broad_lorentz_x', - description='Lorentzian broadening coefficient (dependent on sample strain effects)', + description='Lorentzian broadening from sample strain effects', units='deg', value_spec=AttributeSpec( default=0.0, @@ -62,8 +62,7 @@ def __init__(self): ) self._broad_lorentz_y: Parameter = Parameter( name='broad_lorentz_y', - description='Lorentzian broadening coefficient (dependent on ' - 'microstructural defects and strain)', + description='Lorentzian broadening from microstructural defects', units='deg', value_spec=AttributeSpec( default=0.0, @@ -78,49 +77,79 @@ def __init__(self): @property def broad_gauss_u(self) -> Parameter: + """ + Gaussian broadening from sample size and resolution (deg²). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_gauss_u @broad_gauss_u.setter - def broad_gauss_u(self, value): + def broad_gauss_u(self, value: float) -> None: self._broad_gauss_u.value = value @property def broad_gauss_v(self) -> Parameter: + """ + Gaussian broadening instrumental contribution (deg²). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_gauss_v @broad_gauss_v.setter - def broad_gauss_v(self, value): + def broad_gauss_v(self, value: float) -> None: self._broad_gauss_v.value = value @property def broad_gauss_w(self) -> Parameter: + """ + Gaussian broadening instrumental contribution (deg²). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_gauss_w @broad_gauss_w.setter - def broad_gauss_w(self, value): + def broad_gauss_w(self, value: float) -> None: self._broad_gauss_w.value = value @property def broad_lorentz_x(self) -> Parameter: + """ + Lorentzian broadening (sample strain effects) (deg). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_lorentz_x @broad_lorentz_x.setter - def broad_lorentz_x(self, value): + def broad_lorentz_x(self, value: float) -> None: self._broad_lorentz_x.value = value @property def broad_lorentz_y(self) -> Parameter: + """ + Lorentzian broadening from microstructural defects (deg). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_lorentz_y @broad_lorentz_y.setter - def broad_lorentz_y(self, value): + def broad_lorentz_y(self, value: float) -> None: self._broad_lorentz_y.value = value class EmpiricalAsymmetryMixin: """Empirical CWL peak asymmetry parameters.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._asym_empir_1: Parameter = Parameter( @@ -170,41 +199,65 @@ def __init__(self): @property def asym_empir_1(self) -> Parameter: + """ + Empirical asymmetry coefficient p1. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_empir_1 @asym_empir_1.setter - def asym_empir_1(self, value): + def asym_empir_1(self, value: float) -> None: self._asym_empir_1.value = value @property def asym_empir_2(self) -> Parameter: + """ + Empirical asymmetry coefficient p2. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_empir_2 @asym_empir_2.setter - def asym_empir_2(self, value): + def asym_empir_2(self, value: float) -> None: self._asym_empir_2.value = value @property def asym_empir_3(self) -> Parameter: + """ + Empirical asymmetry coefficient p3. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_empir_3 @asym_empir_3.setter - def asym_empir_3(self, value): + def asym_empir_3(self, value: float) -> None: self._asym_empir_3.value = value @property def asym_empir_4(self) -> Parameter: + """ + Empirical asymmetry coefficient p4. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_empir_4 @asym_empir_4.setter - def asym_empir_4(self, value): + def asym_empir_4(self, value: float) -> None: self._asym_empir_4.value = value class FcjAsymmetryMixin: """Finger–Cox–Jephcoat (FCJ) asymmetry parameters.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._asym_fcj_1: Parameter = Parameter( @@ -233,17 +286,29 @@ def __init__(self): # ------------------------------------------------------------------ @property - def asym_fcj_1(self): + def asym_fcj_1(self) -> Parameter: + """ + Finger-Cox-Jephcoat asymmetry parameter 1. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_fcj_1 @asym_fcj_1.setter - def asym_fcj_1(self, value): + def asym_fcj_1(self, value: float) -> None: self._asym_fcj_1.value = value @property - def asym_fcj_2(self): + def asym_fcj_2(self) -> Parameter: + """ + Finger-Cox-Jephcoat asymmetry parameter 2. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_fcj_2 @asym_fcj_2.setter - def asym_fcj_2(self, value): + def asym_fcj_2(self, value: float) -> None: self._asym_fcj_2.value = value diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/factory.py b/src/easydiffraction/datablocks/experiment/categories/peak/factory.py index 1992b633..ca196748 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/factory.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Peak profile factory — delegates to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/tof.py b/src/easydiffraction/datablocks/experiment/categories/peak/tof.py index 1c70b65b..59c0b9e3 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/tof.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/tof.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Time-of-flight peak profile classes.""" @@ -23,7 +23,10 @@ class TofPseudoVoigt( ): """Time-of-flight pseudo-Voigt peak shape.""" - type_info = TypeInfo(tag='tof-pseudo-voigt', description='TOF pseudo-Voigt profile') + type_info = TypeInfo( + tag='tof-pseudo-voigt', + description='TOF pseudo-Voigt profile', + ) compatibility = Compatibility( scattering_type=frozenset({ScatteringTypeEnum.BRAGG}), beam_mode=frozenset({BeamModeEnum.TIME_OF_FLIGHT}), diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/tof_mixins.py b/src/easydiffraction/datablocks/experiment/categories/peak/tof_mixins.py index 01a10b26..8093d877 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/tof_mixins.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/tof_mixins.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Time-of-flight (TOF) peak-profile component classes. +""" +Time-of-flight (TOF) peak-profile component classes. Defines classes that add Gaussian/Lorentz broadening, mixing, and Ikeda–Carpenter asymmetry parameters used by TOF peak shapes. This @@ -18,12 +19,12 @@ class TofBroadeningMixin: """TOF Gaussian/Lorentz broadening and mixing parameters.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._broad_gauss_sigma_0 = Parameter( name='gauss_sigma_0', - description='Gaussian broadening coefficient (instrumental resolution)', + description='Gaussian broadening (instrumental resolution)', units='µs²', value_spec=AttributeSpec( default=0.0, @@ -33,7 +34,7 @@ def __init__(self): ) self._broad_gauss_sigma_1 = Parameter( name='gauss_sigma_1', - description='Gaussian broadening coefficient (dependent on d-spacing)', + description='Gaussian broadening (dependent on d-spacing)', units='µs/Å', value_spec=AttributeSpec( default=0.0, @@ -43,7 +44,7 @@ def __init__(self): ) self._broad_gauss_sigma_2 = Parameter( name='gauss_sigma_2', - description='Gaussian broadening coefficient (instrument-dependent term)', + description='Gaussian broadening (instrument-dependent term)', units='µs²/Ų', value_spec=AttributeSpec( default=0.0, @@ -53,7 +54,7 @@ def __init__(self): ) self._broad_lorentz_gamma_0 = Parameter( name='lorentz_gamma_0', - description='Lorentzian broadening coefficient (dependent on microstrain effects)', + description='Lorentzian broadening (microstrain effects)', units='µs', value_spec=AttributeSpec( default=0.0, @@ -63,7 +64,7 @@ def __init__(self): ) self._broad_lorentz_gamma_1 = Parameter( name='lorentz_gamma_1', - description='Lorentzian broadening coefficient (dependent on d-spacing)', + description='Lorentzian broadening (dependent on d-spacing)', units='µs/Å', value_spec=AttributeSpec( default=0.0, @@ -73,7 +74,7 @@ def __init__(self): ) self._broad_lorentz_gamma_2 = Parameter( name='lorentz_gamma_2', - description='Lorentzian broadening coefficient (instrument-dependent term)', + description='Lorentzian broadening (instrument-dependent term)', units='µs²/Ų', value_spec=AttributeSpec( default=0.0, @@ -83,8 +84,7 @@ def __init__(self): ) self._broad_mix_beta_0 = Parameter( name='mix_beta_0', - description='Mixing parameter. Defines the ratio of Gaussian ' - 'to Lorentzian contributions in TOF profiles', + description='Ratio of Gaussian to Lorentzian contributions', units='deg', value_spec=AttributeSpec( default=0.0, @@ -94,8 +94,7 @@ def __init__(self): ) self._broad_mix_beta_1 = Parameter( name='mix_beta_1', - description='Mixing parameter. Defines the ratio of Gaussian ' - 'to Lorentzian contributions in TOF profiles', + description='Ratio of Gaussian to Lorentzian contributions', units='deg', value_spec=AttributeSpec( default=0.0, @@ -109,75 +108,122 @@ def __init__(self): # ------------------------------------------------------------------ @property - def broad_gauss_sigma_0(self): + def broad_gauss_sigma_0(self) -> Parameter: + """ + Gaussian broadening (instrumental resolution) (µs²). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_gauss_sigma_0 @broad_gauss_sigma_0.setter - def broad_gauss_sigma_0(self, value): + def broad_gauss_sigma_0(self, value: float) -> None: self._broad_gauss_sigma_0.value = value @property - def broad_gauss_sigma_1(self): + def broad_gauss_sigma_1(self) -> Parameter: + """ + Gaussian broadening (dependent on d-spacing) (µs/Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_gauss_sigma_1 @broad_gauss_sigma_1.setter - def broad_gauss_sigma_1(self, value): + def broad_gauss_sigma_1(self, value: float) -> None: self._broad_gauss_sigma_1.value = value @property - def broad_gauss_sigma_2(self): + def broad_gauss_sigma_2(self) -> Parameter: + """ + Gaussian broadening (instrument-dependent term) (µs²/Ų). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_gauss_sigma_2 @broad_gauss_sigma_2.setter - def broad_gauss_sigma_2(self, value): - """Set Gaussian sigma_2 parameter.""" + def broad_gauss_sigma_2(self, value: float) -> None: self._broad_gauss_sigma_2.value = value @property - def broad_lorentz_gamma_0(self): + def broad_lorentz_gamma_0(self) -> Parameter: + """ + Lorentzian broadening (microstrain effects) (µs). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_lorentz_gamma_0 @broad_lorentz_gamma_0.setter - def broad_lorentz_gamma_0(self, value): + def broad_lorentz_gamma_0(self, value: float) -> None: self._broad_lorentz_gamma_0.value = value @property - def broad_lorentz_gamma_1(self): + def broad_lorentz_gamma_1(self) -> Parameter: + """ + Lorentzian broadening (dependent on d-spacing) (µs/Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_lorentz_gamma_1 @broad_lorentz_gamma_1.setter - def broad_lorentz_gamma_1(self, value): + def broad_lorentz_gamma_1(self, value: float) -> None: self._broad_lorentz_gamma_1.value = value @property - def broad_lorentz_gamma_2(self): + def broad_lorentz_gamma_2(self) -> Parameter: + """ + Lorentzian broadening (instrument-dependent term) (µs²/Ų). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_lorentz_gamma_2 @broad_lorentz_gamma_2.setter - def broad_lorentz_gamma_2(self, value): + def broad_lorentz_gamma_2(self, value: float) -> None: self._broad_lorentz_gamma_2.value = value @property - def broad_mix_beta_0(self): + def broad_mix_beta_0(self) -> Parameter: + """ + Ratio of Gaussian to Lorentzian contributions (deg). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_mix_beta_0 @broad_mix_beta_0.setter - def broad_mix_beta_0(self, value): + def broad_mix_beta_0(self, value: float) -> None: self._broad_mix_beta_0.value = value @property - def broad_mix_beta_1(self): + def broad_mix_beta_1(self) -> Parameter: + """ + Ratio of Gaussian to Lorentzian contributions (deg). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_mix_beta_1 @broad_mix_beta_1.setter - def broad_mix_beta_1(self, value): + def broad_mix_beta_1(self, value: float) -> None: self._broad_mix_beta_1.value = value class IkedaCarpenterAsymmetryMixin: """Ikeda–Carpenter asymmetry parameters.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._asym_alpha_0 = Parameter( @@ -202,17 +248,29 @@ def __init__(self): ) @property - def asym_alpha_0(self): + def asym_alpha_0(self) -> Parameter: + """ + Ikeda-Carpenter asymmetry parameter α₀. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_alpha_0 @asym_alpha_0.setter - def asym_alpha_0(self, value): + def asym_alpha_0(self, value: float) -> None: self._asym_alpha_0.value = value @property - def asym_alpha_1(self): + def asym_alpha_1(self) -> Parameter: + """ + Ikeda-Carpenter asymmetry parameter α₁. + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._asym_alpha_1 @asym_alpha_1.setter - def asym_alpha_1(self, value): + def asym_alpha_1(self, value: float) -> None: self._asym_alpha_1.value = value diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/total.py b/src/easydiffraction/datablocks/experiment/categories/peak/total.py index a1166c1a..61d5e6fd 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/total.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/total.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Total-scattering (PDF) peak profile classes.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/total_mixins.py b/src/easydiffraction/datablocks/experiment/categories/peak/total_mixins.py index 139e37dd..bae2c974 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/total_mixins.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/total_mixins.py @@ -1,7 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Total scattering / pair distribution function (PDF) peak-profile -component classes. +""" +Total scattering / PDF peak-profile component classes. This module provides classes that add broadening and asymmetry parameters. They are composed into concrete peak classes elsewhere via @@ -17,13 +17,12 @@ class TotalBroadeningMixin: """PDF broadening/damping/sharpening parameters.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self._damp_q = Parameter( name='damp_q', - description='Instrumental Q-resolution damping factor ' - '(affects high-r PDF peak amplitude)', + description='Q-resolution damping for high-r PDF peak amplitude', units='Å⁻¹', value_spec=AttributeSpec( default=0.05, @@ -33,8 +32,7 @@ def __init__(self): ) self._broad_q = Parameter( name='broad_q', - description='Quadratic PDF peak broadening coefficient ' - '(thermal and model uncertainty contribution)', + description='Quadratic peak broadening from thermal uncertainty', units='Å⁻²', value_spec=AttributeSpec( default=0.0, @@ -44,8 +42,7 @@ def __init__(self): ) self._cutoff_q = Parameter( name='cutoff_q', - description='Q-value cutoff applied to model PDF for Fourier ' - 'transform (controls real-space resolution)', + description='Q-value cutoff for Fourier transform', units='Å⁻¹', value_spec=AttributeSpec( default=25.0, @@ -55,7 +52,7 @@ def __init__(self): ) self._sharp_delta_1 = Parameter( name='sharp_delta_1', - description='PDF peak sharpening coefficient (1/r dependence)', + description='Peak sharpening coefficient (1/r dependence)', units='Å', value_spec=AttributeSpec( default=0.0, @@ -65,7 +62,7 @@ def __init__(self): ) self._sharp_delta_2 = Parameter( name='sharp_delta_2', - description='PDF peak sharpening coefficient (1/r² dependence)', + description='Peak sharpening coefficient (1/r² dependence)', units='Ų', value_spec=AttributeSpec( default=0.0, @@ -75,7 +72,7 @@ def __init__(self): ) self._damp_particle_diameter = Parameter( name='damp_particle_diameter', - description='Particle diameter for spherical envelope damping correction in PDF', + description='Particle diameter for spherical envelope damping correction', units='Å', value_spec=AttributeSpec( default=0.0, @@ -89,49 +86,85 @@ def __init__(self): # ------------------------------------------------------------------ @property - def damp_q(self): + def damp_q(self) -> Parameter: + """ + Q-resolution damping for high-r PDF peak amplitude (Å⁻¹). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._damp_q @damp_q.setter - def damp_q(self, value): + def damp_q(self, value: float) -> None: self._damp_q.value = value @property - def broad_q(self): + def broad_q(self) -> Parameter: + """ + Quadratic peak broadening from thermal uncertainty (Å⁻²). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._broad_q @broad_q.setter - def broad_q(self, value): + def broad_q(self, value: float) -> None: self._broad_q.value = value @property def cutoff_q(self) -> Parameter: + """ + Q-value cutoff for Fourier transform (Å⁻¹). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._cutoff_q @cutoff_q.setter - def cutoff_q(self, value): + def cutoff_q(self, value: float) -> None: self._cutoff_q.value = value @property def sharp_delta_1(self) -> Parameter: + """ + PDF peak sharpening coefficient (1/r dependence) (Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._sharp_delta_1 @sharp_delta_1.setter - def sharp_delta_1(self, value): + def sharp_delta_1(self, value: float) -> None: self._sharp_delta_1.value = value @property - def sharp_delta_2(self): + def sharp_delta_2(self) -> Parameter: + """ + PDF peak sharpening coefficient (1/r² dependence) (Ų). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._sharp_delta_2 @sharp_delta_2.setter - def sharp_delta_2(self, value): + def sharp_delta_2(self, value: float) -> None: self._sharp_delta_2.value = value @property - def damp_particle_diameter(self): + def damp_particle_diameter(self) -> Parameter: + """ + Particle diameter for spherical envelope damping correction (Å). + + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. + """ return self._damp_particle_diameter @damp_particle_diameter.setter - def damp_particle_diameter(self, value): + def damp_particle_diameter(self, value: float) -> None: self._damp_particle_diameter.value = value diff --git a/src/easydiffraction/datablocks/experiment/collection.py b/src/easydiffraction/datablocks/experiment/collection.py index 854b77ad..5cdf3f6b 100644 --- a/src/easydiffraction/datablocks/experiment/collection.py +++ b/src/easydiffraction/datablocks/experiment/collection.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Collection of experiment data blocks.""" @@ -11,7 +11,8 @@ class Experiments(DatablockCollection): - """Collection of Experiment data blocks. + """ + Collection of Experiment data blocks. Provides convenience constructors for common creation patterns and helper methods for simple presentation of collection contents. @@ -35,14 +36,21 @@ def create( radiation_probe: str | None = None, scattering_type: str | None = None, ) -> None: - """Add an experiment without associating a data file. - - Args: - name: Experiment identifier. - sample_form: Sample form (e.g. ``'powder'``). - beam_mode: Beam mode (e.g. ``'constant wavelength'``). - radiation_probe: Radiation probe (e.g. ``'neutron'``). - scattering_type: Scattering type (e.g. ``'bragg'``). + """ + Add an experiment without associating a data file. + + Parameters + ---------- + name : str + Experiment identifier. + sample_form : str | None, default=None + Sample form (e.g. ``'powder'``). + beam_mode : str | None, default=None + Beam mode (e.g. ``'constant wavelength'``). + radiation_probe : str | None, default=None + Radiation probe (e.g. ``'neutron'``). + scattering_type : str | None, default=None + Scattering type (e.g. ``'bragg'``). """ experiment = ExperimentFactory.from_scratch( name=name, @@ -59,10 +67,13 @@ def add_from_cif_str( self, cif_str: str, ) -> None: - """Add an experiment from a CIF string. + """ + Add an experiment from a CIF string. - Args: - cif_str: Full CIF document as a string. + Parameters + ---------- + cif_str : str + Full CIF document as a string. """ experiment = ExperimentFactory.from_cif_str(cif_str) self.add(experiment) @@ -73,10 +84,13 @@ def add_from_cif_path( self, cif_path: str, ) -> None: - """Add an experiment from a CIF file path. + """ + Add an experiment from a CIF file path. - Args: - cif_path(str): Path to a CIF document. + Parameters + ---------- + cif_path : str + Path to a CIF document. """ experiment = ExperimentFactory.from_cif_path(cif_path) self.add(experiment) @@ -92,15 +106,23 @@ def add_from_data_path( radiation_probe: str | None = None, scattering_type: str | None = None, ) -> None: - """Add an experiment from a data file path. - - Args: - name: Experiment identifier. - data_path: Path to the measured data file. - sample_form: Sample form (e.g. ``'powder'``). - beam_mode: Beam mode (e.g. ``'constant wavelength'``). - radiation_probe: Radiation probe (e.g. ``'neutron'``). - scattering_type: Scattering type (e.g. ``'bragg'``). + """ + Add an experiment from a data file path. + + Parameters + ---------- + name : str + Experiment identifier. + data_path : str + Path to the measured data file. + sample_form : str | None, default=None + Sample form (e.g. ``'powder'``). + beam_mode : str | None, default=None + Beam mode (e.g. ``'constant wavelength'``). + radiation_probe : str | None, default=None + Radiation probe (e.g. ``'neutron'``). + scattering_type : str | None, default=None + Scattering type (e.g. ``'bragg'``). """ experiment = ExperimentFactory.from_data_path( name=name, diff --git a/src/easydiffraction/datablocks/experiment/item/__init__.py b/src/easydiffraction/datablocks/experiment/item/__init__.py index ffe5775d..35a64fb1 100644 --- a/src/easydiffraction/datablocks/experiment/item/__init__.py +++ b/src/easydiffraction/datablocks/experiment/item/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.item.base import ExperimentBase diff --git a/src/easydiffraction/datablocks/experiment/item/base.py b/src/easydiffraction/datablocks/experiment/item/base.py index 24c6ee6e..2dddee44 100644 --- a/src/easydiffraction/datablocks/experiment/item/base.py +++ b/src/easydiffraction/datablocks/experiment/item/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Base classes for experiment datablock items.""" @@ -34,16 +34,14 @@ class ExperimentBase(DatablockItem): - """Base class for all experiment datablock items with only core - attributes. - """ + """Base class for all experiment datablock items.""" def __init__( self, *, name: str, type: ExperimentType, - ): + ) -> None: super().__init__() self._name = name self._type = type @@ -58,18 +56,19 @@ def name(self) -> str: @name.setter def name(self, new: str) -> None: - """Rename the experiment. + """ + Rename the experiment. - Args: - new: New name for this experiment. + Parameters + ---------- + new : str + New name for this experiment. """ self._name = new @property - def type(self): # TODO: Consider another name - """Experiment type descriptor (sample form, probe, beam - mode). - """ + def type(self) -> object: # TODO: Consider another name + """Experiment type: sample form, probe, beam mode.""" return self._type @property @@ -86,10 +85,18 @@ def show_as_cif(self) -> None: @abstractmethod def _load_ascii_data_to_experiment(self, data_path: str) -> None: - """Load ASCII data from file into the experiment data category. + """ + Load ASCII data from file into the experiment data category. + + Parameters + ---------- + data_path : str + Path to the ASCII file to load. - Args: - data_path: Path to the ASCII file to load. + Raises + ------ + NotImplementedError + Subclasses must implement this method. """ raise NotImplementedError() @@ -98,8 +105,9 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: # ------------------------------------------------------------------ @property - def calculator(self): - """The active calculator instance for this experiment. + def calculator(self) -> object: + """ + The active calculator instance for this experiment. Auto-resolved on first access from the experiment's data category ``calculator_support`` and @@ -118,11 +126,14 @@ def calculator_type(self) -> str: @calculator_type.setter def calculator_type(self, tag: str) -> None: - """Switch to a different calculator backend. + """ + Switch to a different calculator backend. - Args: - tag: Calculator tag (e.g. ``'cryspy'``, ``'crysfml'``, - ``'pdffit'``). + Parameters + ---------- + tag : str + Calculator tag (e.g. ``'cryspy'``, ``'crysfml'``, + ``'pdffit'``). """ from easydiffraction.analysis.calculators.factory import CalculatorFactory @@ -140,9 +151,7 @@ def calculator_type(self, tag: str) -> None: console.print(tag) def show_supported_calculator_types(self) -> None: - """Print a table of calculator backends supported by this - experiment. - """ + """Print a table of supported calculator backends.""" from easydiffraction.analysis.calculators.factory import CalculatorFactory supported_tags = self._supported_calculator_tags() @@ -169,10 +178,7 @@ def show_current_calculator_type(self) -> None: console.print(self.calculator_type) def _resolve_calculator(self) -> None: - """Auto-resolve the default calculator from the data category's - ``calculator_support`` and - ``CalculatorFactory._default_rules``. - """ + """Auto-resolve the default calculator from data category.""" from easydiffraction.analysis.calculators.factory import CalculatorFactory tag = CalculatorFactory.default_tag( @@ -185,7 +191,8 @@ def _resolve_calculator(self) -> None: self._calculator_type = tag def _supported_calculator_tags(self) -> list[str]: - """Return calculator tags supported by this experiment. + """ + Return calculator tags supported by this experiment. Intersects the data category's ``calculator_support`` with calculators whose engines are importable. @@ -231,11 +238,14 @@ def __init__( @abstractmethod def _load_ascii_data_to_experiment(self, data_path: str) -> None: - """Load single crystal data from an ASCII file. + """ + Load single crystal data from an ASCII file. - Args: - data_path: Path to data file with columns compatible with - the beam mode. + Parameters + ---------- + data_path : str + Path to data file with columns compatible with the beam + mode. """ pass @@ -244,7 +254,7 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: # ------------------------------------------------------------------ @property - def extinction(self): + def extinction(self) -> object: """Active extinction correction model.""" return self._extinction @@ -255,10 +265,13 @@ def extinction_type(self) -> str: @extinction_type.setter def extinction_type(self, new_type: str) -> None: - """Switch to a different extinction correction model. + """ + Switch to a different extinction correction model. - Args: - new_type: Extinction tag (e.g. ``'shelx'``). + Parameters + ---------- + new_type : str + Extinction tag (e.g. ``'shelx'``). """ supported_tags = ExtinctionFactory.supported_tags() if new_type not in supported_tags: @@ -288,7 +301,7 @@ def show_current_extinction_type(self) -> None: # ------------------------------------------------------------------ @property - def linked_crystal(self): + def linked_crystal(self) -> object: """Linked crystal model for this experiment.""" return self._linked_crystal @@ -299,10 +312,13 @@ def linked_crystal_type(self) -> str: @linked_crystal_type.setter def linked_crystal_type(self, new_type: str) -> None: - """Switch to a different linked-crystal reference type. + """ + Switch to a different linked-crystal reference type. - Args: - new_type: Linked-crystal tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Linked-crystal tag (e.g. ``'default'``). """ supported_tags = LinkedCrystalFactory.supported_tags() if new_type not in supported_tags: @@ -332,7 +348,7 @@ def show_current_linked_crystal_type(self) -> None: # ------------------------------------------------------------------ @property - def instrument(self): + def instrument(self) -> object: """Active instrument model for this experiment.""" return self._instrument @@ -343,10 +359,13 @@ def instrument_type(self) -> str: @instrument_type.setter def instrument_type(self, new_type: str) -> None: - """Switch to a different instrument type. + """ + Switch to a different instrument type. - Args: - new_type: Instrument tag (e.g. ``'cwl-sc'``). + Parameters + ---------- + new_type : str + Instrument tag (e.g. ``'cwl-sc'``). """ supported = InstrumentFactory.supported_for( scattering_type=self.type.scattering_type.value, @@ -384,7 +403,7 @@ def show_current_instrument_type(self) -> None: # ------------------------------------------------------------------ @property - def data(self): + def data(self) -> object: """Data collection for this experiment.""" return self._data @@ -395,10 +414,13 @@ def data_type(self) -> str: @data_type.setter def data_type(self, new_type: str) -> None: - """Switch to a different data collection type. + """ + Switch to a different data collection type. - Args: - new_type: Data tag (e.g. ``'bragg-sc'``). + Parameters + ---------- + new_type : str + Data tag (e.g. ``'bragg-sc'``). """ supported_tags = DataFactory.supported_tags() if new_type not in supported_tags: @@ -454,12 +476,17 @@ def _get_valid_linked_phases( self, structures: Structures, ) -> List[Any]: - """Get valid linked phases for this experiment. + """ + Get valid linked phases for this experiment. - Args: - structures: Collection of structures. + Parameters + ---------- + structures : Structures + Collection of structures. - Returns: + Returns + ------- + List[Any] A list of valid linked phases. """ if not self.linked_phases: @@ -485,16 +512,19 @@ def _get_valid_linked_phases( @abstractmethod def _load_ascii_data_to_experiment(self, data_path: str) -> None: - """Load powder diffraction data from an ASCII file. + """ + Load powder diffraction data from an ASCII file. - Args: - data_path: Path to data file with columns compatible with - the beam mode (e.g. 2θ/I/σ for CWL, TOF/I/σ for TOF). + Parameters + ---------- + data_path : str + Path to data file with columns compatible with the beam mode + (e.g. 2θ/I/σ for CWL, TOF/I/σ for TOF). """ pass @property - def linked_phases(self): + def linked_phases(self) -> object: """Collection of phases linked to this experiment.""" return self._linked_phases @@ -505,10 +535,13 @@ def linked_phases_type(self) -> str: @linked_phases_type.setter def linked_phases_type(self, new_type: str) -> None: - """Switch to a different linked-phases collection type. + """ + Switch to a different linked-phases collection type. - Args: - new_type: Linked-phases tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Linked-phases tag (e.g. ``'default'``). """ supported_tags = LinkedPhasesFactory.supported_tags() if new_type not in supported_tags: @@ -534,7 +567,7 @@ def show_current_linked_phases_type(self) -> None: console.print(self.linked_phases_type) @property - def excluded_regions(self): + def excluded_regions(self) -> object: """Collection of excluded regions for the x-grid.""" return self._excluded_regions @@ -545,10 +578,13 @@ def excluded_regions_type(self) -> str: @excluded_regions_type.setter def excluded_regions_type(self, new_type: str) -> None: - """Switch to a different excluded-regions collection type. + """ + Switch to a different excluded-regions collection type. - Args: - new_type: Excluded-regions tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Excluded-regions tag (e.g. ``'default'``). """ supported_tags = ExcludedRegionsFactory.supported_tags() if new_type not in supported_tags: @@ -565,9 +601,7 @@ def excluded_regions_type(self, new_type: str) -> None: console.print(new_type) def show_supported_excluded_regions_types(self) -> None: - """Print a table of supported excluded-regions collection - types. - """ + """Print a table of supported excluded-regions types.""" ExcludedRegionsFactory.show_supported() def show_current_excluded_regions_type(self) -> None: @@ -580,7 +614,7 @@ def show_current_excluded_regions_type(self) -> None: # ------------------------------------------------------------------ @property - def data(self): + def data(self) -> object: """Data collection for this experiment.""" return self._data @@ -591,10 +625,13 @@ def data_type(self) -> str: @data_type.setter def data_type(self, new_type: str) -> None: - """Switch to a different data collection type. + """ + Switch to a different data collection type. - Args: - new_type: Data tag (e.g. ``'bragg-pd-cwl'``). + Parameters + ---------- + new_type : str + Data tag (e.g. ``'bragg-pd-cwl'``). """ supported_tags = DataFactory.supported_tags() if new_type not in supported_tags: @@ -619,21 +656,24 @@ def show_current_data_type(self) -> None: console.print(self.data_type) @property - def peak(self): + def peak(self) -> object: """Peak category object with profile parameters and mixins.""" return self._peak @property - def peak_profile_type(self): + def peak_profile_type(self) -> object: """Currently selected peak profile type enum.""" return self._peak_profile_type @peak_profile_type.setter - def peak_profile_type(self, new_type: str): - """Change the active peak profile type, if supported. + def peak_profile_type(self, new_type: str) -> None: + """ + Change the active peak profile type, if supported. - Args: - new_type: New profile type as tag string. + Parameters + ---------- + new_type : str + New profile type as tag string. """ supported = PeakFactory.supported_for( scattering_type=self.type.scattering_type.value, @@ -659,14 +699,14 @@ def peak_profile_type(self, new_type: str): console.paragraph(f"Peak profile type for experiment '{self.name}' changed to") console.print(new_type) - def show_supported_peak_profile_types(self): + def show_supported_peak_profile_types(self) -> None: """Print available peak profile types for this experiment.""" PeakFactory.show_supported( scattering_type=self.type.scattering_type.value, beam_mode=self.type.beam_mode.value, ) - def show_current_peak_profile_type(self): + def show_current_peak_profile_type(self) -> None: """Print the currently selected peak profile type.""" console.paragraph('Current peak profile type') console.print(self.peak_profile_type) diff --git a/src/easydiffraction/datablocks/experiment/item/bragg_pd.py b/src/easydiffraction/datablocks/experiment/item/bragg_pd.py index 258085c5..d5b469cf 100644 --- a/src/easydiffraction/datablocks/experiment/item/bragg_pd.py +++ b/src/easydiffraction/datablocks/experiment/item/bragg_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -25,9 +25,7 @@ @ExperimentFactory.register class BraggPdExperiment(PdExperimentBase): - """Standard (Bragg) Powder Diffraction experiment class with - specific attributes. - """ + """Standard Bragg powder diffraction experiment.""" type_info = TypeInfo( tag='bragg-pd', @@ -57,8 +55,8 @@ def __init__( self._background = BackgroundFactory.create(self._background_type) def _load_ascii_data_to_experiment(self, data_path: str) -> None: - """Load (x, y, sy) data from an ASCII file into the data - category. + """ + Load (x, y, sy) data from an ASCII file into the data category. The file format is space/column separated with 2 or 3 columns: ``x y [sy]``. If ``sy`` is missing, it is approximated as @@ -105,7 +103,7 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: # ------------------------------------------------------------------ @property - def instrument(self): + def instrument(self) -> object: """Active instrument model for this experiment.""" return self._instrument @@ -116,10 +114,13 @@ def instrument_type(self) -> str: @instrument_type.setter def instrument_type(self, new_type: str) -> None: - """Switch to a different instrument type. + """ + Switch to a different instrument type. - Args: - new_type: Instrument tag (e.g. ``'cwl-pd'``). + Parameters + ---------- + new_type : str + Instrument tag (e.g. ``'cwl-pd'``). """ supported = InstrumentFactory.supported_for( scattering_type=self.type.scattering_type.value, @@ -157,12 +158,12 @@ def show_current_instrument_type(self) -> None: # ------------------------------------------------------------------ @property - def background_type(self): + def background_type(self) -> object: """Current background type enum value.""" return self._background_type @background_type.setter - def background_type(self, new_type): + def background_type(self, new_type: str) -> None: """Set a new background type and recreate background object.""" if self._background_type == new_type: console.paragraph(f"Background type for experiment '{self.name}' already set to") @@ -190,14 +191,15 @@ def background_type(self, new_type): console.print(new_type) @property - def background(self): + def background(self) -> object: + """Active background model for this experiment.""" return self._background - def show_supported_background_types(self): + def show_supported_background_types(self) -> None: """Print a table of supported background types.""" BackgroundFactory.show_supported() - def show_current_background_type(self): + def show_current_background_type(self) -> None: """Print the currently used background type.""" console.paragraph('Current background type') console.print(self.background_type) diff --git a/src/easydiffraction/datablocks/experiment/item/bragg_sc.py b/src/easydiffraction/datablocks/experiment/item/bragg_sc.py index e8142e0e..3cb3f8a1 100644 --- a/src/easydiffraction/datablocks/experiment/item/bragg_sc.py +++ b/src/easydiffraction/datablocks/experiment/item/bragg_sc.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -23,9 +23,7 @@ @ExperimentFactory.register class CwlScExperiment(ScExperimentBase): - """Standard (Bragg) constant wavelength single srystal experiment - class with specific attributes. - """ + """Bragg constant-wavelength single-crystal experiment.""" type_info = TypeInfo( tag='bragg-sc-cwl', @@ -46,10 +44,11 @@ def __init__( super().__init__(name=name, type=type) def _load_ascii_data_to_experiment(self, data_path: str) -> None: - """Load measured data from an ASCII file into the data category. + """ + Load measured data from an ASCII file into the data category. - The file format is space/column separated with 5 columns: - ``h k l Iobs sIobs``. + The file format is space/column separated with 5 columns: ``h k + l Iobs sIobs``. """ try: data = np.loadtxt(data_path) @@ -87,9 +86,7 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: @ExperimentFactory.register class TofScExperiment(ScExperimentBase): - """Standard (Bragg) time-of-flight single srystal experiment class - with specific attributes. - """ + """Bragg time-of-flight single-crystal experiment.""" type_info = TypeInfo( tag='bragg-sc-tof', @@ -110,10 +107,11 @@ def __init__( super().__init__(name=name, type=type) def _load_ascii_data_to_experiment(self, data_path: str) -> None: - """Load measured data from an ASCII file into the data category. + """ + Load measured data from an ASCII file into the data category. - The file format is space/column separated with 6 columns: - ``h k l Iobs sIobs wavelength``. + The file format is space/column separated with 6 columns: ``h k + l Iobs sIobs wavelength``. """ try: data = np.loadtxt(data_path) diff --git a/src/easydiffraction/datablocks/experiment/item/enums.py b/src/easydiffraction/datablocks/experiment/item/enums.py index 8d0f153e..2375c38e 100644 --- a/src/easydiffraction/datablocks/experiment/item/enums.py +++ b/src/easydiffraction/datablocks/experiment/item/enums.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Enumerations for experiment configuration (forms, modes, types).""" @@ -13,9 +13,25 @@ class SampleFormEnum(str, Enum): @classmethod def default(cls) -> 'SampleFormEnum': + """ + Return the default sample form (POWDER). + + Returns + ------- + 'SampleFormEnum' + The default enum member. + """ return cls.POWDER def description(self) -> str: + """ + Return a human-readable description of this sample form. + + Returns + ------- + str + Description string for the current enum member. + """ if self is SampleFormEnum.POWDER: return 'Powdered or polycrystalline sample.' elif self is SampleFormEnum.SINGLE_CRYSTAL: @@ -30,9 +46,25 @@ class ScatteringTypeEnum(str, Enum): @classmethod def default(cls) -> 'ScatteringTypeEnum': + """ + Return the default scattering type (BRAGG). + + Returns + ------- + 'ScatteringTypeEnum' + The default enum member. + """ return cls.BRAGG def description(self) -> str: + """ + Return a human-readable description of this scattering type. + + Returns + ------- + str + Description string for the current enum member. + """ if self is ScatteringTypeEnum.BRAGG: return 'Bragg diffraction for conventional structure refinement.' elif self is ScatteringTypeEnum.TOTAL: @@ -47,9 +79,25 @@ class RadiationProbeEnum(str, Enum): @classmethod def default(cls) -> 'RadiationProbeEnum': + """ + Return the default radiation probe (NEUTRON). + + Returns + ------- + 'RadiationProbeEnum' + The default enum member. + """ return cls.NEUTRON def description(self) -> str: + """ + Return a human-readable description of this radiation probe. + + Returns + ------- + str + Description string for the current enum member. + """ if self is RadiationProbeEnum.NEUTRON: return 'Neutron diffraction.' elif self is RadiationProbeEnum.XRAY: @@ -65,9 +113,25 @@ class BeamModeEnum(str, Enum): @classmethod def default(cls) -> 'BeamModeEnum': + """ + Return the default beam mode (CONSTANT_WAVELENGTH). + + Returns + ------- + 'BeamModeEnum' + The default enum member. + """ return cls.CONSTANT_WAVELENGTH def description(self) -> str: + """ + Return a human-readable description of this beam mode. + + Returns + ------- + str + Description string for the current enum member. + """ if self is BeamModeEnum.CONSTANT_WAVELENGTH: return 'Constant wavelength (CW) diffraction.' elif self is BeamModeEnum.TIME_OF_FLIGHT: @@ -104,6 +168,23 @@ def default( scattering_type: ScatteringTypeEnum | None = None, beam_mode: BeamModeEnum | None = None, ) -> 'PeakProfileTypeEnum': + """ + Return the default peak profile type for a given mode. + + Parameters + ---------- + scattering_type : ScatteringTypeEnum | None, default=None + Scattering type; defaults to + ``ScatteringTypeEnum.default()`` when ``None``. + beam_mode : BeamModeEnum | None, default=None + Beam mode; defaults to ``BeamModeEnum.default()`` when + ``None``. + + Returns + ------- + 'PeakProfileTypeEnum' + The default profile type for the given combination. + """ if scattering_type is None: scattering_type = ScatteringTypeEnum.default() if beam_mode is None: @@ -119,6 +200,14 @@ def default( }[(scattering_type, beam_mode)] def description(self) -> str: + """ + Return a human-readable description of this peak profile type. + + Returns + ------- + str + Description string for the current enum member. + """ if self is PeakProfileTypeEnum.PSEUDO_VOIGT: return 'Pseudo-Voigt profile' elif self is PeakProfileTypeEnum.SPLIT_PSEUDO_VOIGT: diff --git a/src/easydiffraction/datablocks/experiment/item/factory.py b/src/easydiffraction/datablocks/experiment/item/factory.py index 3e5aebb5..6406ed30 100644 --- a/src/easydiffraction/datablocks/experiment/item/factory.py +++ b/src/easydiffraction/datablocks/experiment/item/factory.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Factory for creating experiment instances from various inputs. +""" +Factory for creating experiment instances from various inputs. Provides individual class methods for each creation pathway: ``from_cif_path``, ``from_cif_str``, ``from_data_path``, and @@ -55,7 +56,7 @@ class ExperimentFactory(FactoryBase): } # TODO: Add to core/factory.py? - def __init__(self): + def __init__(self) -> None: log.error( 'Experiment objects must be created using class methods such as ' '`ExperimentFactory.from_cif_str(...)`, etc.' @@ -74,9 +75,7 @@ def _create_experiment_type( radiation_probe: str | None = None, scattering_type: str | None = None, ) -> ExperimentType: - """Construct an ExperimentType, using defaults for omitted - values. - """ + """Construct ExperimentType with defaults for omitted values.""" # Note: validation of input values is done via Descriptor setter # methods @@ -95,7 +94,7 @@ def _create_experiment_type( @classmethod @typechecked - def _resolve_class(cls, expt_type: ExperimentType): + def _resolve_class(cls, expt_type: ExperimentType) -> type: """Look up the experiment class from the type enums.""" tag = cls.default_tag( scattering_type=expt_type.scattering_type.value, @@ -140,16 +139,25 @@ def from_scratch( radiation_probe: str | None = None, scattering_type: str | None = None, ) -> ExperimentBase: - """Create an experiment without measured data. - - Args: - name: Experiment identifier. - sample_form: Sample form (e.g. ``'powder'``). - beam_mode: Beam mode (e.g. ``'constant wavelength'``). - radiation_probe: Radiation probe (e.g. ``'neutron'``). - scattering_type: Scattering type (e.g. ``'bragg'``). - - Returns: + """ + Create an experiment without measured data. + + Parameters + ---------- + name : str + Experiment identifier. + sample_form : str | None, default=None + Sample form (e.g. ``'powder'``). + beam_mode : str | None, default=None + Beam mode (e.g. ``'constant wavelength'``). + radiation_probe : str | None, default=None + Radiation probe (e.g. ``'neutron'``). + scattering_type : str | None, default=None + Scattering type (e.g. ``'bragg'``). + + Returns + ------- + ExperimentBase An experiment instance with only metadata. """ expt_type = cls._create_experiment_type( @@ -169,12 +177,17 @@ def from_cif_str( cls, cif_str: str, ) -> ExperimentBase: - """Create an experiment from a CIF string. + """ + Create an experiment from a CIF string. - Args: - cif_str: Full CIF document as a string. + Parameters + ---------- + cif_str : str + Full CIF document as a string. - Returns: + Returns + ------- + ExperimentBase A populated experiment instance. """ doc = document_from_string(cif_str) @@ -188,12 +201,17 @@ def from_cif_path( cls, cif_path: str, ) -> ExperimentBase: - """Create an experiment from a CIF file path. + """ + Create an experiment from a CIF file path. - Args: - cif_path: Path to a CIF file. + Parameters + ---------- + cif_path : str + Path to a CIF file. - Returns: + Returns + ------- + ExperimentBase A populated experiment instance. """ doc = document_from_path(cif_path) @@ -212,17 +230,27 @@ def from_data_path( radiation_probe: str | None = None, scattering_type: str | None = None, ) -> ExperimentBase: - """Create an experiment from a raw data ASCII file. - - Args: - name: Experiment identifier. - data_path: Path to the measured data file. - sample_form: Sample form (e.g. ``'powder'``). - beam_mode: Beam mode (e.g. ``'constant wavelength'``). - radiation_probe: Radiation probe (e.g. ``'neutron'``). - scattering_type: Scattering type (e.g. ``'bragg'``). - - Returns: + """ + Create an experiment from a raw data ASCII file. + + Parameters + ---------- + name : str + Experiment identifier. + data_path : str + Path to the measured data file. + sample_form : str | None, default=None + Sample form (e.g. ``'powder'``). + beam_mode : str | None, default=None + Beam mode (e.g. ``'constant wavelength'``). + radiation_probe : str | None, default=None + Radiation probe (e.g. ``'neutron'``). + scattering_type : str | None, default=None + Scattering type (e.g. ``'bragg'``). + + Returns + ------- + ExperimentBase An experiment instance with measured data attached. """ expt_obj = cls.from_scratch( diff --git a/src/easydiffraction/datablocks/experiment/item/total_pd.py b/src/easydiffraction/datablocks/experiment/item/total_pd.py index 881dd0ce..ade11e6f 100644 --- a/src/easydiffraction/datablocks/experiment/item/total_pd.py +++ b/src/easydiffraction/datablocks/experiment/item/total_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -38,15 +38,14 @@ def __init__( self, name: str, type: ExperimentType, - ): + ) -> None: super().__init__(name=name, type=type) - def _load_ascii_data_to_experiment(self, data_path): - """Loads x, y, sy values from an ASCII data file into the - experiment. + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + """ + Load x, y, sy values from an ASCII file into the experiment. - The file must be structured as: - x y sy + The file must be structured as: x y sy """ try: from diffpy.utils.parsers.loaddata import loadData diff --git a/src/easydiffraction/datablocks/structure/__init__.py b/src/easydiffraction/datablocks/structure/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/datablocks/structure/__init__.py +++ b/src/easydiffraction/datablocks/structure/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/datablocks/structure/categories/__init__.py b/src/easydiffraction/datablocks/structure/categories/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/datablocks/structure/categories/__init__.py +++ b/src/easydiffraction/datablocks/structure/categories/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/datablocks/structure/categories/atom_sites/__init__.py b/src/easydiffraction/datablocks/structure/categories/atom_sites/__init__.py index cb4c1750..7bd7b9ad 100644 --- a/src/easydiffraction/datablocks/structure/categories/atom_sites/__init__.py +++ b/src/easydiffraction/datablocks/structure/categories/atom_sites/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.structure.categories.atom_sites.default import AtomSite diff --git a/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py b/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py index 76d890d5..265c3c62 100644 --- a/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py +++ b/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Atom site category. +""" +Atom site category. Defines :class:`AtomSite` items and :class:`AtomSites` collection used in crystallographic structures. @@ -25,7 +26,8 @@ class AtomSite(CategoryItem): - """Single atom site with fractional coordinates and ADP. + """ + Single atom site with fractional coordinates and ADP. Attributes are represented by descriptors to support validation and CIF serialization. @@ -138,19 +140,25 @@ def __init__(self) -> None: @property def _type_symbol_allowed_values(self) -> list[str]: - """Return chemical symbols accepted by *cryspy*. + """ + Return chemical symbols accepted by *cryspy*. - Returns: - list[str]: Unique element/isotope symbols from the database. + Returns + ------- + list[str] + Unique element/isotope symbols from the database. """ return list({key[1] for key in DATABASE['Isotopes']}) @property def _wyckoff_letter_allowed_values(self) -> list[str]: - """Return allowed Wyckoff-letter symbols. + """ + Return allowed Wyckoff-letter symbols. - Returns: - list[str]: Currently a hard-coded placeholder list. + Returns + ------- + list[str] + Currently a hard-coded placeholder list. """ # TODO: Need to now current space group. How to access it? Via # parent Cell? Then letters = @@ -160,10 +168,13 @@ def _wyckoff_letter_allowed_values(self) -> list[str]: @property def _wyckoff_letter_default_value(self) -> str: - """Return the default Wyckoff letter. + """ + Return the default Wyckoff letter. - Returns: - str: First element of the allowed values list. + Returns + ------- + str + First element of the allowed values list. """ # TODO: What to pass as default? return self._wyckoff_letter_allowed_values[0] @@ -174,165 +185,132 @@ def _wyckoff_letter_default_value(self) -> str: @property def label(self) -> StringDescriptor: - """Unique label for this atom site. + """ + Unique identifier for the atom site. - Returns: - StringDescriptor: Descriptor holding the site label. + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. """ return self._label @label.setter def label(self, value: str) -> None: - """Set the atom-site label. - - Args: - value (str): New label string. - """ self._label.value = value @property def type_symbol(self) -> StringDescriptor: - """Chemical element or isotope symbol. + """ + Chemical symbol of the atom at this site. - Returns: - StringDescriptor: Descriptor holding the type symbol. + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. """ return self._type_symbol @type_symbol.setter def type_symbol(self, value: str) -> None: - """Set the chemical element or isotope symbol. - - Args: - value (str): New type symbol (must be in the *cryspy* - database). - """ self._type_symbol.value = value @property def adp_type(self) -> StringDescriptor: - """Type of atomic displacement parameter (e.g. ``'Biso'``). + """ + ADP type used (e.g., Biso, Uiso, Uani, Bani). - Returns: - StringDescriptor: Descriptor holding the ADP type. + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. """ return self._adp_type @adp_type.setter def adp_type(self, value: str) -> None: - """Set the ADP type. - - Args: - value (str): New ADP type string. - """ self._adp_type.value = value @property def wyckoff_letter(self) -> StringDescriptor: - """Wyckoff letter for the symmetry site. + """ + Wyckoff letter for the atom site symmetry position. - Returns: - StringDescriptor: Descriptor holding the Wyckoff letter. + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. """ return self._wyckoff_letter @wyckoff_letter.setter def wyckoff_letter(self, value: str) -> None: - """Set the Wyckoff letter. - - Args: - value (str): New Wyckoff letter. - """ self._wyckoff_letter.value = value @property def fract_x(self) -> Parameter: - """Fractional *x*-coordinate within the unit cell. + """ + Fractional x-coordinate of the atom site within the unit cell. - Returns: - Parameter: Descriptor for the *x* coordinate. + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._fract_x @fract_x.setter def fract_x(self, value: float) -> None: - """Set the fractional *x*-coordinate. - - Args: - value (float): New *x* coordinate. - """ self._fract_x.value = value @property def fract_y(self) -> Parameter: - """Fractional *y*-coordinate within the unit cell. + """ + Fractional y-coordinate of the atom site within the unit cell. - Returns: - Parameter: Descriptor for the *y* coordinate. + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._fract_y @fract_y.setter def fract_y(self, value: float) -> None: - """Set the fractional *y*-coordinate. - - Args: - value (float): New *y* coordinate. - """ self._fract_y.value = value @property def fract_z(self) -> Parameter: - """Fractional *z*-coordinate within the unit cell. + """ + Fractional z-coordinate of the atom site within the unit cell. - Returns: - Parameter: Descriptor for the *z* coordinate. + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._fract_z @fract_z.setter def fract_z(self, value: float) -> None: - """Set the fractional *z*-coordinate. - - Args: - value (float): New *z* coordinate. - """ self._fract_z.value = value @property def occupancy(self) -> Parameter: - """Site occupancy fraction. + """ + Occupancy fraction of the atom type at this site. - Returns: - Parameter: Descriptor for the occupancy (0–1). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._occupancy @occupancy.setter def occupancy(self, value: float) -> None: - """Set the site occupancy. - - Args: - value (float): New occupancy fraction. - """ self._occupancy.value = value @property def b_iso(self) -> Parameter: - r"""Isotropic atomic displacement parameter (*B*-factor). + """ + Isotropic ADP for the atom site (Ų). - Returns: - Parameter: Descriptor for *B*\_iso (Ų). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._b_iso @b_iso.setter def b_iso(self, value: float) -> None: - r"""Set the isotropic displacement parameter. - - Args: - value (float): New *B*\_iso value in Ų. - """ self._b_iso.value = value @@ -354,7 +332,8 @@ def __init__(self) -> None: # ------------------------------------------------------------------ def _apply_atomic_coordinates_symmetry_constraints(self) -> None: - """Apply symmetry rules to fractional coordinates of every site. + """ + Apply symmetry rules to fractional coordinates of every site. Uses the parent structure's space-group symbol, IT coordinate system code and each atom's Wyckoff letter. Atoms without a @@ -387,11 +366,14 @@ def _update( self, called_by_minimizer: bool = False, ) -> None: - """Recalculate atom sites after a change. + """ + Recalculate atom sites after a change. - Args: - called_by_minimizer (bool): Whether the update was triggered - by the fitting minimizer. Currently unused. + Parameters + ---------- + called_by_minimizer : bool, default=False + Whether the update was triggered by the fitting minimizer. + Currently unused. """ del called_by_minimizer diff --git a/src/easydiffraction/datablocks/structure/categories/atom_sites/factory.py b/src/easydiffraction/datablocks/structure/categories/atom_sites/factory.py index e233d0bd..c91b3dda 100644 --- a/src/easydiffraction/datablocks/structure/categories/atom_sites/factory.py +++ b/src/easydiffraction/datablocks/structure/categories/atom_sites/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Atom-sites factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/structure/categories/cell/__init__.py b/src/easydiffraction/datablocks/structure/categories/cell/__init__.py index 08773b3e..16f6cab2 100644 --- a/src/easydiffraction/datablocks/structure/categories/cell/__init__.py +++ b/src/easydiffraction/datablocks/structure/categories/cell/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.structure.categories.cell.default import Cell diff --git a/src/easydiffraction/datablocks/structure/categories/cell/default.py b/src/easydiffraction/datablocks/structure/categories/cell/default.py index e07e20e0..53cc98ee 100644 --- a/src/easydiffraction/datablocks/structure/categories/cell/default.py +++ b/src/easydiffraction/datablocks/structure/categories/cell/default.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Unit cell parameters category for structures.""" @@ -16,8 +16,8 @@ @CellFactory.register class Cell(CategoryItem): - """Unit cell with lengths *a*, *b*, *c* and angles *alpha*, *beta*, - *gamma*. + """ + Unit cell with lengths a, b, c and angles alpha, beta, gamma. All six lattice parameters are exposed as :class:`Parameter` descriptors supporting validation, fitting and CIF serialization. @@ -34,7 +34,7 @@ def __init__(self) -> None: self._length_a = Parameter( name='length_a', - description='Length of the a axis of the unit cell.', + description='Length of the a axis of the unit cell', units='Å', value_spec=AttributeSpec( default=10.0, @@ -44,7 +44,7 @@ def __init__(self) -> None: ) self._length_b = Parameter( name='length_b', - description='Length of the b axis of the unit cell.', + description='Length of the b axis of the unit cell', units='Å', value_spec=AttributeSpec( default=10.0, @@ -54,7 +54,7 @@ def __init__(self) -> None: ) self._length_c = Parameter( name='length_c', - description='Length of the c axis of the unit cell.', + description='Length of the c axis of the unit cell', units='Å', value_spec=AttributeSpec( default=10.0, @@ -64,7 +64,7 @@ def __init__(self) -> None: ) self._angle_alpha = Parameter( name='angle_alpha', - description='Angle between edges b and c.', + description='Angle between edges b and c', units='deg', value_spec=AttributeSpec( default=90.0, @@ -74,7 +74,7 @@ def __init__(self) -> None: ) self._angle_beta = Parameter( name='angle_beta', - description='Angle between edges a and c.', + description='Angle between edges a and c', units='deg', value_spec=AttributeSpec( default=90.0, @@ -84,7 +84,7 @@ def __init__(self) -> None: ) self._angle_gamma = Parameter( name='angle_gamma', - description='Angle between edges a and b.', + description='Angle between edges a and b', units='deg', value_spec=AttributeSpec( default=90.0, @@ -100,7 +100,8 @@ def __init__(self) -> None: # ------------------------------------------------------------------ def _apply_cell_symmetry_constraints(self) -> None: - """Apply symmetry constraints to cell parameters in place. + """ + Apply symmetry constraints to cell parameters in place. Uses the parent structure's space-group symbol to determine which lattice parameters are dependent and sets them @@ -132,11 +133,14 @@ def _update( self, called_by_minimizer: bool = False, ) -> None: - """Recalculate cell parameters after a change. + """ + Recalculate cell parameters after a change. - Args: - called_by_minimizer (bool): Whether the update was triggered - by the fitting minimizer. Currently unused. + Parameters + ---------- + called_by_minimizer : bool, default=False + Whether the update was triggered by the fitting minimizer. + Currently unused. """ del called_by_minimizer # TODO: ??? @@ -148,108 +152,84 @@ def _update( @property def length_a(self) -> Parameter: - """Length of the *a* axis. + """ + Length of the a axis of the unit cell (Å). - Returns: - Parameter: Descriptor for lattice parameter *a* (Å). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._length_a @length_a.setter def length_a(self, value: float) -> None: - """Set the length of the *a* axis. - - Args: - value (float): New length in ångströms. - """ self._length_a.value = value @property def length_b(self) -> Parameter: - """Length of the *b* axis. + """ + Length of the b axis of the unit cell (Å). - Returns: - Parameter: Descriptor for lattice parameter *b* (Å). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._length_b @length_b.setter def length_b(self, value: float) -> None: - """Set the length of the *b* axis. - - Args: - value (float): New length in ångströms. - """ self._length_b.value = value @property def length_c(self) -> Parameter: - """Length of the *c* axis. + """ + Length of the c axis of the unit cell (Å). - Returns: - Parameter: Descriptor for lattice parameter *c* (Å). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._length_c @length_c.setter def length_c(self, value: float) -> None: - """Set the length of the *c* axis. - - Args: - value (float): New length in ångströms. - """ self._length_c.value = value @property def angle_alpha(self) -> Parameter: - """Angle between edges *b* and *c*. + """ + Angle between edges b and c (deg). - Returns: - Parameter: Descriptor for angle *α* (degrees). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._angle_alpha @angle_alpha.setter def angle_alpha(self, value: float) -> None: - """Set the angle between edges *b* and *c*. - - Args: - value (float): New angle in degrees. - """ self._angle_alpha.value = value @property def angle_beta(self) -> Parameter: - """Angle between edges *a* and *c*. + """ + Angle between edges a and c (deg). - Returns: - Parameter: Descriptor for angle *β* (degrees). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._angle_beta @angle_beta.setter def angle_beta(self, value: float) -> None: - """Set the angle between edges *a* and *c*. - - Args: - value (float): New angle in degrees. - """ self._angle_beta.value = value @property def angle_gamma(self) -> Parameter: - """Angle between edges *a* and *b*. + """ + Angle between edges a and b (deg). - Returns: - Parameter: Descriptor for angle *γ* (degrees). + Reading this property returns the underlying ``Parameter`` + object. Assigning to it updates the parameter value. """ return self._angle_gamma @angle_gamma.setter def angle_gamma(self, value: float) -> None: - """Set the angle between edges *a* and *b*. - - Args: - value (float): New angle in degrees. - """ self._angle_gamma.value = value diff --git a/src/easydiffraction/datablocks/structure/categories/cell/factory.py b/src/easydiffraction/datablocks/structure/categories/cell/factory.py index c5fde941..6817b2d7 100644 --- a/src/easydiffraction/datablocks/structure/categories/cell/factory.py +++ b/src/easydiffraction/datablocks/structure/categories/cell/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Cell factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/structure/categories/space_group/__init__.py b/src/easydiffraction/datablocks/structure/categories/space_group/__init__.py index daf02947..a8b33e62 100644 --- a/src/easydiffraction/datablocks/structure/categories/space_group/__init__.py +++ b/src/easydiffraction/datablocks/structure/categories/space_group/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.structure.categories.space_group.default import SpaceGroup diff --git a/src/easydiffraction/datablocks/structure/categories/space_group/default.py b/src/easydiffraction/datablocks/structure/categories/space_group/default.py index 7076d272..a91cc554 100644 --- a/src/easydiffraction/datablocks/structure/categories/space_group/default.py +++ b/src/easydiffraction/datablocks/structure/categories/space_group/default.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Space group category for crystallographic structures.""" @@ -21,8 +21,8 @@ @SpaceGroupFactory.register class SpaceGroup(CategoryItem): - """Space group with Hermann–Mauguin symbol and IT coordinate system - code. + """ + Space group with H-M symbol and IT coordinate system code. Holds the space-group symbol (``name_h_m``) and the International Tables coordinate-system qualifier (``it_coordinate_system_code``). @@ -84,28 +84,30 @@ def __init__(self) -> None: # ------------------------------------------------------------------ def _reset_it_coordinate_system_code(self) -> None: - """Reset the IT coordinate system code to the default for the - current group. - """ + """Reset IT coordinate system code to default for this group.""" self._it_coordinate_system_code.value = self._it_coordinate_system_code_default_value @property def _name_h_m_allowed_values(self) -> list[str]: - """Return the list of recognised Hermann–Mauguin short symbols. + """ + Return the list of recognised Hermann–Mauguin short symbols. - Returns: - list[str]: All short H-M symbols known to *cryspy*. + Returns + ------- + list[str] + All short H-M symbols known to *cryspy*. """ return ACCESIBLE_NAME_HM_SHORT @property def _it_coordinate_system_code_allowed_values(self) -> list[str]: - """Return allowed IT coordinate system codes for the current - group. + """ + Return allowed IT coordinate system codes for the current group. - Returns: - list[str]: Coordinate-system codes, or ``['']`` when none - are defined. + Returns + ------- + list[str] + Coordinate-system codes, or ``['']`` when none are defined. """ name = self.name_h_m.value it_number = get_it_number_by_name_hm_short(name) @@ -115,10 +117,13 @@ def _it_coordinate_system_code_allowed_values(self) -> list[str]: @property def _it_coordinate_system_code_default_value(self) -> str: - """Return the default IT coordinate system code. + """ + Return the default IT coordinate system code. - Returns: - str: First element of the allowed codes list. + Returns + ------- + str + First element of the allowed codes list. """ return self._it_coordinate_system_code_allowed_values[0] @@ -128,40 +133,31 @@ def _it_coordinate_system_code_default_value(self) -> str: @property def name_h_m(self) -> StringDescriptor: - """Hermann–Mauguin symbol of the space group. + """ + Hermann-Mauguin symbol of the space group. - Returns: - StringDescriptor: Descriptor holding the H-M symbol. + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. """ return self._name_h_m @name_h_m.setter def name_h_m(self, value: str) -> None: - """Set the Hermann–Mauguin symbol and reset the coordinate- - system code. - - Args: - value (str): New H-M symbol (must be a recognised short - symbol). - """ self._name_h_m.value = value self._reset_it_coordinate_system_code() @property def it_coordinate_system_code(self) -> StringDescriptor: - """International Tables coordinate-system code. + """ + A qualifier identifying which setting in IT is used. - Returns: - StringDescriptor: Descriptor holding the IT code. + Reading this property returns the underlying + ``StringDescriptor`` object. Assigning to it updates the + parameter value. """ return self._it_coordinate_system_code @it_coordinate_system_code.setter def it_coordinate_system_code(self, value: str) -> None: - """Set the IT coordinate-system code. - - Args: - value (str): New coordinate-system code (must be allowed for - the current space group). - """ self._it_coordinate_system_code.value = value diff --git a/src/easydiffraction/datablocks/structure/categories/space_group/factory.py b/src/easydiffraction/datablocks/structure/categories/space_group/factory.py index 87807cef..9ef8611d 100644 --- a/src/easydiffraction/datablocks/structure/categories/space_group/factory.py +++ b/src/easydiffraction/datablocks/structure/categories/space_group/factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Space-group factory — delegates entirely to ``FactoryBase``.""" diff --git a/src/easydiffraction/datablocks/structure/collection.py b/src/easydiffraction/datablocks/structure/collection.py index ecc8f26e..801b416a 100644 --- a/src/easydiffraction/datablocks/structure/collection.py +++ b/src/easydiffraction/datablocks/structure/collection.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Collection of structure data blocks.""" @@ -11,7 +11,8 @@ class Structures(DatablockCollection): - """Ordered collection of :class:`Structure` instances. + """ + Ordered collection of :class:`Structure` instances. Provides convenience ``add_from_*`` methods that mirror the :class:`StructureFactory` classmethods plus a bare :meth:`add` for @@ -33,10 +34,13 @@ def create( *, name: str, ) -> None: - """Create a minimal structure and add it to the collection. + """ + Create a minimal structure and add it to the collection. - Args: - name (str): Identifier for the new structure. + Parameters + ---------- + name : str + Identifier for the new structure. """ structure = StructureFactory.from_scratch(name=name) self.add(structure) @@ -47,10 +51,13 @@ def add_from_cif_str( self, cif_str: str, ) -> None: - """Create a structure from CIF content and add it. + """ + Create a structure from CIF content and add it. - Args: - cif_str (str): CIF file content as a string. + Parameters + ---------- + cif_str : str + CIF file content as a string. """ structure = StructureFactory.from_cif_str(cif_str) self.add(structure) @@ -61,10 +68,13 @@ def add_from_cif_path( self, cif_path: str, ) -> None: - """Create a structure from a CIF file and add it. + """ + Create a structure from a CIF file and add it. - Args: - cif_path (str): Filesystem path to a CIF file. + Parameters + ---------- + cif_path : str + Filesystem path to a CIF file. """ structure = StructureFactory.from_cif_path(cif_path) self.add(structure) diff --git a/src/easydiffraction/datablocks/structure/item/__init__.py b/src/easydiffraction/datablocks/structure/item/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/datablocks/structure/item/__init__.py +++ b/src/easydiffraction/datablocks/structure/item/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/datablocks/structure/item/base.py b/src/easydiffraction/datablocks/structure/item/base.py index cd517785..80d8f76a 100644 --- a/src/easydiffraction/datablocks/structure/item/base.py +++ b/src/easydiffraction/datablocks/structure/item/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Structure datablock item.""" @@ -40,20 +40,26 @@ def __init__( @property def name(self) -> str: - """Name identifier for this structure. + """ + Name identifier for this structure. - Returns: - str: The structure's name. + Returns + ------- + str + The structure's name. """ return self._name @name.setter @typechecked def name(self, new: str) -> None: - """Set the name identifier for this structure. + """ + Set the name identifier for this structure. - Args: - new (str): New name string. + Parameters + ---------- + new : str + New name string. """ self._name = new @@ -69,10 +75,13 @@ def cell(self) -> Cell: @cell.setter @typechecked def cell(self, new: Cell) -> None: - """Replace the unit-cell category for this structure. + """ + Replace the unit-cell category for this structure. - Args: - new (Cell): New unit-cell instance. + Parameters + ---------- + new : Cell + New unit-cell instance. """ self._cell = new @@ -83,10 +92,13 @@ def cell_type(self) -> str: @cell_type.setter def cell_type(self, new_type: str) -> None: - """Switch to a different unit-cell type. + """ + Switch to a different unit-cell type. - Args: - new_type: Cell tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Cell tag (e.g. ``'default'``). """ supported_tags = CellFactory.supported_tags() if new_type not in supported_tags: @@ -122,10 +134,13 @@ def space_group(self) -> SpaceGroup: @space_group.setter @typechecked def space_group(self, new: SpaceGroup) -> None: - """Replace the space-group category for this structure. + """ + Replace the space-group category for this structure. - Args: - new (SpaceGroup): New space-group instance. + Parameters + ---------- + new : SpaceGroup + New space-group instance. """ self._space_group = new @@ -136,10 +151,13 @@ def space_group_type(self) -> str: @space_group_type.setter def space_group_type(self, new_type: str) -> None: - """Switch to a different space-group type. + """ + Switch to a different space-group type. - Args: - new_type: Space-group tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Space-group tag (e.g. ``'default'``). """ supported_tags = SpaceGroupFactory.supported_tags() if new_type not in supported_tags: @@ -175,10 +193,13 @@ def atom_sites(self) -> AtomSites: @atom_sites.setter @typechecked def atom_sites(self, new: AtomSites) -> None: - """Replace the atom-sites collection for this structure. + """ + Replace the atom-sites collection for this structure. - Args: - new (AtomSites): New atom-sites collection. + Parameters + ---------- + new : AtomSites + New atom-sites collection. """ self._atom_sites = new @@ -189,10 +210,13 @@ def atom_sites_type(self) -> str: @atom_sites_type.setter def atom_sites_type(self, new_type: str) -> None: - """Switch to a different atom-sites collection type. + """ + Switch to a different atom-sites collection type. - Args: - new_type: Atom-sites tag (e.g. ``'default'``). + Parameters + ---------- + new_type : str + Atom-sites tag (e.g. ``'default'``). """ supported_tags = AtomSitesFactory.supported_tags() if new_type not in supported_tags: @@ -221,9 +245,7 @@ def show_current_atom_sites_type(self) -> None: # ------------------------------------------------------------------ def show(self) -> None: - """Display an ASCII projection of the structure on a 2D - plane. - """ + """Display an ASCII projection of the structure in 2D.""" console.paragraph(f"Structure 🧩 '{self.name}'") console.print('Not implemented yet.') diff --git a/src/easydiffraction/datablocks/structure/item/factory.py b/src/easydiffraction/datablocks/structure/item/factory.py index a2067dff..567d26dc 100644 --- a/src/easydiffraction/datablocks/structure/item/factory.py +++ b/src/easydiffraction/datablocks/structure/item/factory.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Factory for creating structure instances from various inputs. +""" +Factory for creating structure instances from various inputs. Provides individual class methods for each creation pathway: ``from_scratch``, ``from_cif_path``, or ``from_cif_str``. @@ -26,7 +27,7 @@ class StructureFactory: """Create :class:`Structure` instances from supported inputs.""" - def __init__(self): + def __init__(self) -> None: log.error( 'Structure objects must be created using class methods such as ' '`StructureFactory.from_cif_str(...)`, etc.' @@ -42,13 +43,18 @@ def _from_gemmi_block( cls, block: gemmi.cif.Block, ) -> Structure: - """Build a structure from a single *gemmi* CIF block. + """ + Build a structure from a single *gemmi* CIF block. - Args: - block (gemmi.cif.Block): Parsed CIF data block. + Parameters + ---------- + block : gemmi.cif.Block + Parsed CIF data block. - Returns: - Structure: A fully populated structure instance. + Returns + ------- + Structure + A fully populated structure instance. """ name = name_from_block(block) structure = Structure(name=name) @@ -67,13 +73,18 @@ def from_scratch( *, name: str, ) -> Structure: - """Create a minimal default structure. + """ + Create a minimal default structure. - Args: - name (str): Identifier for the new structure. + Parameters + ---------- + name : str + Identifier for the new structure. - Returns: - Structure: An empty structure with default categories. + Returns + ------- + Structure + An empty structure with default categories. """ return Structure(name=name) @@ -84,13 +95,18 @@ def from_cif_str( cls, cif_str: str, ) -> Structure: - """Create a structure by parsing a CIF string. + """ + Create a structure by parsing a CIF string. - Args: - cif_str (str): Raw CIF content. + Parameters + ---------- + cif_str : str + Raw CIF content. - Returns: - Structure: A populated structure instance. + Returns + ------- + Structure + A populated structure instance. """ doc = document_from_string(cif_str) block = pick_sole_block(doc) @@ -103,13 +119,18 @@ def from_cif_path( cls, cif_path: str, ) -> Structure: - """Create a structure by reading and parsing a CIF file. + """ + Create a structure by reading and parsing a CIF file. - Args: - cif_path (str): Filesystem path to a CIF file. + Parameters + ---------- + cif_path : str + Filesystem path to a CIF file. - Returns: - Structure: A populated structure instance. + Returns + ------- + Structure + A populated structure instance. """ doc = document_from_path(cif_path) block = pick_sole_block(doc) diff --git a/src/easydiffraction/display/__init__.py b/src/easydiffraction/display/__init__.py index 265560b2..25bccda4 100644 --- a/src/easydiffraction/display/__init__.py +++ b/src/easydiffraction/display/__init__.py @@ -1,14 +1,15 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Display subsystem for tables and plots. +""" +Display subsystem for tables and plots. -This package contains user-facing facades and backend implementations -to render tabular data and plots in different environments. +This package contains user-facing facades and backend implementations to +render tabular data and plots in different environments. - Tables: see :mod:`easydiffraction.display.tables` and the engines in - :mod:`easydiffraction.display.tablers`. -- Plots: see :mod:`easydiffraction.display.plotting` and the engines in - :mod:`easydiffraction.display.plotters`. +:mod:`easydiffraction.display.tablers`. - Plots: see +:mod:`easydiffraction.display.plotting` and the engines in +:mod:`easydiffraction.display.plotters`. """ # TODO: The following works in Jupyter, but breaks MkDocs builds. diff --git a/src/easydiffraction/display/base.py b/src/easydiffraction/display/base.py index d97f7be9..6ffc0698 100644 --- a/src/easydiffraction/display/base.py +++ b/src/easydiffraction/display/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Common base classes for display components and their factories.""" @@ -6,7 +6,6 @@ from abc import ABC from abc import abstractmethod -from typing import Any from typing import List from typing import Tuple @@ -18,14 +17,15 @@ class RendererBase(SingletonBase, ABC): - """Base class for display components with pluggable engines. + """ + Base class for display components with pluggable engines. Subclasses provide a factory and a default engine. This class manages the active backend instance and exposes helpers to inspect supported engines in a table-friendly format. """ - def __init__(self): + def __init__(self) -> None: self._engine = self._default_engine() self._backend = self._factory().create(self._engine) @@ -43,10 +43,27 @@ def _default_engine(cls) -> str: @property def engine(self) -> str: + """ + Return the name of the currently active rendering engine. + + Returns + ------- + str + Identifier of the active engine. + """ return self._engine @engine.setter def engine(self, new_engine: str) -> None: + """ + Switch to a different rendering engine. + + Parameters + ---------- + new_engine : str + Identifier of the engine to activate. Must be a key + returned by ``_factory()._registry()``. + """ if new_engine == self._engine: log.info(f"Engine is already set to '{new_engine}'. No change made.") return @@ -90,18 +107,25 @@ class RendererFactoryBase(ABC): """Base factory that manages discovery and creation of backends.""" @classmethod - def create(cls, engine_name: str) -> Any: - """Create a backend instance for the given engine. + def create(cls, engine_name: str) -> object: + """ + Create a backend instance for the given engine. - Args: - engine_name: Identifier of the engine to instantiate as - listed in ``_registry()``. + Parameters + ---------- + engine_name : str + Identifier of the engine to instantiate as listed in + ``_registry()``. - Returns: + Returns + ------- + object A new backend instance corresponding to ``engine_name``. - Raises: - ValueError: If the engine name is not supported. + Raises + ------ + ValueError + If the engine name is not supported. """ registry = cls._registry() if engine_name not in registry: @@ -117,16 +141,15 @@ def supported_engines(cls) -> List[str]: @classmethod def descriptions(cls) -> List[Tuple[str, str]]: - """Return pairs of engine name and human-friendly - description. - """ + """Return (name, description) pairs for each engine.""" items = cls._registry().items() return [(name, config.get('description')) for name, config in items] @classmethod @abstractmethod def _registry(cls) -> dict: - """Return engine registry. Implementations must provide this. + """ + Return engine registry. Implementations must provide this. The returned mapping should have keys as engine names and values as a config dict with 'description' and 'class'. Lazy imports diff --git a/src/easydiffraction/display/plotters/__init__.py b/src/easydiffraction/display/plotters/__init__.py index 14dae26a..09931ae8 100644 --- a/src/easydiffraction/display/plotters/__init__.py +++ b/src/easydiffraction/display/plotters/__init__.py @@ -1,10 +1,11 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Plotting backends. +""" +Plotting backends. This subpackage implements plotting engines used by the high-level plotting facade: -- :mod:`.ascii` for terminal-friendly ASCII plots. -- :mod:`.plotly` for interactive plots in notebooks or browsers. +- :mod:`.ascii` for terminal-friendly ASCII plots. - :mod:`.plotly` for +interactive plots in notebooks or browsers. """ diff --git a/src/easydiffraction/display/plotters/ascii.py b/src/easydiffraction/display/plotters/ascii.py index 7b5ff6a8..8735a8a0 100644 --- a/src/easydiffraction/display/plotters/ascii.py +++ b/src/easydiffraction/display/plotters/ascii.py @@ -1,10 +1,11 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""ASCII plotting backend. +""" +ASCII plotting backend. -Renders compact line charts in the terminal using -``asciichartpy``. This backend is well suited for quick feedback in -CLI environments and keeps a consistent API with other plotters. +Renders compact line charts in the terminal using ``asciichartpy``. This +backend is well suited for quick feedback in CLI environments and keeps +a consistent API with other plotters. """ import asciichartpy @@ -25,16 +26,21 @@ class AsciiPlotter(PlotterBase): """Terminal-based plotter using ASCII art.""" - def _get_legend_item(self, label): - """Return a colored legend entry for a given series label. + def _get_legend_item(self, label: str) -> str: + """ + Return a colored legend entry for a given series label. - The legend uses a colored line matching the series color and - the human-readable name from :data:`SERIES_CONFIG`. + The legend uses a colored line matching the series color and the + human-readable name from :data:`SERIES_CONFIG`. - Args: - label: Series identifier (e.g., ``'meas'``). + Parameters + ---------- + label : str + Series identifier (e.g., ``'meas'``). - Returns: + Returns + ------- + str A formatted legend string with color escapes. """ color_start = DEFAULT_COLORS[label] @@ -46,27 +52,34 @@ def _get_legend_item(self, label): def plot_powder( self, - x, - y_series, - labels, - axes_labels, - title, - height=None, - ): - """Render a line plot for powder diffraction data. + x: object, + y_series: object, + labels: object, + axes_labels: object, + title: str, + height: int | None = None, + ) -> None: + """ + Render a line plot for powder diffraction data. Suitable for powder diffraction data where intensity is plotted against an x-axis variable (2θ, TOF, d-spacing). Uses ASCII characters for terminal display. - Args: - x: 1D array-like of x values (only used for range - display). - y_series: Sequence of y arrays to plot. - labels: Series identifiers corresponding to y_series. - axes_labels: Ignored; kept for API compatibility. - title: Figure title printed above the chart. - height: Number of text rows to allocate for the chart. + Parameters + ---------- + x : object + 1D array-like of x values (only used for range display). + y_series : object + Sequence of y arrays to plot. + labels : object + Series identifiers corresponding to y_series. + axes_labels : object + Ignored; kept for API compatibility. + title : str + Figure title printed above the chart. + height : int | None, default=None + Number of text rows to allocate for the chart. """ # Intentionally unused; kept for a consistent display API del axes_labels @@ -92,26 +105,34 @@ def plot_powder( def plot_single_crystal( self, - x_calc, - y_meas, - y_meas_su, - axes_labels, - title, - height=None, - ): - """Render a scatter plot for single crystal diffraction data. + x_calc: object, + y_meas: object, + y_meas_su: object, + axes_labels: object, + title: str, + height: int | None = None, + ) -> None: + """ + Render a scatter plot for single crystal diffraction data. Creates an ASCII scatter plot showing measured vs calculated values with a diagonal reference line. - Args: - x_calc: 1D array-like of calculated values (x-axis). - y_meas: 1D array-like of measured values (y-axis). - y_meas_su: 1D array-like of measurement uncertainties - (ignored in ASCII mode). - axes_labels: Pair of strings for the x and y titles. - title: Figure title. - height: Number of text rows for the chart (default: 15). + Parameters + ---------- + x_calc : object + 1D array-like of calculated values (x-axis). + y_meas : object + 1D array-like of measured values (y-axis). + y_meas_su : object + 1D array-like of measurement uncertainties (ignored in ASCII + mode). + axes_labels : object + Pair of strings for the x and y titles. + title : str + Figure title. + height : int | None, default=None + Number of text rows for the chart (default: 15). """ # Intentionally unused; ASCII scatter doesn't show error bars del y_meas_su diff --git a/src/easydiffraction/display/plotters/base.py b/src/easydiffraction/display/plotters/base.py index 7220dfeb..d7ca594c 100644 --- a/src/easydiffraction/display/plotters/base.py +++ b/src/easydiffraction/display/plotters/base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Abstract base and shared constants for plotting backends.""" @@ -18,10 +18,11 @@ class XAxisType(str, Enum): - """X-axis types for diffraction plots. + """ + X-axis types for diffraction plots. - Values match attribute names in data models for direct use - with ``getattr(pattern, x_axis)``. + Values match attribute names in data models for direct use with + ``getattr(pattern, x_axis)``. """ TWO_THETA = 'two_theta' @@ -151,64 +152,80 @@ class XAxisType(str, Enum): class PlotterBase(ABC): - """Abstract base for plotting backends. + """ + Abstract base for plotting backends. Implementations accept x values, multiple y-series, optional labels and render a plot to the chosen medium. - Two main plot types are supported: - - ``plot_powder``: Line plots for powder diffraction patterns - (intensity vs. 2θ/TOF/d-spacing). - - ``plot_single_crystal``: Scatter plots comparing measured vs. - calculated values (e.g., F²meas vs F²calc for single crystal). + Two main plot types are supported: - ``plot_powder``: Line plots for + powder diffraction patterns (intensity vs. 2θ/TOF/d-spacing). - + ``plot_single_crystal``: Scatter plots comparing measured vs. + calculated values (e.g., F²meas vs F²calc for single crystal). """ @abstractmethod def plot_powder( self, - x, - y_series, - labels, - axes_labels, - title, - height, - ): - """Render a line plot for powder diffraction data. + x: object, + y_series: object, + labels: object, + axes_labels: object, + title: str, + height: int | None, + ) -> None: + """ + Render a line plot for powder diffraction data. Suitable for powder diffraction data where intensity is plotted against an x-axis variable (2θ, TOF, d-spacing). - Args: - x: 1D array of x-axis values. - y_series: Sequence of y arrays to plot. - labels: Identifiers corresponding to y_series. - axes_labels: Pair of strings for the x and y titles. - title: Figure title. - height: Backend-specific height (text rows or pixels). + Parameters + ---------- + x : object + 1D array of x-axis values. + y_series : object + Sequence of y arrays to plot. + labels : object + Identifiers corresponding to y_series. + axes_labels : object + Pair of strings for the x and y titles. + title : str + Figure title. + height : int | None + Backend-specific height (text rows or pixels). """ pass @abstractmethod def plot_single_crystal( self, - x_calc, - y_meas, - y_meas_su, - axes_labels, - title, - height, - ): - """Render a scatter plot for single crystal diffraction data. + x_calc: object, + y_meas: object, + y_meas_su: object, + axes_labels: object, + title: str, + height: int | None, + ) -> None: + """ + Render a scatter plot for single crystal diffraction data. Suitable for single crystal diffraction data where measured values are plotted against calculated values with error bars. - Args: - x_calc: 1D array of calculated values (x-axis). - y_meas: 1D array of measured values (y-axis). - y_meas_su: 1D array of measurement uncertainties. - axes_labels: Pair of strings for the x and y titles. - title: Figure title. - height: Backend-specific height (text rows or pixels). + Parameters + ---------- + x_calc : object + 1D array of calculated values (x-axis). + y_meas : object + 1D array of measured values (y-axis). + y_meas_su : object + 1D array of measurement uncertainties. + axes_labels : object + Pair of strings for the x and y titles. + title : str + Figure title. + height : int | None + Backend-specific height (text rows or pixels). """ pass diff --git a/src/easydiffraction/display/plotters/plotly.py b/src/easydiffraction/display/plotters/plotly.py index bf2b4907..4df79cf0 100644 --- a/src/easydiffraction/display/plotters/plotly.py +++ b/src/easydiffraction/display/plotters/plotly.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Plotly plotting backend. +""" +Plotly plotting backend. Provides an interactive plotting implementation using Plotly. In notebooks, figures are displayed inline; in other environments a browser @@ -38,19 +39,25 @@ class PlotlyPlotter(PlotterBase): def _get_powder_trace( self, - x, - y, - label, - ): - """Create a Plotly trace for powder diffraction data. - - Args: - x: 1D array-like of x-axis values. - y: 1D array-like of y-axis values. - label: Series identifier (``'meas'``, ``'calc'``, or - ``'resid'``). - - Returns: + x: object, + y: object, + label: str, + ) -> object: + """ + Create a Plotly trace for powder diffraction data. + + Parameters + ---------- + x : object + 1D array-like of x-axis values. + y : object + 1D array- like of y-axis values. + label : str + Series identifier (``'meas'``, ``'calc'``, or ``'resid'``). + + Returns + ------- + object A configured :class:`plotly.graph_objects.Scatter` trace. """ mode = SERIES_CONFIG[label]['mode'] @@ -70,18 +77,25 @@ def _get_powder_trace( def _get_single_crystal_trace( self, - x_calc, - y_meas, - y_meas_su, - ): - """Create a Plotly trace for single crystal diffraction data. - - Args: - x_calc: 1D array-like of calculated values (x-axis). - y_meas: 1D array-like of measured values (y-axis). - y_meas_su: 1D array-like of measurement uncertainties. - - Returns: + x_calc: object, + y_meas: object, + y_meas_su: object, + ) -> object: + """ + Create a Plotly trace for single crystal diffraction data. + + Parameters + ---------- + x_calc : object + 1D array-like of calculated values (x-axis). + y_meas : object + 1D array-like of measured values (y-axis). + y_meas_su : object + 1D array-like of measurement uncertainties. + + Returns + ------- + object A configured :class:`plotly.graph_objects.Scatter` trace with markers and error bars. """ @@ -105,13 +119,16 @@ def _get_single_crystal_trace( return trace - def _get_diagonal_shape(self): - """Create a diagonal reference line shape. + def _get_diagonal_shape(self) -> dict: + """ + Create a diagonal reference line shape. Returns a y=x diagonal line spanning the plot area using paper coordinates (0,0) to (1,1). - Returns: + Returns + ------- + dict A dict configuring a diagonal line shape. """ return dict( @@ -126,10 +143,13 @@ def _get_diagonal_shape(self): line=dict(width=0.5), ) - def _get_config(self): - """Return the Plotly figure configuration. + def _get_config(self) -> dict: + """ + Return the Plotly figure configuration. - Returns: + Returns + ------- + dict A dict with display and mode bar settings. """ return dict( @@ -145,16 +165,22 @@ def _get_config(self): def _get_figure( self, - data, - layout, - ): - """Create and configure a Plotly figure. - - Args: - data: List of traces to include in the figure. - layout: Layout configuration dict. - - Returns: + data: object, + layout: object, + ) -> object: + """ + Create and configure a Plotly figure. + + Parameters + ---------- + data : object + List of traces to include in the figure. + layout : object + Layout configuration dict. + + Returns + ------- + object A configured :class:`plotly.graph_objects.Figure`. """ fig = go.Figure(data=data, layout=layout) @@ -166,15 +192,18 @@ def _get_figure( def _show_figure( self, - fig, - ): - """Display a Plotly figure. + fig: object, + ) -> None: + """ + Display a Plotly figure. Renders the figure using the appropriate method for the current environment (browser for PyCharm, inline HTML for Jupyter). - Args: - fig: A :class:`plotly.graph_objects.Figure` to display. + Parameters + ---------- + fig : object + A :class:`plotly.graph_objects.Figure` to display. """ config = self._get_config() @@ -191,18 +220,25 @@ def _show_figure( def _get_layout( self, - title, - axes_labels, - **kwargs, - ): - """Create a Plotly layout configuration. - - Args: - title: Figure title. - axes_labels: Pair of strings for the x and y titles. - **kwargs: Additional layout parameters (e.g., shapes). - - Returns: + title: str, + axes_labels: object, + **kwargs: object, + ) -> object: + """ + Create a Plotly layout configuration. + + Parameters + ---------- + title : str + Figure title. + axes_labels : object + Pair of strings for the x and y titles. + **kwargs : object + Additional layout parameters (e.g., shapes). + + Returns + ------- + object A configured :class:`plotly.graph_objects.Layout`. """ return go.Layout( @@ -238,25 +274,33 @@ def _get_layout( def plot_powder( self, - x, - y_series, - labels, - axes_labels, - title, - height=None, - ): - """Render a line plot for powder diffraction data. + x: object, + y_series: object, + labels: object, + axes_labels: object, + title: str, + height: int | None = None, + ) -> None: + """ + Render a line plot for powder diffraction data. Suitable for powder diffraction data where intensity is plotted against an x-axis variable (2θ, TOF, d-spacing). - Args: - x: 1D array-like of x-axis values. - y_series: Sequence of y arrays to plot. - labels: Series identifiers corresponding to y_series. - axes_labels: Pair of strings for the x and y titles. - title: Figure title. - height: Ignored; Plotly auto-sizes based on renderer. + Parameters + ---------- + x : object + 1D array-like of x-axis values. + y_series : object + Sequence of y arrays to plot. + labels : object + Series identifiers corresponding to y_series. + axes_labels : object + Pair of strings for the x and y titles. + title : str + Figure title. + height : int | None, default=None + Ignored; Plotly auto-sizes based on renderer. """ # Intentionally unused; accepted for API compatibility del height @@ -277,26 +321,34 @@ def plot_powder( def plot_single_crystal( self, - x_calc, - y_meas, - y_meas_su, - axes_labels, - title, - height=None, - ): - """Render a scatter plot for single crystal diffraction data. + x_calc: object, + y_meas: object, + y_meas_su: object, + axes_labels: object, + title: str, + height: int | None = None, + ) -> None: + """ + Render a scatter plot for single crystal diffraction data. Suitable for single crystal diffraction data where measured - values are plotted against calculated values with error bars - and a diagonal reference line. - - Args: - x_calc: 1D array-like of calculated values (x-axis). - y_meas: 1D array-like of measured values (y-axis). - y_meas_su: 1D array-like of measurement uncertainties. - axes_labels: Pair of strings for the x and y titles. - title: Figure title. - height: Ignored; Plotly auto-sizes based on renderer. + values are plotted against calculated values with error bars and + a diagonal reference line. + + Parameters + ---------- + x_calc : object + 1D array-like of calculated values (x-axis). + y_meas : object + 1D array-like of measured values (y-axis). + y_meas_su : object + 1D array-like of measurement uncertainties. + axes_labels : object + Pair of strings for the x and y titles. + title : str + Figure title. + height : int | None, default=None + Ignored; Plotly auto-sizes based on renderer. """ # Intentionally unused; accepted for API compatibility del height diff --git a/src/easydiffraction/display/plotting.py b/src/easydiffraction/display/plotting.py index 90d54261..ec8e1d5c 100644 --- a/src/easydiffraction/display/plotting.py +++ b/src/easydiffraction/display/plotting.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Plotting facade for measured and calculated patterns. +""" +Plotting facade for measured and calculated patterns. Uses the common :class:`RendererBase` so plotters and tablers share a consistent configuration surface and engine handling. @@ -28,6 +29,8 @@ class PlotterEngineEnum(str, Enum): + """Available plotting engine backends.""" + ASCII = 'asciichartpy' PLOTLY = 'plotly' @@ -56,7 +59,7 @@ class Plotter(RendererBase): # Private special methods # ------------------------------------------------------------------ - def __init__(self): + def __init__(self) -> None: super().__init__() # X-axis limits self._x_min = DEFAULT_MIN @@ -80,17 +83,30 @@ def _default_engine(cls) -> str: # Private helper methods # ------------------------------------------------------------------ - def _auto_x_range_for_ascii(self, pattern, x_array, x_min, x_max): - """For the ASCII engine, narrow the range around the tallest - peak. - - Args: - pattern: Data pattern object (needs ``intensity_meas``). - x_array: Full x-axis array. - x_min: Current minimum (may be ``None``). - x_max: Current maximum (may be ``None``). - - Returns: + def _auto_x_range_for_ascii( + self, + pattern: object, + x_array: object, + x_min: object, + x_max: object, + ) -> tuple: + """ + For the ASCII engine, narrow the range around the tallest peak. + + Parameters + ---------- + pattern : object + Data pattern object (needs ``intensity_meas``). + x_array : object + Full x-axis array. + x_min : object + Current minimum (may be ``None``). + x_max : object + Current maximum (may be ``None``). + + Returns + ------- + tuple Tuple of ``(x_min, x_max)``, possibly narrowed. """ if self._engine == 'asciichartpy' and (x_min is None or x_max is None): @@ -104,21 +120,28 @@ def _auto_x_range_for_ascii(self, pattern, x_array, x_min, x_max): def _filtered_y_array( self, - y_array, - x_array, - x_min, - x_max, - ): - """Filter an array by the inclusive x-range limits. - - Args: - y_array: 1D array-like of y values. - x_array: 1D array-like of x values (same length as - ``y_array``). - x_min: Minimum x limit (or ``None`` to use default). - x_max: Maximum x limit (or ``None`` to use default). - - Returns: + y_array: object, + x_array: object, + x_min: object, + x_max: object, + ) -> object: + """ + Filter an array by the inclusive x-range limits. + + Parameters + ---------- + y_array : object + 1D array-like of y values. + x_array : object + 1D array-like of x values (same length as ``y_array``). + x_min : object + Minimum x limit (or ``None`` to use default). + x_max : object + Maximum x limit (or ``None`` to use default). + + Returns + ------- + object Filtered ``y_array`` values where ``x_array`` lies within ``[x_min, x_max]``. """ @@ -132,39 +155,55 @@ def _filtered_y_array( return filtered_y_array - def _get_axes_labels(self, sample_form, scattering_type, x_axis): - """Look up axis labels for the given experiment / x-axis - combination. - """ + def _get_axes_labels( + self, + sample_form: object, + scattering_type: object, + x_axis: object, + ) -> list: + """Look up axis labels for the experiment / x-axis.""" return DEFAULT_AXES_LABELS[(sample_form, scattering_type, x_axis)] def _prepare_powder_data( self, - pattern, - expt_name, - expt_type, - x_min, - x_max, - x, - need_meas=False, - need_calc=False, - show_residual=False, - ): - """Validate, resolve axes, auto-range, and filter arrays. - - Args: - pattern: Data pattern object with intensity arrays. - expt_name: Experiment name for error messages. - expt_type: Experiment type with sample_form, scattering, - and beam enums. - x_min: Optional minimum x-axis limit. - x_max: Optional maximum x-axis limit. - x: Explicit x-axis type or ``None``. - need_meas: Whether ``intensity_meas`` is required. - need_calc: Whether ``intensity_calc`` is required. - show_residual: If ``True``, compute meas − calc residual. - - Returns: + pattern: object, + expt_name: str, + expt_type: object, + x_min: object, + x_max: object, + x: object, + need_meas: bool = False, + need_calc: bool = False, + show_residual: bool = False, + ) -> dict | None: + """ + Validate, resolve axes, auto-range, and filter arrays. + + Parameters + ---------- + pattern : object + Data pattern object with intensity arrays. + expt_name : str + Experiment name for error messages. + expt_type : object + Experiment type with sample_form, scattering, and beam + enums. + x_min : object + Optional minimum x-axis limit. + x_max : object + Optional maximum x-axis limit. + x : object + Explicit x-axis type or ``None``. + need_meas : bool, default=False + Whether ``intensity_meas`` is required. + need_calc : bool, default=False + Whether ``intensity_calc`` is required. + show_residual : bool, default=False + If ``True``, compute meas − calc residual. + + Returns + ------- + dict | None A dict with keys ``x_filtered``, ``y_series``, ``y_labels``, ``axes_labels``, and ``x_axis``; or ``None`` when a required array is missing. @@ -222,15 +261,21 @@ def _prepare_powder_data( 'x_axis': x_axis, } - def _resolve_x_axis(self, expt_type, x): - """Determine the x-axis type from experiment metadata. - - Args: - expt_type: Experiment type with sample_form, - scattering_type, and beam_mode enums. - x: Explicit x-axis type or ``None`` to auto-detect. - - Returns: + def _resolve_x_axis(self, expt_type: object, x: object) -> tuple: + """ + Determine the x-axis type from experiment metadata. + + Parameters + ---------- + expt_type : object + Experiment type with sample_form, scattering_type, and + beam_mode enums. + x : object + Explicit x-axis type or ``None`` to auto-detect. + + Returns + ------- + tuple Tuple of ``(x_axis, x_name, sample_form, scattering_type, beam_mode)``. """ @@ -246,16 +291,19 @@ def _resolve_x_axis(self, expt_type, x): # ------------------------------------------------------------------ @property - def x_min(self): + def x_min(self) -> float: """Minimum x-axis limit.""" return self._x_min @x_min.setter - def x_min(self, value): - """Set the minimum x-axis limit. + def x_min(self, value: object) -> None: + """ + Set the minimum x-axis limit. - Args: - value: Minimum limit or ``None`` to reset to default. + Parameters + ---------- + value : object + Minimum limit or ``None`` to reset to default. """ if value is not None: self._x_min = value @@ -263,16 +311,19 @@ def x_min(self, value): self._x_min = DEFAULT_MIN @property - def x_max(self): + def x_max(self) -> float: """Maximum x-axis limit.""" return self._x_max @x_max.setter - def x_max(self, value): - """Set the maximum x-axis limit. + def x_max(self, value: object) -> None: + """ + Set the maximum x-axis limit. - Args: - value: Maximum limit or ``None`` to reset to default. + Parameters + ---------- + value : object + Maximum limit or ``None`` to reset to default. """ if value is not None: self._x_max = value @@ -280,16 +331,19 @@ def x_max(self, value): self._x_max = DEFAULT_MAX @property - def height(self): + def height(self) -> int: """Plot height (rows for ASCII, pixels for Plotly).""" return self._height @height.setter - def height(self, value): - """Set plot height. + def height(self, value: object) -> None: + """ + Set plot height. - Args: - value: Height value or ``None`` to reset to default. + Parameters + ---------- + value : object + Height value or ``None`` to reset to default. """ if value is not None: self._height = value @@ -300,7 +354,7 @@ def height(self, value): # Public methods # ------------------------------------------------------------------ - def show_config(self): + def show_config(self) -> None: """Display the current plotting configuration.""" headers = [ ('Parameter', 'left'), @@ -317,25 +371,32 @@ def show_config(self): def plot_meas( self, - pattern, - expt_name, - expt_type, - x_min=None, - x_max=None, - x=None, - ): - """Plot measured pattern using the current engine. - - Args: - pattern: Object with x-axis arrays (``two_theta``, - ``time_of_flight``, ``d_spacing``) and ``meas`` array. - expt_name: Experiment name for the title. - expt_type: Experiment type with scattering/beam enums. - x_min: Optional minimum x-axis limit. - x_max: Optional maximum x-axis limit. - x: X-axis type (``'two_theta'``, ``'time_of_flight'``, or - ``'d_spacing'``). If ``None``, auto-detected from - beam mode. + pattern: object, + expt_name: str, + expt_type: object, + x_min: object = None, + x_max: object = None, + x: object = None, + ) -> None: + """ + Plot measured pattern using the current engine. + + Parameters + ---------- + pattern : object + Object with x-axis arrays (``two_theta``, + ``time_of_flight``, ``d_spacing``) and ``meas`` array. + expt_name : str + Experiment name for the title. + expt_type : object + Experiment type with scattering/beam enums. + x_min : object, default=None + Optional minimum x-axis limit. + x_max : object, default=None + Optional maximum x-axis limit. + x : object, default=None + X-axis type (``'two_theta'``, ``'time_of_flight'``, or + ``'d_spacing'``). If ``None``, auto-detected from beam mode. """ ctx = self._prepare_powder_data( pattern, @@ -360,25 +421,32 @@ def plot_meas( def plot_calc( self, - pattern, - expt_name, - expt_type, - x_min=None, - x_max=None, - x=None, - ): - """Plot calculated pattern using the current engine. - - Args: - pattern: Object with x-axis arrays (``two_theta``, - ``time_of_flight``, ``d_spacing``) and ``calc`` array. - expt_name: Experiment name for the title. - expt_type: Experiment type with scattering/beam enums. - x_min: Optional minimum x-axis limit. - x_max: Optional maximum x-axis limit. - x: X-axis type (``'two_theta'``, ``'time_of_flight'``, or - ``'d_spacing'``). If ``None``, auto-detected from - beam mode. + pattern: object, + expt_name: str, + expt_type: object, + x_min: object = None, + x_max: object = None, + x: object = None, + ) -> None: + """ + Plot calculated pattern using the current engine. + + Parameters + ---------- + pattern : object + Object with x-axis arrays (``two_theta``, + ``time_of_flight``, ``d_spacing``) and ``calc`` array. + expt_name : str + Experiment name for the title. + expt_type : object + Experiment type with scattering/beam enums. + x_min : object, default=None + Optional minimum x-axis limit. + x_max : object, default=None + Optional maximum x-axis limit. + x : object, default=None + X-axis type (``'two_theta'``, ``'time_of_flight'``, or + ``'d_spacing'``). If ``None``, auto-detected from beam mode. """ ctx = self._prepare_powder_data( pattern, @@ -403,37 +471,44 @@ def plot_calc( def plot_meas_vs_calc( self, - pattern, - expt_name, - expt_type, - x_min=None, - x_max=None, - show_residual=False, - x=None, - ): - """Plot measured and calculated series and optional residual. + pattern: object, + expt_name: str, + expt_type: object, + x_min: object = None, + x_max: object = None, + show_residual: bool = False, + x: object = None, + ) -> None: + """ + Plot measured and calculated series and optional residual. Supports both powder and single crystal data with a unified API. - For powder diffraction: - - x='two_theta', 'time_of_flight', or 'd_spacing' - - Auto-detected from beam mode if not specified - - For single crystal diffraction: - - x='intensity_calc' (default): scatter plot - - x='d_spacing' or 'sin_theta_over_lambda': line plot - - Args: - pattern: Data pattern object with meas/calc arrays. - expt_name: Experiment name for the title. - expt_type: Experiment type with sample_form, - scattering, and beam enums. - x_min: Optional minimum x-axis limit. - x_max: Optional maximum x-axis limit. - show_residual: If ``True``, add residual series - (powder only). - x: X-axis type. If ``None``, auto-detected from sample form - and beam mode. + For powder diffraction: - x='two_theta', 'time_of_flight', or + 'd_spacing' - Auto-detected from beam mode if not specified + + For single crystal diffraction: - x='intensity_calc' (default): + scatter plot - x='d_spacing' or 'sin_theta_over_lambda': line + plot + + Parameters + ---------- + pattern : object + Data pattern object with meas/calc arrays. + expt_name : str + Experiment name for the title. + expt_type : object + Experiment type with sample_form, scattering, and beam + enums. + x_min : object, default=None + Optional minimum x-axis limit. + x_max : object, default=None + Optional maximum x-axis limit. + show_residual : bool, default=False + If ``True``, add residual series (powder only). + x : object, default=None + X-axis type. If ``None``, auto-detected from sample form and + beam mode. """ x_axis, _, sample_form, scattering_type, _ = self._resolve_x_axis(expt_type, x) diff --git a/src/easydiffraction/display/tablers/__init__.py b/src/easydiffraction/display/tablers/__init__.py index 93d84417..6471fbff 100644 --- a/src/easydiffraction/display/tablers/__init__.py +++ b/src/easydiffraction/display/tablers/__init__.py @@ -1,10 +1,11 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Tabular rendering backends. +""" +Tabular rendering backends. -This subpackage provides concrete implementations for rendering -tables in different environments: +This subpackage provides concrete implementations for rendering tables +in different environments: -- :mod:`.rich` for terminal and notebooks using the Rich library. -- :mod:`.pandas` for notebooks using DataFrame Styler. +- :mod:`.rich` for terminal and notebooks using the Rich library. - +:mod:`.pandas` for notebooks using DataFrame Styler. """ diff --git a/src/easydiffraction/display/tablers/base.py b/src/easydiffraction/display/tablers/base.py index 2b710d8f..869c5a17 100644 --- a/src/easydiffraction/display/tablers/base.py +++ b/src/easydiffraction/display/tablers/base.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Low-level backends for rendering tables. +""" +Low-level backends for rendering tables. This module defines the abstract base for tabular renderers and small helpers for consistent styling across terminal and notebook outputs. @@ -10,7 +11,6 @@ from abc import ABC from abc import abstractmethod -from typing import Any from IPython import get_ipython from rich.color import Color @@ -19,11 +19,11 @@ class TableBackendBase(ABC): - """Abstract base class for concrete table backends. + """ + Abstract base class for concrete table backends. - Subclasses implement the ``render`` method which receives an - index-aware pandas DataFrame and the alignment for each column - header. + Subclasses implement the ``render`` method which receives an index- + aware pandas DataFrame and the alignment for each column header. """ FLOAT_PRECISION = 5 @@ -34,20 +34,26 @@ def __init__(self) -> None: super().__init__() self._float_fmt = f'{{:.{self.FLOAT_PRECISION}f}}'.format - def _format_value(self, value: Any) -> Any: - """Format floats with fixed precision and others as strings. + def _format_value(self, value: object) -> object: + """ + Format floats with fixed precision and others as strings. - Args: - value: Cell value to format. + Parameters + ---------- + value : object + Cell value to format. - Returns: + Returns + ------- + object A string representation with fixed precision for floats or ``str(value)`` for other types. """ return self._float_fmt(value) if isinstance(value, float) else str(value) def _is_dark_theme(self) -> bool: - """Return True when a dark theme is detected in Jupyter. + """ + Return True when a dark theme is detected in Jupyter. If not running inside Jupyter, return a sane default (True). """ @@ -62,14 +68,18 @@ def _is_dark_theme(self) -> bool: return is_dark() - def _rich_to_hex(self, color): - """Convert a Rich color name to a CSS-style hex string. + def _rich_to_hex(self, color: str) -> str: + """ + Convert a Rich color name to a CSS-style hex string. - Args: - color: Rich color name or specification parsable by - :mod:`rich`. + Parameters + ---------- + color : str + Rich color name or specification parsable by :mod:`rich`. - Returns: + Returns + ------- + str Hex color string in the form ``#RRGGBB``. """ c = Color.parse(color) @@ -90,21 +100,27 @@ def _pandas_border_color(self) -> str: @abstractmethod def render( self, - alignments, - df, - display_handle: Any | None = None, - ) -> Any: - """Render the provided DataFrame with backend-specific styling. - - Args: - alignments: Iterable of column justifications (e.g., - ``'left'`` or ``'center'``) corresponding to the data - columns. - df: Index-aware DataFrame with data to render. - display_handle: Optional environment-specific handle to - enable in-place updates. - - Returns: + alignments: object, + df: object, + display_handle: object | None = None, + ) -> object: + """ + Render the provided DataFrame with backend-specific styling. + + Parameters + ---------- + alignments : object + Iterable of column justifications (e.g., ``'left'`` or + ``'center'``) corresponding to the data columns. + df : object + Index-aware DataFrame with data to render. + display_handle : object | None, default=None + Optional environment-specific handle to enable in-place + updates. + + Returns + ------- + object Backend-defined return value (commonly ``None``). """ pass diff --git a/src/easydiffraction/display/tablers/pandas.py b/src/easydiffraction/display/tablers/pandas.py index da05b5e8..4efef148 100644 --- a/src/easydiffraction/display/tablers/pandas.py +++ b/src/easydiffraction/display/tablers/pandas.py @@ -1,11 +1,9 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Pandas-based table renderer for notebooks using DataFrame Styler.""" from __future__ import annotations -from typing import Any - try: from IPython.display import HTML from IPython.display import display @@ -22,13 +20,18 @@ class PandasTableBackend(TableBackendBase): """Render tables using the pandas Styler in Jupyter environments.""" def _build_base_styles(self, color: str) -> list[dict]: - """Return base CSS table styles for a given border color. + """ + Return base CSS table styles for a given border color. - Args: - color: CSS color value (e.g., ``#RRGGBB``) to use for - borders and header accents. + Parameters + ---------- + color : str + CSS color value (e.g., ``#RRGGBB``) to use for borders and + header accents. - Returns: + Returns + ------- + list[dict] A list of ``Styler.set_table_styles`` dictionaries. """ return [ @@ -76,15 +79,21 @@ def _build_base_styles(self, color: str) -> list[dict]: }, ] - def _build_header_alignment_styles(self, df, alignments) -> list[dict]: - """Generate header cell alignment styles per column. - - Args: - df: DataFrame whose columns are being rendered. - alignments: Iterable of text alignment values (e.g., - ``'left'``, ``'center'``) matching ``df`` columns. - - Returns: + def _build_header_alignment_styles(self, df: object, alignments: object) -> list[dict]: + """ + Generate header cell alignment styles per column. + + Parameters + ---------- + df : object + DataFrame whose columns are being rendered. + alignments : object + Iterable of text alignment values (e.g., ``'left'``, + ``'center'``) matching ``df`` columns. + + Returns + ------- + list[dict] A list of CSS rules for header cell alignment. """ return [ @@ -95,15 +104,22 @@ def _build_header_alignment_styles(self, df, alignments) -> list[dict]: for column, align in zip(df.columns, alignments, strict=False) ] - def _apply_styling(self, df, alignments, color: str): - """Build a configured Styler with alignments and base styles. - - Args: - df: DataFrame to style. - alignments: Iterable of text alignment values for columns. - color: CSS color value used for borders/header. - - Returns: + def _apply_styling(self, df: object, alignments: object, color: str) -> object: + """ + Build a configured Styler with alignments and base styles. + + Parameters + ---------- + df : object + DataFrame to style. + alignments : object + Iterable of text alignment values for columns. + color : str + CSS color value used for borders/header. + + Returns + ------- + object A configured pandas Styler ready for display. """ table_styles = self._build_base_styles(color) @@ -120,17 +136,20 @@ def _apply_styling(self, df, alignments, color: str): ) return styler - def _update_display(self, styler, display_handle) -> None: - """Single, consistent update path for Jupyter. + def _update_display(self, styler: object, display_handle: object) -> None: + """ + Single, consistent update path for Jupyter. If a handle with ``update()`` is provided and it's a DisplayHandle, update the output area in-place using HTML. Otherwise, display once via IPython ``display()``. - Args: - styler: Configured DataFrame Styler to be rendered. - display_handle: Optional IPython DisplayHandle used for - in-place updates. + Parameters + ---------- + styler : object + Configured DataFrame Styler to be rendered. + display_handle : object + Optional IPython DisplayHandle used for in-place updates. """ # Handle with update() method if display_handle is not None and hasattr(display_handle, 'update'): @@ -152,17 +171,27 @@ def _update_display(self, styler, display_handle) -> None: def render( self, - alignments, - df, - display_handle: Any | None = None, - ) -> Any: - """Render a styled DataFrame. - - Args: - alignments: Iterable of column justifications (e.g. 'left'). - df: DataFrame whose index is displayed as the first column. - display_handle: Optional IPython DisplayHandle to update an - existing output area in place when running in Jupyter. + alignments: object, + df: object, + display_handle: object | None = None, + ) -> object: + """ + Render a styled DataFrame. + + Parameters + ---------- + alignments : object + Iterable of column justifications (e.g. 'left'). + df : object + DataFrame whose index is displayed as the first column. + display_handle : object | None, default=None + Optional IPython DisplayHandle to update an existing output + area in place when running in Jupyter. + + Returns + ------- + object + Backend-defined return value (commonly ``None``). """ color = self._pandas_border_color styler = self._apply_styling(df, alignments, color) diff --git a/src/easydiffraction/display/tablers/rich.py b/src/easydiffraction/display/tablers/rich.py index e7a8afbb..fba2a400 100644 --- a/src/easydiffraction/display/tablers/rich.py +++ b/src/easydiffraction/display/tablers/rich.py @@ -1,11 +1,10 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Rich-based table renderer for terminals and notebooks.""" from __future__ import annotations import io -from typing import Any from rich.box import Box from rich.console import Console @@ -41,16 +40,20 @@ class RichTableBackend(TableBackendBase): """Render tables to terminal or Jupyter using the Rich library.""" def _to_html(self, table: Table) -> str: - """Render a Rich table to HTML using an off-screen console. + """ + Render a Rich table to HTML using an off-screen console. - A fresh ``Console(record=True, file=StringIO())`` avoids - private attribute access and guarantees no visible output - in notebooks. + A fresh ``Console(record=True, file=StringIO())`` avoids private + attribute access and guarantees no visible output in notebooks. - Args: - table: Rich :class:`~rich.table.Table` to export. + Parameters + ---------- + table : Table + Rich :class:`~rich.table.Table` to export. - Returns: + Returns + ------- + str HTML string with inline styles for notebook display. """ tmp = Console(force_jupyter=False, record=True, file=io.StringIO()) @@ -63,15 +66,22 @@ def _to_html(self, table: Table) -> str: ) return html - def _build_table(self, df, alignments, color: str) -> Table: - """Construct a Rich Table with formatted data and alignment. - - Args: - df: DataFrame-like object providing rows to render. - alignments: Iterable of text alignment values for columns. - color: Rich color name used for borders/index style. - - Returns: + def _build_table(self, df: object, alignments: object, color: str) -> Table: + """ + Construct a Rich Table with formatted data and alignment. + + Parameters + ---------- + df : object + DataFrame-like object providing rows to render. + alignments : object + Iterable of text alignment values for columns. + color : str + Rich color name used for borders/index style. + + Returns + ------- + Table A :class:`~rich.table.Table` configured for display. """ table = Table( @@ -96,20 +106,23 @@ def _build_table(self, df, alignments, color: str) -> Table: return table - def _update_display(self, table: Table, display_handle) -> None: - """Single, consistent update path for Jupyter and terminal. - - - With a handle that has ``update()``: - * If it's an IPython DisplayHandle, export to HTML and - update. - * Otherwise, treat it as a terminal/live-like handle and - update with the Rich renderable. - - Without a handle, print once to the shared console. - - Args: - table: Rich :class:`~rich.table.Table` to display. - display_handle: Optional environment-specific handle for - in-place updates (IPython or terminal live). + def _update_display(self, table: Table, display_handle: object) -> None: + """ + Single, consistent update path for Jupyter and terminal. + + - With a handle that has ``update()``: * If it's an IPython + DisplayHandle, export to HTML and update. * Otherwise, treat it + as a terminal/live-like handle and update with the Rich + renderable. - Without a handle, print once to the shared + console. + + Parameters + ---------- + table : Table + Rich :class:`~rich.table.Table` to display. + display_handle : object + Optional environment-specific handle for in- place updates + (IPython or terminal live). """ # Handle with update() method if display_handle is not None and hasattr(display_handle, 'update'): @@ -136,17 +149,26 @@ def _update_display(self, table: Table, display_handle) -> None: def render( self, - alignments, - df, - display_handle=None, - ) -> Any: - """Render a styled table using Rich. - - Args: - alignments: Iterable of text-align values for columns. - df: Index-aware DataFrame to render. - display_handle: Optional environment handle for in-place - updates. + alignments: object, + df: object, + display_handle: object = None, + ) -> object: + """ + Render a styled table using Rich. + + Parameters + ---------- + alignments : object + Iterable of text-align values for columns. + df : object + Index-aware DataFrame to render. + display_handle : object, default=None + Optional environment handle for in-place updates. + + Returns + ------- + object + Backend-defined return value (commonly ``None``). """ color = self._rich_border_color table = self._build_table(df, alignments, color) diff --git a/src/easydiffraction/display/tables.py b/src/easydiffraction/display/tables.py index e149609d..1ae39594 100644 --- a/src/easydiffraction/display/tables.py +++ b/src/easydiffraction/display/tables.py @@ -1,11 +1,10 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Table rendering engines: console (Rich) and Jupyter (pandas).""" from __future__ import annotations from enum import Enum -from typing import Any import pandas as pd @@ -19,12 +18,15 @@ class TableEngineEnum(str, Enum): + """Available table rendering backends.""" + RICH = 'rich' PANDAS = 'pandas' @classmethod def default(cls) -> 'TableEngineEnum': - """Select default engine based on environment. + """ + Select default engine based on environment. Returns Pandas when running in Jupyter, otherwise Rich. """ @@ -35,6 +37,14 @@ def default(cls) -> 'TableEngineEnum': return cls.RICH def description(self) -> str: + """ + Return a human-readable description of this table engine. + + Returns + ------- + str + Description string for the current enum member. + """ if self is TableEngineEnum.RICH: return 'Console rendering with Rich' elif self is TableEngineEnum.PANDAS: @@ -65,17 +75,23 @@ def show_config(self) -> None: console.paragraph('Current tabler configuration') TableRenderer.get().render(df) - def render(self, df, display_handle: Any | None = None) -> Any: - """Render a DataFrame as a table using the active backend. - - Args: - df: DataFrame with a two-level column index where the - second level provides per-column alignment. - display_handle: Optional environment-specific handle used - to update an existing output area in-place (e.g., an - IPython DisplayHandle or a terminal live handle). - - Returns: + def render(self, df: object, display_handle: object | None = None) -> object: + """ + Render a DataFrame as a table using the active backend. + + Parameters + ---------- + df : object + DataFrame with a two-level column index where the second + level provides per-column alignment. + display_handle : object | None, default=None + Optional environment-specific handle used to update an + existing output area in-place (e.g., an IPython + DisplayHandle or a terminal live handle). + + Returns + ------- + object Backend-specific return value (usually ``None``). """ # Work on a copy to avoid mutating the original DataFrame @@ -98,11 +114,11 @@ class TableRendererFactory(RendererFactoryBase): @classmethod def _registry(cls) -> dict: - """Build registry, adapting available engines to the - environment. + """ + Build registry, adapting available engines to the environment. - - In Jupyter: expose both 'rich' and 'pandas'. - - In terminal: expose only 'rich' (pandas is notebook-only). + - In Jupyter: expose both 'rich' and 'pandas'. - In terminal: + expose only 'rich' (pandas is notebook-only). """ base = { TableEngineEnum.RICH.value: { diff --git a/src/easydiffraction/display/utils.py b/src/easydiffraction/display/utils.py index 8c06f384..17c6fa94 100644 --- a/src/easydiffraction/display/utils.py +++ b/src/easydiffraction/display/utils.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -18,9 +18,7 @@ class JupyterScrollManager: - """Ensures that Jupyter output cells are not scrollable (applied - once). - """ + """Ensures Jupyter output cells are not scrollable (once).""" _applied: ClassVar[bool] = False diff --git a/src/easydiffraction/io/__init__.py b/src/easydiffraction/io/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/io/__init__.py +++ b/src/easydiffraction/io/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/io/cif/__init__.py b/src/easydiffraction/io/cif/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/io/cif/__init__.py +++ b/src/easydiffraction/io/cif/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/io/cif/handler.py b/src/easydiffraction/io/cif/handler.py index 16ed4343..0cd3b62b 100644 --- a/src/easydiffraction/io/cif/handler.py +++ b/src/easydiffraction/io/cif/handler.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Minimal CIF tag handler used by descriptors/parameters.""" @@ -6,7 +6,8 @@ class CifHandler: - """Canonical CIF handler used by descriptors/parameters. + """ + Canonical CIF handler used by descriptors/parameters. Holds CIF tags (names) and attaches to an owning descriptor so it can derive a stable uid if needed. @@ -16,7 +17,7 @@ def __init__(self, *, names: list[str]) -> None: self._names = names self._owner = None # set by attach - def attach(self, owner): + def attach(self, owner: object) -> None: """Attach to a descriptor or parameter instance.""" self._owner = owner diff --git a/src/easydiffraction/io/cif/parse.py b/src/easydiffraction/io/cif/parse.py index ad0d736d..a320cf60 100644 --- a/src/easydiffraction/io/cif/parse.py +++ b/src/easydiffraction/io/cif/parse.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import gemmi diff --git a/src/easydiffraction/io/cif/serialize.py b/src/easydiffraction/io/cif/serialize.py index 971b08c4..f885aed7 100644 --- a/src/easydiffraction/io/cif/serialize.py +++ b/src/easydiffraction/io/cif/serialize.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -22,14 +22,13 @@ from easydiffraction.core.variable import GenericDescriptorBase -def format_value(value) -> str: - """Format a single CIF value, quoting strings with whitespace, and - format floats with global precision. +def format_value(value: object) -> str: + """ + Format a single CIF value for output. - .. note:: - The precision must be high enough so that the minimizer's - finite-difference Jacobian probes (typically ~1e-8 relative) - survive the float→string→float round-trip through CIF. + .. note:: The precision must be high enough so that the + minimizer's finite-difference Jacobian probes (typically ~1e-8 + relative) survive the float→string→float round-trip through CIF. """ width = 12 precision = 8 @@ -61,8 +60,9 @@ def format_value(value) -> str: ################## -def param_to_cif(param) -> str: - """Render a single descriptor/parameter to a CIF line. +def param_to_cif(param: object) -> str: + """ + Render a single descriptor/parameter to a CIF line. Expects ``param`` to expose ``_cif_handler.names`` and ``value``. """ @@ -71,8 +71,9 @@ def param_to_cif(param) -> str: return f'{main_key} {format_value(param.value)}' -def category_item_to_cif(item) -> str: - """Render a CategoryItem-like object to CIF text. +def category_item_to_cif(item: object) -> str: + """ + Render a CategoryItem-like object to CIF text. Expects ``item.parameters`` iterable of params with ``_cif_handler.names`` and ``value``. @@ -84,10 +85,11 @@ def category_item_to_cif(item) -> str: def category_collection_to_cif( - collection, + collection: object, max_display: Optional[int] = 20, ) -> str: - """Render a CategoryCollection-like object to CIF text. + """ + Render a CategoryCollection-like object to CIF text. Uses first item to build loop header, then emits rows for each item. """ @@ -125,8 +127,9 @@ def category_collection_to_cif( return '\n'.join(lines) -def datablock_item_to_cif(datablock) -> str: - """Render a DatablockItem-like object to CIF text. +def datablock_item_to_cif(datablock: object) -> str: + """ + Render a DatablockItem-like object to CIF text. Emits a data_ header and then concatenates category CIF sections. """ @@ -150,15 +153,13 @@ def datablock_item_to_cif(datablock) -> str: return '\n\n'.join(parts) -def datablock_collection_to_cif(collection) -> str: +def datablock_collection_to_cif(collection: object) -> str: """Render a collection of datablocks by joining their CIF blocks.""" return '\n\n'.join([block.as_cif for block in collection.values()]) -def project_info_to_cif(info) -> str: - """Render ProjectInfo to CIF text (id, title, description, - dates). - """ +def project_info_to_cif(info: object) -> str: + """Render ProjectInfo to CIF text (id, title, description).""" name = f'{info.name}' title = f'{info.title}' @@ -184,7 +185,7 @@ def project_info_to_cif(info) -> str: ) -def project_to_cif(project) -> str: +def project_to_cif(project: object) -> str: """Render a whole project by concatenating sections when present.""" parts: list[str] = [] if hasattr(project, 'info'): @@ -200,12 +201,12 @@ def project_to_cif(project) -> str: return '\n\n'.join([p for p in parts if p]) -def experiment_to_cif(experiment) -> str: +def experiment_to_cif(experiment: object) -> str: """Render an experiment: datablock part plus measured data.""" return datablock_item_to_cif(experiment) -def analysis_to_cif(analysis) -> str: +def analysis_to_cif(analysis: object) -> str: """Render analysis metadata, aliases, and constraints to CIF.""" cur_min = format_value(analysis.current_minimizer) lines: list[str] = [] @@ -222,7 +223,7 @@ def analysis_to_cif(analysis) -> str: return '\n'.join(lines) -def summary_to_cif(_summary) -> str: +def summary_to_cif(_summary: object) -> str: """Render a summary CIF block (placeholder for now).""" return 'To be added...' @@ -239,6 +240,18 @@ def param_from_cif( block: gemmi.cif.Block, idx: int = 0, ) -> None: + """ + Populate a single descriptor from a CIF block. + + Parameters + ---------- + self : GenericDescriptorBase + The descriptor instance to populate. + block : gemmi.cif.Block + Parsed CIF block to read values from. + idx : int, default=0 + Row index used when the tag belongs to a loop. + """ found_values: list[Any] = [] # Try to find the value(s) from the CIF block iterating over @@ -290,6 +303,21 @@ def category_collection_from_cif( self: CategoryCollection, block: gemmi.cif.Block, ) -> None: + """ + Populate a CategoryCollection from a CIF loop. + + Parameters + ---------- + self : CategoryCollection + The collection instance to populate. + block : gemmi.cif.Block + Parsed CIF block to read the loop from. + + Raises + ------ + ValueError + If the collection has no ``_item_type`` defined. + """ # TODO: Find a better way and then remove TODO in the AtomSite # class # TODO: Rename to _item_cls? @@ -302,7 +330,7 @@ def category_collection_from_cif( # Iterate over category parameters and their possible CIF names # trying to find the whole loop it belongs to inside the CIF block - def _get_loop(block, category_item): + def _get_loop(block: object, category_item: object) -> object | None: for param in category_item.parameters: for name in param._cif_handler.names: loop = block.find_loop(name).get_loop() diff --git a/src/easydiffraction/project/__init__.py b/src/easydiffraction/project/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/project/__init__.py +++ b/src/easydiffraction/project/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/project/project.py b/src/easydiffraction/project/project.py index f7a3f487..af5a5922 100644 --- a/src/easydiffraction/project/project.py +++ b/src/easydiffraction/project/project.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Project facade to orchestrate models, experiments, and analysis.""" @@ -22,7 +22,8 @@ class Project(GuardedBase): - """Central API for managing a diffraction data analysis project. + """ + Central API for managing a diffraction data analysis project. Provides access to structures, experiments, analysis, and summary. """ @@ -74,13 +75,19 @@ def info(self) -> ProjectInfo: @property def name(self) -> str: - """Convenience property to access the project's name - directly. - """ + """Convenience property for the project name.""" return self._info.name @property def full_name(self) -> str: + """ + Return the full project name (alias for :attr:`name`). + + Returns + ------- + str + The project name. + """ return self.name @property @@ -94,42 +101,42 @@ def structures(self, structures: Structures) -> None: self._structures = structures @property - def experiments(self): + def experiments(self) -> Experiments: """Collection of experiments in the project.""" return self._experiments @experiments.setter @typechecked - def experiments(self, experiments: Experiments): + def experiments(self, experiments: Experiments) -> None: self._experiments = experiments @property - def plotter(self): + def plotter(self) -> Plotter: """Plotting facade bound to the project.""" return self._plotter @property - def tabler(self): + def tabler(self) -> TableRenderer: """Tables rendering facade bound to the project.""" return self._tabler @property - def analysis(self): + def analysis(self) -> Analysis: """Analysis entry-point bound to the project.""" return self._analysis @property - def summary(self): + def summary(self) -> Summary: """Summary report builder bound to the project.""" return self._summary @property - def parameters(self): + def parameters(self) -> list: """Return parameters from all structures and experiments.""" return self.structures.parameters + self.experiments.parameters @property - def as_cif(self): + def as_cif(self) -> str: """Export whole project as CIF text.""" # Concatenate sections using centralized CIF serializers return project_to_cif(self) @@ -139,7 +146,8 @@ def as_cif(self): # ------------------------------------------ def load(self, dir_path: str) -> None: - """Load a project from a given directory. + """ + Load a project from a given directory. Loads project info, structures, experiments, etc. """ @@ -216,7 +224,7 @@ def save_as( # Plotting # ------------------------------------------ - def _update_categories(self, expt_name) -> None: + def _update_categories(self, expt_name: str) -> None: for structure in self.structures: structure._update_categories() self.analysis._update_categories() @@ -225,11 +233,25 @@ def _update_categories(self, expt_name) -> None: def plot_meas( self, - expt_name, - x_min=None, - x_max=None, - x=None, - ): + expt_name: str, + x_min: float | None = None, + x_max: float | None = None, + x: object | None = None, + ) -> None: + """ + Plot measured diffraction data for an experiment. + + Parameters + ---------- + expt_name : str + Name of the experiment to plot. + x_min : float | None, default=None + Lower bound for the x-axis range. + x_max : float | None, default=None + Upper bound for the x-axis range. + x : object | None, default=None + Optional explicit x-axis data to override stored values. + """ self._update_categories(expt_name) experiment = self.experiments[expt_name] @@ -244,11 +266,25 @@ def plot_meas( def plot_calc( self, - expt_name, - x_min=None, - x_max=None, - x=None, - ): + expt_name: str, + x_min: float | None = None, + x_max: float | None = None, + x: object | None = None, + ) -> None: + """ + Plot calculated diffraction pattern for an experiment. + + Parameters + ---------- + expt_name : str + Name of the experiment to plot. + x_min : float | None, default=None + Lower bound for the x-axis range. + x_max : float | None, default=None + Upper bound for the x-axis range. + x : object | None, default=None + Optional explicit x-axis data to override stored values. + """ self._update_categories(expt_name) experiment = self.experiments[expt_name] @@ -263,12 +299,28 @@ def plot_calc( def plot_meas_vs_calc( self, - expt_name, - x_min=None, - x_max=None, - show_residual=False, - x=None, - ): + expt_name: str, + x_min: float | None = None, + x_max: float | None = None, + show_residual: bool = False, + x: object | None = None, + ) -> None: + """ + Plot measured vs calculated data for an experiment. + + Parameters + ---------- + expt_name : str + Name of the experiment to plot. + x_min : float | None, default=None + Lower bound for the x-axis range. + x_max : float | None, default=None + Upper bound for the x-axis range. + show_residual : bool, default=False + When ``True``, include the residual (difference) curve. + x : object | None, default=None + Optional explicit x-axis data to override stored values. + """ self._update_categories(expt_name) experiment = self.experiments[expt_name] diff --git a/src/easydiffraction/project/project_info.py b/src/easydiffraction/project/project_info.py index 998ba200..d062d522 100644 --- a/src/easydiffraction/project/project_info.py +++ b/src/easydiffraction/project/project_info.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Project metadata container used by Project.""" @@ -12,9 +12,7 @@ class ProjectInfo(GuardedBase): - """Stores metadata about the project, such as name, title, - description, and file paths. - """ + """Store project metadata: name, title, description, paths.""" def __init__( self, @@ -38,6 +36,14 @@ def name(self) -> str: @name.setter def name(self, value: str) -> None: + """ + Set the project name. + + Parameters + ---------- + value : str + New project name. + """ self._name = value @property @@ -52,6 +58,14 @@ def title(self) -> str: @title.setter def title(self, value: str) -> None: + """ + Set the project title. + + Parameters + ---------- + value : str + New project title. + """ self._title = value @property @@ -61,6 +75,14 @@ def description(self) -> str: @description.setter def description(self, value: str) -> None: + """ + Set the project description (whitespace normalized). + + Parameters + ---------- + value : str + New description text. + """ self._description = ' '.join(value.split()) @property @@ -69,7 +91,15 @@ def path(self) -> pathlib.Path: return self._path @path.setter - def path(self, value) -> None: + def path(self, value: object) -> None: + """ + Set the project directory path. + + Parameters + ---------- + value : object + New path as a :class:`str` or :class:`pathlib.Path`. + """ # Accept str or Path; normalize to Path self._path = pathlib.Path(value) @@ -87,8 +117,8 @@ def update_last_modified(self) -> None: """Update the last modified timestamp.""" self._last_modified = datetime.datetime.now() - def parameters(self): - """Placeholder for parameter listing.""" + def parameters(self) -> None: + """List parameters (not implemented).""" pass # TODO: Consider moving to io.cif.serialize diff --git a/src/easydiffraction/summary/__init__.py b/src/easydiffraction/summary/__init__.py index 429f2648..4e798e20 100644 --- a/src/easydiffraction/summary/__init__.py +++ b/src/easydiffraction/summary/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/summary/summary.py b/src/easydiffraction/summary/summary.py index 7e72d825..9d715bac 100644 --- a/src/easydiffraction/summary/summary.py +++ b/src/easydiffraction/summary/summary.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from textwrap import wrap @@ -9,17 +9,21 @@ class Summary: - """Generates reports and exports results from the project. + """ + Generates reports and exports results from the project. This class collects and presents all relevant information about the fitted model, experiments, and analysis results. """ - def __init__(self, project) -> None: - """Initialize the summary with a reference to the project. + def __init__(self, project: object) -> None: + """ + Initialize the summary with a reference to the project. - Args: - project: The Project instance this summary belongs to. + Parameters + ---------- + project : object + The Project instance this summary belongs to. """ self.project = project @@ -28,6 +32,7 @@ def __init__(self, project) -> None: # ------------------------------------------ def show_report(self) -> None: + """Print a full project report covering all sections.""" self.show_project_info() self.show_crystallographic_data() self.show_experimental_data() @@ -52,9 +57,7 @@ def show_project_info(self) -> None: print('\n'.join(desc_lines)) def show_crystallographic_data(self) -> None: - """Print crystallographic data including phase datablocks, space - groups, cell parameters, and atom sites. - """ + """Print crystallographic data for all phases.""" console.section('Crystallographic data') for model in self.project.structures.values(): @@ -114,9 +117,7 @@ def show_crystallographic_data(self) -> None: ) def show_experimental_data(self) -> None: - """Print experimental data including experiment datablocks, - types, instrument settings, and peak profile information. - """ + """Print experimental data for all experiments.""" console.section('Experiments') for expt in self.project.experiments.values(): @@ -174,9 +175,7 @@ def show_experimental_data(self) -> None: ) def show_fitting_details(self) -> None: - """Print fitting details including calculation and minimization - engines, and fit quality metrics. - """ + """Print fitting details including engines and metrics.""" console.section('Fitting') console.paragraph('Calculation engine') @@ -206,9 +205,7 @@ def show_fitting_details(self) -> None: # ------------------------------------------ def as_cif(self) -> str: - """Export the final fitted data and analysis results as CIF - format. - """ + """Export fitted data and analysis results as CIF.""" from easydiffraction.io.cif.serialize import summary_to_cif return summary_to_cif(self) diff --git a/src/easydiffraction/utils/__init__.py b/src/easydiffraction/utils/__init__.py index d193bf2f..53fde2c6 100644 --- a/src/easydiffraction/utils/__init__.py +++ b/src/easydiffraction/utils/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.utils.utils import _is_dev_version diff --git a/src/easydiffraction/utils/_vendored/__init__.py b/src/easydiffraction/utils/_vendored/__init__.py index 20506363..c124e1af 100644 --- a/src/easydiffraction/utils/_vendored/__init__.py +++ b/src/easydiffraction/utils/_vendored/__init__.py @@ -1,23 +1,22 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Vendored third-party modules. +""" +Vendored third-party modules. This package contains third-party code that has been vendored into the project to avoid external dependencies not available on conda-forge. -Packages: - jupyter_dark_detect/ - Vendored copy of jupyter_dark_detect (MIT License). - Check jupyter_dark_detect.__version__ for vendored version. +Packages: jupyter_dark_detect/ Vendored copy of +jupyter_dark_detect (MIT License). Check +jupyter_dark_detect.__version__ for vendored version. - To update, replace these files from upstream: - - https://github.com/OpenMined/jupyter-dark-detect/blob/main/jupyter_dark_detect/__init__.py - - https://github.com/OpenMined/jupyter-dark-detect/blob/main/jupyter_dark_detect/detector.py +To update, replace these files from upstream: - +https://github.com/OpenMined/jupyter-dark-detect/blob/main/jupyter_dark_detect/__init__.py +- +https://github.com/OpenMined/jupyter-dark-detect/blob/main/jupyter_dark_detect/detector.py - Then run 'pixi run fix' to format and check for issues. +Then run 'pixi run fix' to format and check for issues. -Modules: - theme_detect: - Custom wrapper around jupyter_dark_detect with optimized - detection order for EasyDiffraction's use case. +Modules: theme_detect: Custom wrapper around jupyter_dark_detect with +optimized detection order for EasyDiffraction's use case. """ diff --git a/src/easydiffraction/utils/_vendored/jupyter_dark_detect/__init__.py b/src/easydiffraction/utils/_vendored/jupyter_dark_detect/__init__.py index 11ef7b95..640883dd 100644 --- a/src/easydiffraction/utils/_vendored/jupyter_dark_detect/__init__.py +++ b/src/easydiffraction/utils/_vendored/jupyter_dark_detect/__init__.py @@ -1,7 +1,7 @@ -"""jupyter-dark-detect: Detect dark mode in Jupyter environments. +"""Jupyter-dark-detect: Detect dark mode in Jupyter environments. -This package provides a simple API to detect whether Jupyter Notebook/Lab -is running in dark mode across different environments. +This package provides a simple API to detect whether Jupyter +Notebook/Lab is running in dark mode across different environments. """ from .detector import is_dark diff --git a/src/easydiffraction/utils/_vendored/jupyter_dark_detect/detector.py b/src/easydiffraction/utils/_vendored/jupyter_dark_detect/detector.py index 8f34b5af..e247ba87 100644 --- a/src/easydiffraction/utils/_vendored/jupyter_dark_detect/detector.py +++ b/src/easydiffraction/utils/_vendored/jupyter_dark_detect/detector.py @@ -14,14 +14,11 @@ def is_dark() -> bool: """Check if Jupyter Notebook/Lab is running in dark mode. - This function attempts multiple detection strategies: - 1. JupyterLab theme settings files - 2. VS Code settings (when running in VS Code) - 3. JavaScript DOM inspection - 4. System preferences (macOS) - - Returns: - bool: True if dark mode is detected, False otherwise + This function attempts multiple detection strategies: 1. JupyterLab + theme settings files 2. VS Code settings (when running in VS Code) + 3. JavaScript DOM inspection 4. System preferences (macOS) + + Returns: bool: True if dark mode is detected, False otherwise """ # Try JupyterLab settings first result = _check_jupyterlab_settings() diff --git a/src/easydiffraction/utils/_vendored/theme_detect.py b/src/easydiffraction/utils/_vendored/theme_detect.py index 1d555f0a..9f75ee72 100644 --- a/src/easydiffraction/utils/_vendored/theme_detect.py +++ b/src/easydiffraction/utils/_vendored/theme_detect.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Jupyter theme detection with custom detection order. +""" +Jupyter theme detection with custom detection order. This module wraps the vendored jupyter_dark_detect package and provides a custom detection order optimized for EasyDiffraction's use case. @@ -12,15 +13,12 @@ 3. JavaScript DOM inspection (for browser-based environments) 4. System preferences (macOS, Windows) - fallback only -Note: - The detection order differs from upstream jupyter_dark_detect. - We prioritize JavaScript DOM inspection over system preferences - because the Jupyter theme may differ from the system theme. +Note: The detection order differs from upstream jupyter_dark_detect. We +prioritize JavaScript DOM inspection over system preferences because the +Jupyter theme may differ from the system theme. -Example: - >>> from easydiffraction.utils._vendored.theme_detect import is_dark - >>> if is_dark(): - ... print('Dark mode detected') +Example: >>> from easydiffraction.utils._vendored.theme_detect import +is_dark >>> if is_dark(): ... print('Dark mode detected') """ from __future__ import annotations @@ -37,19 +35,22 @@ def is_dark() -> bool: - """Check if the Jupyter environment is running in dark mode. + """ + Check if the Jupyter environment is running in dark mode. This function uses a custom detection order that prioritizes Jupyter-specific detection over system preferences. Detection order: - 1. JupyterLab settings files (most reliable for JupyterLab) - 2. VS Code settings (when running in VS Code) - 3. JavaScript DOM inspection (for browser-based Jupyter) - 4. System preferences (fallback - may differ from Jupyter theme) + 1. JupyterLab settings files (most reliable for JupyterLab) 2. VS + Code settings (when running in VS Code) 3. JavaScript DOM inspection + (for browser-based Jupyter) 4. System preferences (fallback - may + differ from Jupyter theme) - Returns: + Returns + ------- + bool True if dark mode is detected, False otherwise. """ # Try Jupyter-specific methods first @@ -77,11 +78,14 @@ def is_dark() -> bool: def get_detection_result() -> dict[str, Optional[bool]]: - """Get results from all detection methods for debugging. + """ + Get results from all detection methods for debugging. - Returns: - Dictionary with detection method names as keys and their - results (True/False/None) as values. + Returns + ------- + dict[str, Optional[bool]] + Dictionary with detection method names as keys and their results + (True/False/None) as values. """ return { 'jupyterlab_settings': _check_jupyterlab_settings(), diff --git a/src/easydiffraction/utils/environment.py b/src/easydiffraction/utils/environment.py index aa7de5a6..2c518092 100644 --- a/src/easydiffraction/utils/environment.py +++ b/src/easydiffraction/utils/environment.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -9,27 +9,50 @@ def in_pytest() -> bool: + """ + Determine whether the code is running inside a pytest session. + + Returns + ------- + bool + True if pytest is loaded, False otherwise. + """ return 'pytest' in sys.modules def in_warp() -> bool: + """ + Determine whether the terminal is the Warp terminal emulator. + + Returns + ------- + bool + True if the TERM_PROGRAM environment variable equals + ``'WarpTerminal'``, False otherwise. + """ return os.getenv('TERM_PROGRAM') == 'WarpTerminal' def in_pycharm() -> bool: - """Determines if the current environment is PyCharm. + """ + Check whether the current environment is PyCharm. - Returns: - bool: True if running inside PyCharm, False otherwise. + Returns + ------- + bool + True if running inside PyCharm, False otherwise. """ return os.environ.get('PYCHARM_HOSTED') == '1' def in_colab() -> bool: - """Determines if the current environment is Google Colab. + """ + Check whether the current environment is Google Colab. - Returns: - bool: True if running in Google Colab, False otherwise. + Returns + ------- + bool + True if running in Google Colab, False otherwise. """ try: return find_spec('google.colab') is not None @@ -38,10 +61,13 @@ def in_colab() -> bool: def in_jupyter() -> bool: - """Return True when running inside a Jupyter Notebook. + """ + Return True when running inside a Jupyter Notebook. - Returns: - bool: True if inside a Jupyter Notebook, False otherwise. + Returns + ------- + bool + True if inside a Jupyter Notebook, False otherwise. """ try: import IPython # type: ignore[import-not-found] @@ -76,11 +102,13 @@ def in_jupyter() -> bool: def in_github_ci() -> bool: - """Return True when running under GitHub Actions CI. + """ + Return True when running under GitHub Actions CI. - Returns: - bool: True if env var ``GITHUB_ACTIONS`` is set, False - otherwise. + Returns + ------- + bool + True if env var ``GITHUB_ACTIONS`` is set, False otherwise. """ return os.environ.get('GITHUB_ACTIONS') is not None @@ -91,12 +119,13 @@ def in_github_ci() -> bool: def is_ipython_display_handle(obj: object) -> bool: - """Return True if ``obj`` is an IPython DisplayHandle instance. + """ + Return True if ``obj`` is an IPython DisplayHandle instance. Tries to import ``IPython.display.DisplayHandle`` and uses - ``isinstance`` when available. Falls back to a conservative - module name heuristic if IPython is missing. Any errors result - in ``False``. + ``isinstance`` when available. Falls back to a conservative module + name heuristic if IPython is missing. Any errors result in + ``False``. """ try: # Fast path when IPython is available from IPython.display import DisplayHandle # type: ignore[import-not-found] @@ -115,7 +144,8 @@ def is_ipython_display_handle(obj: object) -> bool: def can_update_ipython_display() -> bool: - """Return True if IPython HTML display utilities are available. + """ + Return True if IPython HTML display utilities are available. This indicates we can safely construct ``IPython.display.HTML`` and update a display handle. @@ -129,7 +159,8 @@ def can_update_ipython_display() -> bool: def can_use_ipython_display(handle: object) -> bool: - """Return True if we can update the given IPython DisplayHandle. + """ + Return True if we can update the given IPython DisplayHandle. Combines type checking of the handle with availability of IPython HTML utilities. diff --git a/src/easydiffraction/utils/logging.py b/src/easydiffraction/utils/logging.py index 056d650b..876d7956 100644 --- a/src/easydiffraction/utils/logging.py +++ b/src/easydiffraction/utils/logging.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Project-wide logging utilities built on top of Rich. +""" +Project-wide logging utilities built on top of Rich. Provides a shared Rich console, a compact/verbose logger with consistent formatting, Jupyter traceback handling, and a small printing façade @@ -43,9 +44,7 @@ class IconifiedRichHandler(RichHandler): - """RichHandler that uses icons for log levels in compact mode, Rich - default in verbose mode. - """ + """RichHandler using icons (compact) or names (verbose).""" _icons = { logging.CRITICAL: '💀', @@ -55,11 +54,24 @@ class IconifiedRichHandler(RichHandler): logging.INFO: 'ℹ️', } - def __init__(self, *args, mode: str = 'compact', **kwargs): + def __init__(self, *args: object, mode: str = 'compact', **kwargs: object) -> None: super().__init__(*args, **kwargs) self.mode = mode def get_level_text(self, record: logging.LogRecord) -> Text: + """ + Return an icon or level name for the log record. + + Parameters + ---------- + record : logging.LogRecord + The log record being rendered. + + Returns + ------- + Text + A Rich Text object with the level indicator. + """ if self.mode == 'compact': icon = self._icons.get(record.levelno, record.levelname) if in_warp() and not in_jupyter() and icon in ['⚠️', '⚙️', 'ℹ️']: @@ -70,9 +82,21 @@ def get_level_text(self, record: logging.LogRecord) -> Text: return super().get_level_text(record) def render_message(self, record: logging.LogRecord, message: str) -> Text: - # In compact mode, let the icon come from get_level_text and - # keep the message body unadorned. In verbose mode, defer to - # RichHandler. + """ + Render the log message body as a Rich Text object. + + Parameters + ---------- + record : logging.LogRecord + The log record being rendered. + message : str + Pre-formatted log message string. + + Returns + ------- + Text + A Rich Text object with the rendered message. + """ if self.mode == 'compact': try: return Text.from_markup(message) @@ -94,9 +118,12 @@ class ConsoleManager: @staticmethod def _detect_width() -> int: - """Detect a suitable console width for the shared Console. + """ + Detect a suitable console width for the shared Console. - Returns: + Returns + ------- + int The detected terminal width, clamped at ``_MIN_CONSOLE_WIDTH`` to avoid cramped layouts. """ @@ -134,13 +161,19 @@ def setup_handlers( rich_tracebacks: bool, mode: str = 'compact', ) -> None: - """Install Rich handler and optional Jupyter traceback support. - - Args: - logger: Logger instance to attach handlers to. - level: Minimum log level to emit. - rich_tracebacks: Whether to enable Rich tracebacks. - mode: Output mode name ("compact" or "verbose"). + """ + Install Rich handler and optional Jupyter traceback support. + + Parameters + ---------- + logger : logging.Logger + Logger instance to attach handlers to. + level : int + Minimum log level to emit. + rich_tracebacks : bool + Whether to enable Rich tracebacks. + mode : str, default='compact' + Output mode name ("compact" or "verbose"). """ logger.handlers.clear() logger.propagate = False @@ -175,13 +208,19 @@ def configure( level: 'Logger.Level', rich_tracebacks: bool, ) -> None: - """Configure the logger with RichHandler and exception hooks. - - Args: - logger: Logger instance to configure. - mode: Output mode (compact or verbose). - level: Minimum log level to emit. - rich_tracebacks: Whether to enable Rich tracebacks. + """ + Configure the logger with RichHandler and exception hooks. + + Parameters + ---------- + logger : logging.Logger + Logger instance to configure. + mode : 'Logger.Mode' + Output mode (compact or verbose). + level : 'Logger.Level' + Minimum log level to emit. + rich_tracebacks : bool + Whether to enable Rich tracebacks. """ LoggerConfig.setup_handlers( logger, @@ -204,10 +243,13 @@ class ExceptionHookManager: @staticmethod def install_verbose_hook(logger: logging.Logger) -> None: - """Install a verbose exception hook that prints rich tracebacks. + """ + Install a verbose exception hook that prints rich tracebacks. - Args: - logger: Logger used to emit the exception information. + Parameters + ---------- + logger : logging.Logger + Logger used to emit the exception information. """ if not hasattr(Logger, '_orig_excepthook'): Logger._orig_excepthook = sys.excepthook # type: ignore[attr-defined] @@ -217,6 +259,7 @@ def aligned_excepthook( exc: BaseException, tb: 'TracebackType | None', ) -> None: + """Log the exception with full traceback via Rich.""" original_args = getattr(exc, 'args', tuple()) message = str(exc) with suppress(Exception): @@ -233,10 +276,13 @@ def aligned_excepthook( @staticmethod def install_compact_hook(logger: logging.Logger) -> None: - """Install a compact exception hook that logs message-only. + """ + Install a compact exception hook that logs message-only. - Args: - logger: Logger used to emit the error message. + Parameters + ---------- + logger : logging.Logger + Logger used to emit the error message. """ if not hasattr(Logger, '_orig_excepthook'): Logger._orig_excepthook = sys.excepthook # type: ignore[attr-defined] @@ -246,33 +292,39 @@ def compact_excepthook( exc: BaseException, _tb: 'TracebackType | None', ) -> None: + """Log the exception message and exit.""" logger.error(str(exc)) raise SystemExit(1) sys.excepthook = compact_excepthook # type: ignore[assignment] @staticmethod - def restore_original_hook(): + def restore_original_hook() -> None: """Restore the original sys.excepthook if it was overridden.""" if hasattr(Logger, '_orig_excepthook'): sys.excepthook = Logger._orig_excepthook # type: ignore[attr-defined] # Jupyter-specific traceback suppression (inlined here) @staticmethod - def _suppress_traceback(logger): - """Build a Jupyter custom exception callback that logs only the - message. + def _suppress_traceback(logger: object) -> object: + """ + Build a Jupyter exception callback that logs the message only. - Args: - logger: Logger used to emit error messages. + Parameters + ---------- + logger : object + Logger used to emit error messages. - Returns: + Returns + ------- + object A callable suitable for IPython's set_custom_exc that suppresses full tracebacks and logs only the exception message. """ - def suppress_jupyter_traceback(*args, **kwargs): + def suppress_jupyter_traceback(*args: object, **kwargs: object) -> None: + """Log only the exception message.""" try: _evalue = ( args[2] if len(args) > 2 else kwargs.get('_evalue') or kwargs.get('evalue') @@ -286,11 +338,13 @@ def suppress_jupyter_traceback(*args, **kwargs): @staticmethod def install_jupyter_traceback_suppressor(logger: logging.Logger) -> None: - """Install a Jupyter/IPython custom exception handler that - suppresses tracebacks. + """ + Install a Jupyter/IPython exception handler for tracebacks. - Args: - logger: Logger used to emit error messages. + Parameters + ---------- + logger : logging.Logger + Logger used to emit error messages. """ try: from IPython import get_ipython @@ -311,11 +365,11 @@ def install_jupyter_traceback_suppressor(logger: logging.Logger) -> None: class Logger: - """Centralized logging with Rich formatting and two modes. + """ + Centralized logging with Rich formatting and two modes. - Environment variables: - ED_LOG_MODE: set default mode ('verbose' or 'compact') - ED_LOG_LEVEL: set default level ('DEBUG', 'INFO', etc.) + Environment variables: ED_LOG_MODE: set default mode ('verbose' or + 'compact') ED_LOG_LEVEL: set default level ('DEBUG', 'INFO', etc.) """ # --- Enums --- @@ -326,7 +380,8 @@ class Mode(Enum): COMPACT = 'compact' # single line; no traceback @classmethod - def default(cls): + def default(cls) -> Logger.Mode: + """Return the default output mode (compact).""" return cls.COMPACT class Level(IntEnum): @@ -339,7 +394,8 @@ class Level(IntEnum): CRITICAL = logging.CRITICAL @classmethod - def default(cls): + def default(cls) -> Logger.Level: + """Return the default log level (WARNING).""" return cls.WARNING class Reaction(Enum): @@ -349,7 +405,8 @@ class Reaction(Enum): WARN = auto() @classmethod - def default(cls): + def default(cls) -> Logger.Reaction: + """Return the default error reaction (RAISE).""" return cls.RAISE # --- Internal state --- @@ -369,15 +426,15 @@ def configure( reaction: Reaction | None = None, rich_tracebacks: bool | None = None, ) -> None: - """Configure logger. + """ + Configure logger. - mode: default COMPACT in Jupyter else VERBOSE - level: minimum log level - rich_tracebacks: override automatic choice + mode: default COMPACT in Jupyter else VERBOSE level: minimum log + level rich_tracebacks: override automatic choice - Environment variables: - ED_LOG_MODE: set default mode ('verbose' or 'compact') - ED_LOG_LEVEL: set default level ('DEBUG', 'INFO', etc.) + Environment variables: ED_LOG_MODE: set default mode ('verbose' + or 'compact') ED_LOG_LEVEL: set default level ('DEBUG', 'INFO', + etc.) """ env_mode = os.getenv('ED_LOG_MODE') env_level = os.getenv('ED_LOG_LEVEL') @@ -418,22 +475,44 @@ def configure( @classmethod def _install_jupyter_traceback_suppressor(cls) -> None: - """Install traceback suppressor in Jupyter, safely and lint- - clean. - """ + """Install the Jupyter traceback suppressor safely.""" ExceptionHookManager.install_jupyter_traceback_suppressor(cls._logger) # ===== Helpers ===== @classmethod def set_mode(cls, mode: Mode) -> None: + """ + Set the output mode and reconfigure the logger. + + Parameters + ---------- + mode : Mode + The desired output mode (VERBOSE or COMPACT). + """ cls.configure(mode=mode, level=cls.Level(cls._logger.level)) @classmethod def set_level(cls, level: Level) -> None: + """ + Set the minimum log level and reconfigure the logger. + + Parameters + ---------- + level : Level + The desired minimum log level. + """ cls.configure(mode=cls._mode, level=level) @classmethod def mode(cls) -> Mode: + """ + Return the currently active output mode. + + Returns + ------- + Mode + The current Logger.Mode value. + """ return cls._mode @classmethod @@ -478,22 +557,68 @@ def handle( @classmethod def debug(cls, *messages: str) -> None: + """ + Log one or more messages at DEBUG level. + + Parameters + ---------- + *messages : str + Message parts joined with a space before logging. + """ cls.handle(*messages, level=cls.Level.DEBUG, exc_type=None) @classmethod def info(cls, *messages: str) -> None: + """ + Log one or more messages at INFO level. + + Parameters + ---------- + *messages : str + Message parts joined with a space before logging. + """ cls.handle(*messages, level=cls.Level.INFO, exc_type=None) @classmethod def warning(cls, *messages: str, exc_type: type[BaseException] | None = None) -> None: + """ + Log one or more messages at WARNING level. + + Parameters + ---------- + *messages : str + Message parts joined with a space before logging. + exc_type : type[BaseException] | None, default=None + If provided, raise this exception type instead of logging. + """ cls.handle(*messages, level=cls.Level.WARNING, exc_type=exc_type) @classmethod def error(cls, *messages: str, exc_type: type[BaseException] = AttributeError) -> None: + """ + Log one or more messages at ERROR level. + + Parameters + ---------- + *messages : str + Message parts joined with a space before logging. + exc_type : type[BaseException], default=AttributeError + Exception type to raise in VERBOSE/COMPACT mode. + """ cls.handle(*messages, level=cls.Level.ERROR, exc_type=exc_type) @classmethod def critical(cls, *messages: str, exc_type: type[BaseException] = RuntimeError) -> None: + """ + Log one or more messages at CRITICAL level. + + Parameters + ---------- + *messages : str + Message parts joined with a space before logging. + exc_type : type[BaseException], default=RuntimeError + Exception type to raise in VERBOSE/COMPACT mode. + """ cls.handle(*messages, level=cls.Level.CRITICAL, exc_type=exc_type) @@ -503,20 +628,18 @@ def critical(cls, *messages: str, exc_type: type[BaseException] = RuntimeError) class ConsolePrinter: - """Printer utility that prints objects to the shared console with - left padding. - """ + """Printer utility for the shared console with left padding.""" _console = ConsoleManager.get() @classmethod - def print(cls, *objects, **kwargs): - """Print objects to the console with left padding. + def print(cls, *objects: object, **kwargs: object) -> None: + """ + Print objects to the console with left padding. - Renderables (Rich types like Text, Table, Panel, etc.) are - kept as-is. - - Non-renderables (ints, floats, Path, etc.) are converted to - str(). + kept as-is. - Non-renderables (ints, floats, Path, etc.) are + converted to str(). """ safe_objects = [] for obj in objects: @@ -538,6 +661,15 @@ def print(cls, *objects, **kwargs): @classmethod def paragraph(cls, title: str) -> None: + """ + Print a bold blue paragraph heading. + + Parameters + ---------- + title : str + Heading text; substrings enclosed in single quotes are + rendered without the bold-blue style. + """ parts = re.split(r"('.*?')", title) text = Text() for part in parts: @@ -552,7 +684,7 @@ def paragraph(cls, title: str) -> None: @classmethod def section(cls, title: str) -> None: - """Formats a section header with bold green text.""" + """Format a section header with bold green text.""" full_title = f'{title.upper()}' line = '—' * len(full_title) formatted = f'[bold green]{line}\n{full_title}\n{line}[/bold green]' @@ -562,9 +694,7 @@ def section(cls, title: str) -> None: @classmethod def chapter(cls, title: str) -> None: - """Formats a chapter header with bold magenta text, uppercase, - and padding. - """ + """Format a chapter header in bold magenta, uppercase.""" width = ConsoleManager._detect_width() symbol = '—' full_title = f' {title.upper()} ' diff --git a/src/easydiffraction/utils/utils.py b/src/easydiffraction/utils/utils.py index 82118302..3e8c5f1e 100644 --- a/src/easydiffraction/utils/utils.py +++ b/src/easydiffraction/utils/utils.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations @@ -30,13 +30,18 @@ def _validate_url(url: str) -> None: - """Validate that a URL uses only safe HTTP/HTTPS schemes. + """ + Validate that a URL uses only safe HTTP/HTTPS schemes. - Args: - url: The URL to validate. + Parameters + ---------- + url : str + The URL to validate. - Raises: - ValueError: If the URL scheme is not HTTP or HTTPS. + Raises + ------ + ValueError + If the URL scheme is not HTTP or HTTPS. """ parsed = urlparse(url) if parsed.scheme not in ('http', 'https'): @@ -44,16 +49,15 @@ def _validate_url(url: str) -> None: def _filename_for_id_from_url(data_id: int | str, url: str) -> str: - """Return local filename like 'ed-12.xye' using extension from the - URL. - """ + """Return local filename using the extension from the URL.""" suffix = pathlib.Path(urlparse(url).path).suffix # includes leading dot ('.cif', '.xye', ...) # If URL has no suffix, fall back to no extension. return f'ed-{data_id}{suffix}' def _normalize_known_hash(value: str | None) -> str | None: - """Return pooch-compatible known_hash or None. + """ + Return pooch-compatible known_hash or None. Treat placeholder values like 'sha256:...' as unset. """ @@ -66,9 +70,7 @@ def _normalize_known_hash(value: str | None) -> str | None: def _fetch_data_index() -> dict: - """Fetch & cache the diffraction data index.json and return it as - dict. - """ + """Fetch and cache the diffraction data index.json.""" index_url = 'https://raw.githubusercontent.com/easyscience/data/refs/heads/master/diffraction/index.json' _validate_url(index_url) @@ -92,18 +94,20 @@ def _fetch_data_index() -> dict: @functools.lru_cache(maxsize=1) def _fetch_tutorials_index() -> dict: - """Fetch & cache the tutorials index.json from gh-pages and return - it as dict. + """ + Fetch and cache the tutorials index.json from gh-pages. The index is fetched from: https://easyscience.github.io/diffraction-lib/{version}/tutorials/index.json - For released versions, {version} is the public version string - (e.g., '0.8.0.post1'). For development versions, 'dev' is used. + For released versions, {version} is the public version string (e.g., + '0.8.0.post1'). For development versions, 'dev' is used. - Returns: - dict: The tutorials index as a dictionary, or empty dict if - fetch fails. + Returns + ------- + dict + The tutorials index as a dictionary, or empty dict if fetch + fails. """ version = _get_version_for_url() index_url = f'https://easyscience.github.io/diffraction-lib/{version}/tutorials/index.json' @@ -125,24 +129,29 @@ def download_data( destination: str = 'data', overwrite: bool = False, ) -> str: - """Download a dataset by numeric ID using the remote diffraction - index. - - Example: - path = download_data(id=12, destination="data") + """ + Download a dataset by numeric ID using the remote diffraction index. - Args: - id: Numeric dataset id (e.g. 12). - destination: Directory to save the file into (created if - missing). - overwrite: Whether to overwrite the file if it already exists. + Example: path = download_data(id=12, destination="data") - Returns: - str: Full path to the downloaded file as string. + Parameters + ---------- + id : int | str + Numeric dataset id (e.g. 12). + destination : str, default='data' + Directory to save the file into (created if missing). + overwrite : bool, default=False + Whether to overwrite the file if it already exists. + + Returns + ------- + str + Full path to the downloaded file as string. - Raises: - KeyError: If the id is not found in the index. - ValueError: If the resolved URL is not HTTP/HTTPS. + Raises + ------ + KeyError + If the id is not found in the index. """ index = _fetch_data_index() key = str(id) @@ -195,14 +204,19 @@ def download_data( def package_version(package_name: str) -> str | None: - """Get the installed version string of the specified package. + """ + Get the installed version string of the specified package. - Args: - package_name (str): The name of the package to query. + Parameters + ---------- + package_name : str + The name of the package to query. - Returns: - str | None: The raw version string (may include local part, - e.g., '1.2.3+abc123'), or None if the package is not installed. + Returns + ------- + str | None + The raw version string (may include local part, e.g., + '1.2.3+abc123'), or None if the package is not installed. """ try: return version(package_name) @@ -211,18 +225,22 @@ def package_version(package_name: str) -> str | None: def stripped_package_version(package_name: str) -> str | None: - """Get the installed version of the specified package, stripped of - any local version part. + """ + Get installed package version, stripped of local version parts. Returns only the public version segment (e.g., '1.2.3' or '1.2.3.post4'), omitting any local segment (e.g., '+d136'). - Args: - package_name (str): The name of the package to query. + Parameters + ---------- + package_name : str + The name of the package to query. - Returns: - str | None: The public version string, or None if the package - is not installed. + Returns + ------- + str | None + The public version string, or None if the package is not + installed. """ v_str = package_version(package_name) if v_str is None: @@ -235,21 +253,22 @@ def stripped_package_version(package_name: str) -> str | None: def _is_dev_version(package_name: str) -> bool: - """Check if the installed package version is a development/local - version. + """ + Check if the installed package version is a dev version. - A version is considered "dev" if: - - The raw version contains '+dev', '+dirty', or '+devdirty' (local - suffixes from versioningit) - - The public version is '999.0.0' (versioningit default-tag - fallback) + A version is considered "dev" if: - The raw version contains '+dev', + '+dirty', or '+devdirty' (local suffixes from versioningit) - The + public version is '999.0.0' (versioningit default-tag fallback) - Args: - package_name (str): The name of the package to query. + Parameters + ---------- + package_name : str + The name of the package to query. - Returns: - bool: True if the version is a development version, False - otherwise. + Returns + ------- + bool + True if the version is a development version, False otherwise. """ raw_version = package_version(package_name) if raw_version is None: @@ -265,26 +284,31 @@ def _is_dev_version(package_name: str) -> bool: def _get_version_for_url(package_name: str = 'easydiffraction') -> str: - """Get the version string to use in URLs for fetching remote - resources. + """ + Get the version string to use in URLs for fetching remote resources. Returns the public version for released versions, or 'dev' for development/local versions. - Args: - package_name (str): The name of the package to query. + Parameters + ---------- + package_name : str, default='easydiffraction' + The name of the package to query. - Returns: - str: The version string to use in URLs ('dev' or a version like - '0.8.0.post1'). + Returns + ------- + str + The version string to use in URLs ('dev' or a version like + '0.8.0.post1'). """ if _is_dev_version(package_name): return 'dev' return stripped_package_version(package_name) or 'dev' -def _safe_urlopen(request_or_url): # type: ignore[no-untyped-def] - """Wrapper for urlopen with prior validation. +def _safe_urlopen(request_or_url: object) -> object: # type: ignore[no-untyped-def] + """ + Open a URL with prior validation. Centralises lint suppression for validated HTTPS requests. """ @@ -301,25 +325,29 @@ def _safe_urlopen(request_or_url): # type: ignore[no-untyped-def] def _resolve_tutorial_url(url_template: str) -> str: - """Replace {version} placeholder in URL template with actual - version. + """ + Replace {version} placeholder in URL template with actual version. - Args: - url_template (str): URL template containing {version} - placeholder. + Parameters + ---------- + url_template : str + URL template containing {version} placeholder. - Returns: - str: URL with {version} replaced by actual version string. + Returns + ------- + str + URL with {version} replaced by actual version string. """ version = _get_version_for_url() return url_template.replace('{version}', version) def list_tutorials() -> None: - """Display a table of available tutorial notebooks. + """ + Display a table of available tutorial notebooks. - Shows tutorial ID, filename, title, and description for all - tutorials available for the current version of easydiffraction. + Shows tutorial ID, filename and title for all tutorials available + for the current version of easydiffraction. """ index = _fetch_tutorials_index() if not index: @@ -327,19 +355,17 @@ def list_tutorials() -> None: return version = _get_version_for_url() - console.print(f'Tutorials available for easydiffraction v{version}:') - console.print('') + console.paragraph(f'Tutorials available for easydiffraction v{version}:') - columns_headers = ['id', 'file', 'title', 'description'] - columns_alignment = ['right', 'left', 'left', 'left'] + columns_headers = ['id', 'file', 'title'] + columns_alignment = ['right', 'left', 'left'] columns_data = [] - for tutorial_id in sorted(index.keys(), key=lambda x: int(x) if x.isdigit() else x): + for tutorial_id in index: record = index[tutorial_id] filename = f'ed-{tutorial_id}.ipynb' title = record.get('title', '') - description = record.get('description', '') - columns_data.append([tutorial_id, filename, title, description]) + columns_data.append([tutorial_id, filename, title]) render_table( columns_headers=columns_headers, @@ -353,23 +379,29 @@ def download_tutorial( destination: str = 'tutorials', overwrite: bool = False, ) -> str: - """Download a tutorial notebook by numeric ID. - - Example: - path = download_tutorial(id=1, destination="tutorials") + """ + Download a tutorial notebook by numeric ID. - Args: - id: Numeric tutorial id (e.g. 1). - destination: Directory to save the file into (created if - missing). - overwrite: Whether to overwrite the file if it already exists. + Example: path = download_tutorial(id=1, destination="tutorials") - Returns: - str: Full path to the downloaded file as string. + Parameters + ---------- + id : int | str + Numeric tutorial id (e.g. 1). + destination : str, default='tutorials' + Directory to save the file into (created if missing). + overwrite : bool, default=False + Whether to overwrite the file if it already exists. + + Returns + ------- + str + Full path to the downloaded file as string. - Raises: - KeyError: If the id is not found in the index. - ValueError: If the resolved URL is not HTTP/HTTPS. + Raises + ------ + KeyError + If the id is not found in the index. """ index = _fetch_tutorials_index() key = str(id) @@ -420,18 +452,22 @@ def download_all_tutorials( destination: str = 'tutorials', overwrite: bool = False, ) -> list[str]: - """Download all available tutorial notebooks. + """ + Download all available tutorial notebooks. - Example: - paths = download_all_tutorials(destination="tutorials") + Example: paths = download_all_tutorials(destination="tutorials") - Args: - destination: Directory to save the files into (created if - missing). - overwrite: Whether to overwrite files if they already exist. + Parameters + ---------- + destination : str, default='tutorials' + Directory to save the files into (created if missing). + overwrite : bool, default=False + Whether to overwrite files if they already exist. - Returns: - list[str]: List of full paths to the downloaded files. + Returns + ------- + list[str] + List of full paths to the downloaded files. """ index = _fetch_tutorials_index() if not index: @@ -458,11 +494,7 @@ def download_all_tutorials( def show_version() -> None: - """Print the installed version of the easydiffraction package. - - Args: - None - """ + """Print the installed version of the easydiffraction package.""" current_ed_version = package_version('easydiffraction') console.print(f'Current easydiffraction v{current_ed_version}') @@ -470,11 +502,27 @@ def show_version() -> None: # TODO: This is a temporary utility function. Complete migration to # TableRenderer (as e.g. in show_all_params) and remove this. def render_table( - columns_data, - columns_alignment, - columns_headers=None, - display_handle=None, -): + columns_data: object, + columns_alignment: object, + columns_headers: object = None, + display_handle: object = None, +) -> None: + """ + Render tabular data to the active display backend. + + Parameters + ---------- + columns_data : object + A list of rows, where each row is a list of cell values. + columns_alignment : object + A list of alignment strings (e.g. ``'left'``, ``'right'``, + ``'center'``) matching the number of columns. + columns_headers : object, default=None + Optional list of column header strings. + display_handle : object, default=None + Optional display handle for in-place updates (e.g. in Jupyter or + a terminal Live context). + """ headers = [ (col, align) for col, align in zip(columns_headers, columns_alignment, strict=False) ] @@ -484,12 +532,14 @@ def render_table( tabler.render(df, display_handle=display_handle) -def render_cif(cif_text) -> None: - """Display the CIF text as a formatted table in Jupyter Notebook or - terminal. +def render_cif(cif_text: str) -> None: + """ + Display CIF text as a formatted table in Jupyter or terminal. - Args: - cif_text: The CIF text to display. + Parameters + ---------- + cif_text : str + The CIF text to display. """ # Split into lines lines: List[str] = [line for line in cif_text.splitlines()] @@ -510,37 +560,42 @@ def tof_to_d( offset: float, linear: float, quad: float, - quad_eps=1e-20, + quad_eps: float = 1e-20, ) -> np.ndarray: - """Convert time-of-flight (TOF) to d-spacing using a quadratic - calibration. - - Model: - TOF = offset + linear * d + quad * d² - - The function: - - Uses a linear fallback when the quadratic term is effectively - zero. - - Solves the quadratic for d and selects the smallest positive, - finite root. - - Returns NaN where no valid solution exists. - - Expects ``tof`` as a NumPy array; output matches its shape. - - Args: - tof (np.ndarray): Time-of-flight values (µs). Must be a NumPy - array. - offset (float): Calibration offset (µs). - linear (float): Linear calibration coefficient (µs/Å). - quad (float): Quadratic calibration coefficient (µs/Ų). - quad_eps (float, optional): Threshold to treat ``quad`` as zero. - Defaults to 1e-20. - - Returns: - np.ndarray: d-spacing values (Å), NaN where invalid. - - Raises: - TypeError: If ``tof`` is not a NumPy array or coefficients are - not real numbers. + """ + Convert time-of-flight to d-spacing using quadratic calibration. + + Model: TOF = offset + linear * d + quad * d² + + The function: - Uses a linear fallback when the quadratic term is + effectively zero. - Solves the quadratic for d and selects the + smallest positive, finite root. - Returns NaN where no valid + solution exists. - Expects ``tof`` as a NumPy array; output matches + its shape. + + Parameters + ---------- + tof : np.ndarray + Time-of-flight values (µs). Must be a NumPy array. + offset : float + Calibration offset (µs). + linear : float + Linear calibration coefficient (µs/Å). + quad : float + Quadratic calibration coefficient (µs/Ų). + quad_eps : float, default=1e-20 + Threshold to treat ``quad`` as zero. + + Returns + ------- + np.ndarray + d-spacing values (Å), NaN where invalid. + + Raises + ------ + TypeError + If ``tof`` is not a NumPy array or coefficients are not real + numbers. """ # Type checks if not isinstance(tof, np.ndarray): @@ -594,15 +649,21 @@ def tof_to_d( return d_out -def twotheta_to_d(twotheta, wavelength): - """Convert 2-theta to d-spacing using Bragg's law. +def twotheta_to_d(twotheta: object, wavelength: float) -> object: + """ + Convert 2-theta to d-spacing using Bragg's law. - Parameters: - twotheta (float or np.ndarray): 2-theta angle in degrees. - wavelength (float): Wavelength in Å. + Parameters + ---------- + twotheta : object + 2-theta angle in degrees (float or np.ndarray). + wavelength : float + Wavelength in Å. - Returns: - d (float or np.ndarray): d-spacing in Å. + Returns + ------- + object + d-spacing in Å (float or np.ndarray). """ # Convert twotheta from degrees to radians theta_rad = np.radians(twotheta / 2) @@ -613,15 +674,19 @@ def twotheta_to_d(twotheta, wavelength): return d -def sin_theta_over_lambda_to_d_spacing(sin_theta_over_lambda): - """Convert sin(theta)/lambda to d-spacing. +def sin_theta_over_lambda_to_d_spacing(sin_theta_over_lambda: object) -> object: + """ + Convert sin(theta)/lambda to d-spacing. - Parameters: - sin_theta_over_lambda (float or np.ndarray): sin(theta)/lambda - in 1/Å. + Parameters + ---------- + sin_theta_over_lambda : object + sin(theta)/lambda in 1/Å (float or np.ndarray). - Returns: - d (float or np.ndarray): d-spacing in Å. + Returns + ------- + object + d-spacing in Å (float or np.ndarray). """ # Avoid division by zero with np.errstate(divide='ignore', invalid='ignore'): @@ -631,19 +696,26 @@ def sin_theta_over_lambda_to_d_spacing(sin_theta_over_lambda): return d -def get_value_from_xye_header(file_path, key): - """Extracts a floating point value from the first line of the file, - corresponding to the given key. +def get_value_from_xye_header(file_path: str, key: str) -> float: + """ + Extract a float from the first line of the file by key. - Parameters: - file_path (str): Path to the input file. - key (str): The key to extract ('DIFC' or 'two_theta'). + Parameters + ---------- + file_path : str + Path to the input file. + key : str + The key to extract ('DIFC' or 'two_theta'). - Returns: - float: The extracted value. + Returns + ------- + float + The extracted value. - Raises: - ValueError: If the key is not found. + Raises + ------ + ValueError + If the key is not found. """ pattern = rf'{key}\s*=\s*([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)' @@ -658,35 +730,30 @@ def get_value_from_xye_header(file_path, key): def str_to_ufloat(s: Optional[str], default: Optional[float] = None) -> UFloat: - """Parse a CIF-style numeric string into a `ufloat` with an optional - uncertainty. - - Examples of supported input: - - "3.566" → ufloat(3.566, nan) - - "3.566(2)" → ufloat(3.566, 0.002) - - None → ufloat(default, nan) - - Behavior: - - If the input string contains a value with parentheses (e.g. - "3.566(2)"), the number in parentheses is interpreted as an - estimated standard deviation (esd) in the last digit(s). - - If the input string has no parentheses, an uncertainty of NaN is - assigned to indicate "no esd provided". - - If parsing fails, the function falls back to the given `default` - value with uncertainty NaN. + """ + Parse a CIF-style numeric string into a ufloat. + + Examples of supported input: - "3.566" → ufloat(3.566, nan) - + "3.566(2)" → ufloat(3.566, 0.002) - None → ufloat(default, nan) + + Behavior: - If the input string contains a value with parentheses + (e.g. "3.566(2)"), the number in parentheses is interpreted as an + estimated standard deviation (esd) in the last digit(s). - If the + input string has no parentheses, an uncertainty of NaN is assigned + to indicate "no esd provided". - If parsing fails, the function + falls back to the given ``default`` value with uncertainty NaN. Parameters ---------- - s : str or None + s : Optional[str] Numeric string in CIF format (e.g. "3.566", "3.566(2)") or None. - default : float or None, optional - Default value to use if `s` is None or parsing fails. - Defaults to None. + default : Optional[float], default=None + Default value to use if ``s`` is None or parsing fails. - Returns: + Returns ------- UFloat - An `uncertainties.UFloat` object with the parsed value and + An ``uncertainties.UFloat`` object with the parsed value and uncertainty. The uncertainty will be NaN if not specified or parsing failed. """ diff --git a/tests/integration/fitting/test_multi.py b/tests/integration/fitting/test_multi.py index 60e78f0a..6b1a0c8f 100644 --- a/tests/integration/fitting/test_multi.py +++ b/tests/integration/fitting/test_multi.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import tempfile diff --git a/tests/integration/fitting/test_pair-distribution-function.py b/tests/integration/fitting/test_pair-distribution-function.py index 823fd420..066e727e 100644 --- a/tests/integration/fitting/test_pair-distribution-function.py +++ b/tests/integration/fitting/test_pair-distribution-function.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import tempfile diff --git a/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py b/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py index 49e7b4b1..5045dd27 100644 --- a/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py +++ b/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import tempfile diff --git a/tests/integration/fitting/test_powder-diffraction_joint-fit.py b/tests/integration/fitting/test_powder-diffraction_joint-fit.py index cc3e600e..fb94a8ff 100644 --- a/tests/integration/fitting/test_powder-diffraction_joint-fit.py +++ b/tests/integration/fitting/test_powder-diffraction_joint-fit.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import tempfile diff --git a/tests/integration/fitting/test_powder-diffraction_time-of-flight.py b/tests/integration/fitting/test_powder-diffraction_time-of-flight.py index ae702b4d..18123254 100644 --- a/tests/integration/fitting/test_powder-diffraction_time-of-flight.py +++ b/tests/integration/fitting/test_powder-diffraction_time-of-flight.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import tempfile diff --git a/tests/integration/fitting/test_single-crystal-diffraction.py b/tests/integration/fitting/test_single-crystal-diffraction.py index af915d92..f5273fdd 100644 --- a/tests/integration/fitting/test_single-crystal-diffraction.py +++ b/tests/integration/fitting/test_single-crystal-diffraction.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import tempfile diff --git a/tests/integration/scipp-analysis/dream/conftest.py b/tests/integration/scipp-analysis/dream/conftest.py index 56aabf1f..b16da0a1 100644 --- a/tests/integration/scipp-analysis/dream/conftest.py +++ b/tests/integration/scipp-analysis/dream/conftest.py @@ -1,5 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026 DMSC + """Shared fixtures for DREAM scipp-analysis integration tests. This module provides pytest fixtures for downloading and parsing diff --git a/tests/integration/scipp-analysis/dream/test_analyze_reduced_data.py b/tests/integration/scipp-analysis/dream/test_analyze_reduced_data.py index cde09c8a..23821ee2 100644 --- a/tests/integration/scipp-analysis/dream/test_analyze_reduced_data.py +++ b/tests/integration/scipp-analysis/dream/test_analyze_reduced_data.py @@ -1,5 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026 DMSC + """Tests for analyzing reduced diffraction data using easydiffraction. These tests verify the complete workflow: diff --git a/tests/integration/scipp-analysis/dream/test_package_import.py b/tests/integration/scipp-analysis/dream/test_package_import.py index 7c10d02b..03125806 100644 --- a/tests/integration/scipp-analysis/dream/test_package_import.py +++ b/tests/integration/scipp-analysis/dream/test_package_import.py @@ -1,5 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026 DMSC + """Tests for verifying package installation and version consistency. These tests check that easydiffraction and essdiffraction packages are diff --git a/tests/integration/scipp-analysis/dream/test_read_reduced_data.py b/tests/integration/scipp-analysis/dream/test_read_reduced_data.py index 616c9876..db589354 100644 --- a/tests/integration/scipp-analysis/dream/test_read_reduced_data.py +++ b/tests/integration/scipp-analysis/dream/test_read_reduced_data.py @@ -1,5 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026 DMSC + """Tests for reading reduced data from CIF files. These tests verify that the CIF file can be fetched, read as text, diff --git a/tests/integration/scipp-analysis/dream/test_validate_meta_data.py b/tests/integration/scipp-analysis/dream/test_validate_meta_data.py index 7712dbc3..6f07845a 100644 --- a/tests/integration/scipp-analysis/dream/test_validate_meta_data.py +++ b/tests/integration/scipp-analysis/dream/test_validate_meta_data.py @@ -1,5 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026 DMSC + """Tests for validating metadata structure in CIF files. These tests verify that the CIF file contains the expected data blocks, diff --git a/tests/integration/scipp-analysis/dream/test_validate_physical_data.py b/tests/integration/scipp-analysis/dream/test_validate_physical_data.py index f1be5eb3..a8e5d4fb 100644 --- a/tests/integration/scipp-analysis/dream/test_validate_physical_data.py +++ b/tests/integration/scipp-analysis/dream/test_validate_physical_data.py @@ -1,5 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026 DMSC + """Tests for validating physical data values in CIF files. These tests verify that numerical data columns contain valid, diff --git a/tests/unit/easydiffraction/analysis/calculators/test_base.py b/tests/unit/easydiffraction/analysis/calculators/test_base.py index 6483af6e..5070b9a3 100644 --- a/tests/unit/easydiffraction/analysis/calculators/test_base.py +++ b/tests/unit/easydiffraction/analysis/calculators/test_base.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.analysis.calculators.base as MUT diff --git a/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py b/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py index e35d1bd5..a6a1371c 100644 --- a/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py +++ b/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest diff --git a/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py b/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py index bccd63ec..8593bfe0 100644 --- a/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py +++ b/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.analysis.calculators.cryspy as MUT diff --git a/tests/unit/easydiffraction/analysis/calculators/test_factory.py b/tests/unit/easydiffraction/analysis/calculators/test_factory.py index 7df08b97..681299fa 100644 --- a/tests/unit/easydiffraction/analysis/calculators/test_factory.py +++ b/tests/unit/easydiffraction/analysis/calculators/test_factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest diff --git a/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py b/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py index 268d0d84..9dce59f5 100644 --- a/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py +++ b/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/analysis/categories/test_aliases.py b/tests/unit/easydiffraction/analysis/categories/test_aliases.py index 4860961d..2545218a 100644 --- a/tests/unit/easydiffraction/analysis/categories/test_aliases.py +++ b/tests/unit/easydiffraction/analysis/categories/test_aliases.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.categories.aliases import Alias @@ -7,8 +7,8 @@ def test_alias_creation_and_collection(): a = Alias() - a.label='x' - a.param_uid='p1' + a.label = 'x' + a.param_uid = 'p1' assert a.label.value == 'x' coll = Aliases() coll.create(label='x', param_uid='p1') diff --git a/tests/unit/easydiffraction/analysis/categories/test_constraints.py b/tests/unit/easydiffraction/analysis/categories/test_constraints.py index 7d4acb0a..443f9b1b 100644 --- a/tests/unit/easydiffraction/analysis/categories/test_constraints.py +++ b/tests/unit/easydiffraction/analysis/categories/test_constraints.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.categories.constraints import Constraint @@ -7,8 +7,8 @@ def test_constraint_creation_and_collection(): c = Constraint() - c.lhs_alias='a' - c.rhs_expr='b + c' + c.lhs_alias = 'a' + c.rhs_expr = 'b + c' assert c.lhs_alias.value == 'a' coll = Constraints() coll.create(lhs_alias='a', rhs_expr='b + c') diff --git a/tests/unit/easydiffraction/analysis/categories/test_joint_fit_experiments.py b/tests/unit/easydiffraction/analysis/categories/test_joint_fit_experiments.py index c4194c35..51777f11 100644 --- a/tests/unit/easydiffraction/analysis/categories/test_joint_fit_experiments.py +++ b/tests/unit/easydiffraction/analysis/categories/test_joint_fit_experiments.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.categories.joint_fit_experiments import JointFitExperiment @@ -7,8 +7,8 @@ def test_joint_fit_experiment_and_collection(): j = JointFitExperiment() - j.id='ex1' - j.weight=0.5 + j.id = 'ex1' + j.weight = 0.5 assert j.id.value == 'ex1' assert j.weight.value == 0.5 coll = JointFitExperiments() diff --git a/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py b/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py index 7c8b75c9..2fc968f8 100644 --- a/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py +++ b/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/analysis/fit_helpers/test_reporting.py b/tests/unit/easydiffraction/analysis/fit_helpers/test_reporting.py index 458519ce..ee8014f2 100644 --- a/tests/unit/easydiffraction/analysis/fit_helpers/test_reporting.py +++ b/tests/unit/easydiffraction/analysis/fit_helpers/test_reporting.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.analysis.fit_helpers.reporting as MUT diff --git a/tests/unit/easydiffraction/analysis/fit_helpers/test_tracking.py b/tests/unit/easydiffraction/analysis/fit_helpers/test_tracking.py index bf3873ab..561eb54c 100644 --- a/tests/unit/easydiffraction/analysis/fit_helpers/test_tracking.py +++ b/tests/unit/easydiffraction/analysis/fit_helpers/test_tracking.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_base.py b/tests/unit/easydiffraction/analysis/minimizers/test_base.py index 5d04a75a..501a2a98 100644 --- a/tests/unit/easydiffraction/analysis/minimizers/test_base.py +++ b/tests/unit/easydiffraction/analysis/minimizers/test_base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py b/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py index 88e22dee..72d01949 100644 --- a/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py +++ b/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_factory.py b/tests/unit/easydiffraction/analysis/minimizers/test_factory.py index 236d1656..961ef782 100644 --- a/tests/unit/easydiffraction/analysis/minimizers/test_factory.py +++ b/tests/unit/easydiffraction/analysis/minimizers/test_factory.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_minimizer_factory_list_and_show(capsys): from easydiffraction.analysis.minimizers.factory import MinimizerFactory diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py b/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py index 77b694ee..69005bd1 100644 --- a/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py +++ b/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import types diff --git a/tests/unit/easydiffraction/analysis/test_analysis.py b/tests/unit/easydiffraction/analysis/test_analysis.py index 56d493aa..19bc3c2a 100644 --- a/tests/unit/easydiffraction/analysis/test_analysis.py +++ b/tests/unit/easydiffraction/analysis/test_analysis.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.analysis.analysis as MUT @@ -36,7 +37,6 @@ def test_show_current_minimizer_prints(capsys): assert 'lmfit' in out - def test_fit_mode_category_and_joint_fit_experiments(monkeypatch, capsys): from easydiffraction.analysis.analysis import Analysis diff --git a/tests/unit/easydiffraction/analysis/test_analysis_access_params.py b/tests/unit/easydiffraction/analysis/test_analysis_access_params.py index 96bf6ca1..bdd5ead0 100644 --- a/tests/unit/easydiffraction/analysis/test_analysis_access_params.py +++ b/tests/unit/easydiffraction/analysis/test_analysis_access_params.py @@ -1,12 +1,13 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_how_to_access_parameters_prints_paths_and_uids(capsys, monkeypatch): + import easydiffraction.analysis.analysis as analysis_mod from easydiffraction.analysis.analysis import Analysis - from easydiffraction.core.variable import Parameter from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import Parameter from easydiffraction.io.cif.handler import CifHandler - import easydiffraction.analysis.analysis as analysis_mod # Build two parameters with identity metadata set directly def make_param(db, cat, entry, name, val): diff --git a/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py b/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py index 921127ba..7f2895b4 100644 --- a/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py +++ b/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_show_params_empty_branches(capsys): from easydiffraction.analysis.analysis import Analysis diff --git a/tests/unit/easydiffraction/analysis/test_fitting.py b/tests/unit/easydiffraction/analysis/test_fitting.py index d3a1b1fb..2f703f77 100644 --- a/tests/unit/easydiffraction/analysis/test_fitting.py +++ b/tests/unit/easydiffraction/analysis/test_fitting.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.analysis.fitting as MUT diff --git a/tests/unit/easydiffraction/core/test_category.py b/tests/unit/easydiffraction/core/test_category.py index c53fd12c..632d96f1 100644 --- a/tests/unit/easydiffraction/core/test_category.py +++ b/tests/unit/easydiffraction/core/test_category.py @@ -1,10 +1,10 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.core.category import CategoryCollection from easydiffraction.core.category import CategoryItem -from easydiffraction.core.variable import StringDescriptor from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.variable import StringDescriptor from easydiffraction.io.cif.handler import CifHandler @@ -97,5 +97,3 @@ def test_category_collection_help(capsys): assert 'Items (2)' in out assert 'n1' in out assert 'n2' in out - - diff --git a/tests/unit/easydiffraction/core/test_collection.py b/tests/unit/easydiffraction/core/test_collection.py index 616b232f..817b76dd 100644 --- a/tests/unit/easydiffraction/core/test_collection.py +++ b/tests/unit/easydiffraction/core/test_collection.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_collection_add_get_delete_and_names(): from easydiffraction.core.collection import CollectionBase from easydiffraction.core.identity import Identity @@ -55,11 +56,11 @@ def as_cif(self) -> str: def test_collection_remove(): + import pytest + from easydiffraction.core.collection import CollectionBase from easydiffraction.core.identity import Identity - import pytest - class Item: def __init__(self, name): self._identity = Identity(owner=self, category_entry=lambda: name) @@ -119,4 +120,3 @@ def as_cif(self) -> str: del c['beta'] assert 'beta' not in c assert len(c) == 1 - diff --git a/tests/unit/easydiffraction/core/test_datablock.py b/tests/unit/easydiffraction/core/test_datablock.py index 6565bbaa..b41f68b7 100644 --- a/tests/unit/easydiffraction/core/test_datablock.py +++ b/tests/unit/easydiffraction/core/test_datablock.py @@ -1,12 +1,13 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_datablock_collection_add_and_filters_with_real_parameters(): from easydiffraction.core.category import CategoryItem from easydiffraction.core.datablock import DatablockCollection from easydiffraction.core.datablock import DatablockItem - from easydiffraction.core.variable import Parameter from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import Parameter from easydiffraction.io.cif.handler import CifHandler class Cat(CategoryItem): @@ -78,8 +79,8 @@ def cat(self): def test_datablock_item_help(capsys): from easydiffraction.core.category import CategoryItem from easydiffraction.core.datablock import DatablockItem - from easydiffraction.core.variable import Parameter from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import Parameter from easydiffraction.io.cif.handler import CifHandler class Cat(CategoryItem): @@ -133,4 +134,3 @@ def __init__(self, name): out = capsys.readouterr().out assert 'Items (1)' in out assert 'A' in out - diff --git a/tests/unit/easydiffraction/core/test_diagnostic.py b/tests/unit/easydiffraction/core/test_diagnostic.py index 98e96320..cda7ce98 100644 --- a/tests/unit/easydiffraction/core/test_diagnostic.py +++ b/tests/unit/easydiffraction/core/test_diagnostic.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest diff --git a/tests/unit/easydiffraction/core/test_factory.py b/tests/unit/easydiffraction/core/test_factory.py index ee85b97c..78150ea5 100644 --- a/tests/unit/easydiffraction/core/test_factory.py +++ b/tests/unit/easydiffraction/core/test_factory.py @@ -1,5 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause - -# core/factory.py was removed — FactoryBase and _validate_args are no -# longer part of the codebase. This test file is intentionally empty. diff --git a/tests/unit/easydiffraction/core/test_guard.py b/tests/unit/easydiffraction/core/test_guard.py index 34407914..64d46680 100644 --- a/tests/unit/easydiffraction/core/test_guard.py +++ b/tests/unit/easydiffraction/core/test_guard.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest @@ -99,5 +99,3 @@ def test_first_sentence_extracts_first_paragraph(): assert GuardedBase._first_sentence('One liner.') == 'One liner.' assert GuardedBase._first_sentence('First.\n\nSecond.') == 'First.' assert GuardedBase._first_sentence('Line one\ncontinued.') == 'Line one continued.' - - diff --git a/tests/unit/easydiffraction/core/test_identity.py b/tests/unit/easydiffraction/core/test_identity.py index 584135d7..61da0723 100644 --- a/tests/unit/easydiffraction/core/test_identity.py +++ b/tests/unit/easydiffraction/core/test_identity.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_identity_direct_and_parent_resolution(): from easydiffraction.core.identity import Identity diff --git a/tests/unit/easydiffraction/core/test_parameters.py b/tests/unit/easydiffraction/core/test_parameters.py index 35c65558..4bf46f0d 100644 --- a/tests/unit/easydiffraction/core/test_parameters.py +++ b/tests/unit/easydiffraction/core/test_parameters.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -13,9 +13,9 @@ def test_module_import(): def test_string_descriptor_type_override_raises_type_error(): # Creating a StringDescriptor with a NUMERIC spec should raise via Diagnostics - from easydiffraction.core.variable import StringDescriptor from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import DataTypes + from easydiffraction.core.variable import StringDescriptor from easydiffraction.io.cif.handler import CifHandler with pytest.raises(TypeError): @@ -28,8 +28,8 @@ def test_string_descriptor_type_override_raises_type_error(): def test_numeric_descriptor_str_includes_units(): - from easydiffraction.core.variable import NumericDescriptor from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import NumericDescriptor from easydiffraction.io.cif.handler import CifHandler d = NumericDescriptor( @@ -43,8 +43,8 @@ def test_numeric_descriptor_str_includes_units(): def test_parameter_string_repr_and_as_cif_and_flags(): - from easydiffraction.core.variable import Parameter from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import Parameter from easydiffraction.io.cif.handler import CifHandler p = Parameter( @@ -69,8 +69,8 @@ def test_parameter_string_repr_and_as_cif_and_flags(): def test_parameter_uncertainty_must_be_non_negative(): - from easydiffraction.core.variable import Parameter from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import Parameter from easydiffraction.io.cif.handler import CifHandler p = Parameter( @@ -83,8 +83,8 @@ def test_parameter_uncertainty_must_be_non_negative(): def test_parameter_fit_bounds_assign_and_read(): - from easydiffraction.core.variable import Parameter from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import Parameter from easydiffraction.io.cif.handler import CifHandler p = Parameter( diff --git a/tests/unit/easydiffraction/core/test_singletons.py b/tests/unit/easydiffraction/core/test_singletons.py index bd2c5aef..ba69f07a 100644 --- a/tests/unit/easydiffraction/core/test_singletons.py +++ b/tests/unit/easydiffraction/core/test_singletons.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest diff --git a/tests/unit/easydiffraction/core/test_validation.py b/tests/unit/easydiffraction/core/test_validation.py index 70a92bb5..3bbd0ac3 100644 --- a/tests/unit/easydiffraction/core/test_validation.py +++ b/tests/unit/easydiffraction/core/test_validation.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.core.validation as MUT diff --git a/tests/unit/easydiffraction/crystallography/test_crystallography.py b/tests/unit/easydiffraction/crystallography/test_crystallography.py index 3c2bf7df..73e1a134 100644 --- a/tests/unit/easydiffraction/crystallography/test_crystallography.py +++ b/tests/unit/easydiffraction/crystallography/test_crystallography.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.crystallography.crystallography as MUT diff --git a/tests/unit/easydiffraction/crystallography/test_space_groups.py b/tests/unit/easydiffraction/crystallography/test_space_groups.py index 5629f633..dd1482cd 100644 --- a/tests/unit/easydiffraction/crystallography/test_space_groups.py +++ b/tests/unit/easydiffraction/crystallography/test_space_groups.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.crystallography.space_groups as MUT diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_base.py b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_base.py index 31c7fd0b..88049e83 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_base.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -7,9 +7,9 @@ def test_background_base_minimal_impl_and_collection_cif(): from easydiffraction.core.category import CategoryItem from easydiffraction.core.collection import CollectionBase - from easydiffraction.core.variable import Parameter from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import DataTypes + from easydiffraction.core.variable import Parameter from easydiffraction.datablocks.experiment.categories.background.base import BackgroundBase from easydiffraction.io.cif.handler import CifHandler diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_chebyshev.py b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_chebyshev.py index b59ea102..d10a3c40 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_chebyshev.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_chebyshev.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_enums.py b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_enums.py index 47e0f5f3..b37a22e6 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_enums.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_enums.py @@ -1,14 +1,14 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause def test_background_type_info(): - from easydiffraction.datablocks.experiment.categories.background.line_segment import ( - LineSegmentBackground, - ) from easydiffraction.datablocks.experiment.categories.background.chebyshev import ( ChebyshevPolynomialBackground, ) + from easydiffraction.datablocks.experiment.categories.background.line_segment import ( + LineSegmentBackground, + ) assert LineSegmentBackground.type_info.tag == 'line-segment' assert LineSegmentBackground.type_info.description == 'Linear interpolation between points' diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_factory.py index 15c40a19..09679489 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_factory.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_factory.py @@ -1,11 +1,13 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest def test_background_factory_default_and_errors(): - from easydiffraction.datablocks.experiment.categories.background.factory import BackgroundFactory + from easydiffraction.datablocks.experiment.categories.background.factory import ( + BackgroundFactory, + ) # Default via default_tag() obj = BackgroundFactory.create(BackgroundFactory.default_tag()) diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_line_segment.py b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_line_segment.py index e4e89605..ff231943 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_line_segment.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/background/test_line_segment.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_pd.py b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_pd.py index eaa48808..1a3f233c 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_pd.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -142,4 +142,3 @@ def test_pd_data_intensity_meas_su_zero_replacement(): assert su[0] == 1.0 # replaced assert su[1] == 1.0 # replaced assert su[2] == 5.0 # kept - diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_sc.py b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_sc.py index 06dd634d..534fd192 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_sc.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_bragg_sc.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -90,4 +90,3 @@ def test_refln_data_type_info(): assert ReflnData.type_info.tag == 'bragg-sc' assert ReflnData.type_info.description == 'Bragg single-crystal reflection data' - diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_factory.py index 18ecd5d0..6f52cdff 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_factory.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_factory.py @@ -1,16 +1,15 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest def test_data_factory_default_and_errors(): - from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory - # Ensure concrete classes are registered from easydiffraction.datablocks.experiment.categories.data import bragg_pd # noqa: F401 from easydiffraction.datablocks.experiment.categories.data import bragg_sc # noqa: F401 from easydiffraction.datablocks.experiment.categories.data import total_pd # noqa: F401 + from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory # Explicit type by tag obj = DataFactory.create('bragg-pd') @@ -32,15 +31,14 @@ def test_data_factory_default_and_errors(): def test_data_factory_default_tag_resolution(): - from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory - from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum - from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum - from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum - # Ensure concrete classes are registered from easydiffraction.datablocks.experiment.categories.data import bragg_pd # noqa: F401 from easydiffraction.datablocks.experiment.categories.data import bragg_sc # noqa: F401 from easydiffraction.datablocks.experiment.categories.data import total_pd # noqa: F401 + from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory + from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum + from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum + from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum # Context-dependent default: Bragg powder CWL tag = DataFactory.default_tag( @@ -74,16 +72,14 @@ def test_data_factory_default_tag_resolution(): def test_data_factory_supported_tags(): - from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory - # Ensure concrete classes are registered from easydiffraction.datablocks.experiment.categories.data import bragg_pd # noqa: F401 from easydiffraction.datablocks.experiment.categories.data import bragg_sc # noqa: F401 from easydiffraction.datablocks.experiment.categories.data import total_pd # noqa: F401 + from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory tags = DataFactory.supported_tags() assert 'bragg-pd' in tags assert 'bragg-pd-tof' in tags assert 'bragg-sc' in tags assert 'total-pd' in tags - diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_total_pd.py b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_total_pd.py index 90c85e3e..41e638e5 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_total_pd.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/data/test_total_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -89,4 +89,3 @@ def test_total_data_type_info(): assert TotalData.type_info.tag == 'total-pd' assert TotalData.type_info.description == 'Total scattering (PDF) data' - diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_base.py b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_base.py index 70d36b8a..38bcb8c7 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_base.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_base.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_instrument_base_sets_category_code(): from easydiffraction.datablocks.experiment.categories.instrument.base import InstrumentBase diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_cwl.py b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_cwl.py index 205abd50..0816f6e7 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_cwl.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_cwl.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.instrument.cwl import CwlPdInstrument diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_factory.py index f122f9be..04117aa9 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_factory.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest @@ -6,7 +6,9 @@ def test_instrument_factory_default_and_errors(): try: - from easydiffraction.datablocks.experiment.categories.instrument.factory import InstrumentFactory + from easydiffraction.datablocks.experiment.categories.instrument.factory import ( + InstrumentFactory, + ) from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum except ImportError as e: # pragma: no cover - environment-specific circular import diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_tof.py b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_tof.py index 91c4f0d0..bfd6cc12 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_tof.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/instrument/test_tof.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_base.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_base.py index ad3708dc..229fc734 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_base.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.peak.base import PeakBase diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl.py index f4505287..d941afe9 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_cwl_peak_classes_expose_expected_parameters_and_category(): from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlPseudoVoigt from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlSplitPseudoVoigt diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl_mixins.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl_mixins.py index 3bdc4466..19026f50 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl_mixins.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_cwl_mixins.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.peak.cwl import CwlPseudoVoigt diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_factory.py index 6ff155ac..4b0ccd35 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_factory.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof.py index fde6d062..78e25d52 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.peak.tof import TofPseudoVoigt diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof_mixins.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof_mixins.py index ae39c99c..c2f114a0 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof_mixins.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_tof_mixins.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -6,10 +6,16 @@ def test_tof_broadening_and_asymmetry_mixins(): from easydiffraction.datablocks.experiment.categories.peak.base import PeakBase - from easydiffraction.datablocks.experiment.categories.peak.tof_mixins import IkedaCarpenterAsymmetryMixin + from easydiffraction.datablocks.experiment.categories.peak.tof_mixins import ( + IkedaCarpenterAsymmetryMixin, + ) from easydiffraction.datablocks.experiment.categories.peak.tof_mixins import TofBroadeningMixin - class TofPeak(PeakBase, TofBroadeningMixin, IkedaCarpenterAsymmetryMixin,): + class TofPeak( + PeakBase, + TofBroadeningMixin, + IkedaCarpenterAsymmetryMixin, + ): def __init__(self): super().__init__() diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total.py index c49d1cf7..46a6497b 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total_mixins.py b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total_mixins.py index 0979dcb9..c3534847 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total_mixins.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/peak/test_total_mixins.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.experiment.categories.peak.total import TotalGaussianDampedSinc diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/test_excluded_regions.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_excluded_regions.py index 8026740b..8f34b8de 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/test_excluded_regions.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_excluded_regions.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -23,7 +23,7 @@ def test_excluded_regions_add_updates_datastore_and_cif(): meas=full_meas.copy(), meas_su=full_meas_su.copy(), ) - + def set_calc_status(status): # _set_calc_status sets excluded to the inverse ds.excluded = ~status @@ -31,7 +31,7 @@ def set_calc_status(status): ds.x = ds.full_x[status] ds.meas = ds.full_meas[status] ds.meas_su = ds.full_meas_su[status] - + ds._set_calc_status = set_calc_status coll = ExcludedRegions() diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/test_experiment_type.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_experiment_type.py index 169510ab..9190071e 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/test_experiment_type.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_experiment_type.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.datablocks.experiment.categories.experiment_type as MUT diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py index 5287b488..dcee6f6e 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause @@ -74,5 +74,3 @@ def test_extinction_factory_default_tag(): ) assert ExtinctionFactory.default_tag() == 'shelx' - - diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_crystal.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_crystal.py index 06035598..69f96e7b 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_crystal.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_crystal.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause @@ -86,5 +86,3 @@ def test_linked_crystal_factory_default_tag(): ) assert LinkedCrystalFactory.default_tag() == 'default' - - diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_phases.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_phases.py index 263586f8..3b4b9fa7 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_phases.py +++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_linked_phases.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_linked_phases_add_and_cif_headers(): from easydiffraction.datablocks.experiment.categories.linked_phases import LinkedPhase from easydiffraction.datablocks.experiment.categories.linked_phases import LinkedPhases diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_base.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_base.py index 6a6766ed..c2a0ab92 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/item/test_base.py +++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_base.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.datablocks.experiment.item.base as MUT diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_pd.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_pd.py index 8b164ed0..89c763dc 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_pd.py +++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np @@ -22,8 +22,6 @@ def _mk_type_powder_cwl_bragg(): return et - - def test_background_defaults_and_change(): expt = BraggPdExperiment(name='e1', type=_mk_type_powder_cwl_bragg()) # default background type diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_sc.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_sc.py index c01a5ee2..a69dd1bd 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_sc.py +++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_sc.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import pytest @@ -21,7 +21,6 @@ def _mk_type_sc_bragg(): return et - class _ConcreteCwlSc(CwlScExperiment): def _load_ascii_data_to_experiment(self, data_path: str) -> None: # Not used in this test diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_enums.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_enums.py index dff44c0f..983f991b 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/item/test_enums.py +++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_enums.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.datablocks.experiment.item.enums as MUT diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py index 8dac91b6..c185ed30 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py +++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.datablocks.experiment.item.factory as MUT @@ -25,4 +26,3 @@ def test_experiment_factory_from_scratch(): ) # Instance should be created (BraggPdExperiment) assert hasattr(ex, 'type') and ex.type.sample_form.value == SampleFormEnum.POWDER.value - diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_total_pd.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_total_pd.py index 78d40afc..d021dc34 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/item/test_total_pd.py +++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_total_pd.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/datablocks/experiment/test_collection.py b/tests/unit/easydiffraction/datablocks/experiment/test_collection.py index 5165f141..bcb9f822 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/test_collection.py +++ b/tests/unit/easydiffraction/datablocks/experiment/test_collection.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.datablocks.experiment.collection as MUT @@ -10,8 +11,8 @@ def test_module_import(): def test_experiments_show_and_remove(monkeypatch, capsys): - from easydiffraction.datablocks.experiment.item.base import ExperimentBase from easydiffraction.datablocks.experiment.collection import Experiments + from easydiffraction.datablocks.experiment.item.base import ExperimentBase class DummyType: def __init__(self): diff --git a/tests/unit/easydiffraction/datablocks/structure/categories/test_space_group.py b/tests/unit/easydiffraction/datablocks/structure/categories/test_space_group.py index 89786b9e..4025bd83 100644 --- a/tests/unit/easydiffraction/datablocks/structure/categories/test_space_group.py +++ b/tests/unit/easydiffraction/datablocks/structure/categories/test_space_group.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.structure.categories.space_group import SpaceGroup diff --git a/tests/unit/easydiffraction/datablocks/structure/item/test_base.py b/tests/unit/easydiffraction/datablocks/structure/item/test_base.py index 33bb878b..154d0405 100644 --- a/tests/unit/easydiffraction/datablocks/structure/item/test_base.py +++ b/tests/unit/easydiffraction/datablocks/structure/item/test_base.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.structure.item.base import Structure diff --git a/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py b/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py index d1b50776..148e010a 100644 --- a/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py +++ b/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.datablocks.structure.item.factory import StructureFactory diff --git a/tests/unit/easydiffraction/datablocks/structure/test_collection.py b/tests/unit/easydiffraction/datablocks/structure/test_collection.py index 9955a1e3..4e798e20 100644 --- a/tests/unit/easydiffraction/datablocks/structure/test_collection.py +++ b/tests/unit/easydiffraction/datablocks/structure/test_collection.py @@ -1,6 +1,2 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause - -import pytest - -from easydiffraction.datablocks.structure.collection import Structures diff --git a/tests/unit/easydiffraction/display/plotters/test_ascii.py b/tests/unit/easydiffraction/display/plotters/test_ascii.py index 616bc60d..e0c04f8b 100644 --- a/tests/unit/easydiffraction/display/plotters/test_ascii.py +++ b/tests/unit/easydiffraction/display/plotters/test_ascii.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np diff --git a/tests/unit/easydiffraction/display/plotters/test_base.py b/tests/unit/easydiffraction/display/plotters/test_base.py index d1c2d561..b1029d2c 100644 --- a/tests/unit/easydiffraction/display/plotters/test_base.py +++ b/tests/unit/easydiffraction/display/plotters/test_base.py @@ -1,9 +1,8 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -import importlib -import types import sys +import types def test_module_import(): @@ -36,10 +35,34 @@ def test_default_axes_labels_keys_present(): from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum # Powder Bragg - assert (SampleFormEnum.POWDER, ScatteringTypeEnum.BRAGG, pb.XAxisType.TWO_THETA) in pb.DEFAULT_AXES_LABELS - assert (SampleFormEnum.POWDER, ScatteringTypeEnum.BRAGG, pb.XAxisType.TIME_OF_FLIGHT) in pb.DEFAULT_AXES_LABELS - assert (SampleFormEnum.POWDER, ScatteringTypeEnum.BRAGG, pb.XAxisType.D_SPACING) in pb.DEFAULT_AXES_LABELS + assert ( + SampleFormEnum.POWDER, + ScatteringTypeEnum.BRAGG, + pb.XAxisType.TWO_THETA, + ) in pb.DEFAULT_AXES_LABELS + assert ( + SampleFormEnum.POWDER, + ScatteringTypeEnum.BRAGG, + pb.XAxisType.TIME_OF_FLIGHT, + ) in pb.DEFAULT_AXES_LABELS + assert ( + SampleFormEnum.POWDER, + ScatteringTypeEnum.BRAGG, + pb.XAxisType.D_SPACING, + ) in pb.DEFAULT_AXES_LABELS # Single crystal Bragg - assert (SampleFormEnum.SINGLE_CRYSTAL, ScatteringTypeEnum.BRAGG, pb.XAxisType.INTENSITY_CALC) in pb.DEFAULT_AXES_LABELS - assert (SampleFormEnum.SINGLE_CRYSTAL, ScatteringTypeEnum.BRAGG, pb.XAxisType.D_SPACING) in pb.DEFAULT_AXES_LABELS - assert (SampleFormEnum.SINGLE_CRYSTAL, ScatteringTypeEnum.BRAGG, pb.XAxisType.SIN_THETA_OVER_LAMBDA) in pb.DEFAULT_AXES_LABELS + assert ( + SampleFormEnum.SINGLE_CRYSTAL, + ScatteringTypeEnum.BRAGG, + pb.XAxisType.INTENSITY_CALC, + ) in pb.DEFAULT_AXES_LABELS + assert ( + SampleFormEnum.SINGLE_CRYSTAL, + ScatteringTypeEnum.BRAGG, + pb.XAxisType.D_SPACING, + ) in pb.DEFAULT_AXES_LABELS + assert ( + SampleFormEnum.SINGLE_CRYSTAL, + ScatteringTypeEnum.BRAGG, + pb.XAxisType.SIN_THETA_OVER_LAMBDA, + ) in pb.DEFAULT_AXES_LABELS diff --git a/tests/unit/easydiffraction/display/plotters/test_plotly.py b/tests/unit/easydiffraction/display/plotters/test_plotly.py index 06539f2b..5a1549f0 100644 --- a/tests/unit/easydiffraction/display/plotters/test_plotly.py +++ b/tests/unit/easydiffraction/display/plotters/test_plotly.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.display.plotters.plotly as MUT diff --git a/tests/unit/easydiffraction/display/test_plotting.py b/tests/unit/easydiffraction/display/test_plotting.py index 15caf344..be5b4239 100644 --- a/tests/unit/easydiffraction/display/test_plotting.py +++ b/tests/unit/easydiffraction/display/test_plotting.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.display.plotting as MUT @@ -59,7 +60,9 @@ def test_plotter_error_paths_and_filtering(capsys): from easydiffraction.display.plotting import Plotter class Ptn: - def __init__(self, two_theta=None, intensity_meas=None, intensity_calc=None, d_spacing=None): + def __init__( + self, two_theta=None, intensity_meas=None, intensity_calc=None, d_spacing=None + ): self.two_theta = two_theta self.intensity_meas = intensity_meas self.intensity_calc = intensity_calc @@ -90,13 +93,19 @@ def __init__(self): out = capsys.readouterr().out assert 'No calculated data available for experiment E' in out - p.plot_meas_vs_calc(Ptn(two_theta=None, intensity_meas=None, intensity_calc=None), 'E', ExptType()) + p.plot_meas_vs_calc( + Ptn(two_theta=None, intensity_meas=None, intensity_calc=None), 'E', ExptType() + ) out = capsys.readouterr().out assert 'No measured data available for experiment E' in out - p.plot_meas_vs_calc(Ptn(two_theta=[1], intensity_meas=None, intensity_calc=[1]), 'E', ExptType()) + p.plot_meas_vs_calc( + Ptn(two_theta=[1], intensity_meas=None, intensity_calc=[1]), 'E', ExptType() + ) out = capsys.readouterr().out assert 'No measured data available for experiment E' in out - p.plot_meas_vs_calc(Ptn(two_theta=[1], intensity_meas=[1], intensity_calc=None), 'E', ExptType()) + p.plot_meas_vs_calc( + Ptn(two_theta=[1], intensity_meas=[1], intensity_calc=None), 'E', ExptType() + ) out = capsys.readouterr().out assert 'No calculated data available for experiment E' in out diff --git a/tests/unit/easydiffraction/io/cif/test_handler.py b/tests/unit/easydiffraction/io/cif/test_handler.py index ea31b833..a9c1be85 100644 --- a/tests/unit/easydiffraction/io/cif/test_handler.py +++ b/tests/unit/easydiffraction/io/cif/test_handler.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_cif_handler_names_and_uid(): import easydiffraction.io.cif.handler as H diff --git a/tests/unit/easydiffraction/io/cif/test_serialize.py b/tests/unit/easydiffraction/io/cif/test_serialize.py index 48f044a1..6b92a0e4 100644 --- a/tests/unit/easydiffraction/io/cif/test_serialize.py +++ b/tests/unit/easydiffraction/io/cif/test_serialize.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.io.cif.serialize as MUT diff --git a/tests/unit/easydiffraction/io/cif/test_serialize_more.py b/tests/unit/easydiffraction/io/cif/test_serialize_more.py index 54f345ee..fc8ef714 100644 --- a/tests/unit/easydiffraction/io/cif/test_serialize_more.py +++ b/tests/unit/easydiffraction/io/cif/test_serialize_more.py @@ -1,9 +1,6 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -import numpy as np -import pytest - def test_datablock_item_to_cif_includes_item_and_collection(): import easydiffraction.io.cif.serialize as MUT diff --git a/tests/unit/easydiffraction/project/test_project.py b/tests/unit/easydiffraction/project/test_project.py index 1a949fc5..c6a64055 100644 --- a/tests/unit/easydiffraction/project/test_project.py +++ b/tests/unit/easydiffraction/project/test_project.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.project.project as MUT @@ -19,4 +20,3 @@ def test_project_help(capsys): assert 'experiments' in out assert 'analysis' in out assert 'summary' in out - diff --git a/tests/unit/easydiffraction/project/test_project_info.py b/tests/unit/easydiffraction/project/test_project_info.py index ef8b061f..8c3455ab 100644 --- a/tests/unit/easydiffraction/project/test_project_info.py +++ b/tests/unit/easydiffraction/project/test_project_info.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.project.project_info as MUT diff --git a/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py b/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py index 5cb103b8..cdeafd35 100644 --- a/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py +++ b/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_project_load_prints_and_sets_path(tmp_path, capsys): import pytest diff --git a/tests/unit/easydiffraction/project/test_project_save.py b/tests/unit/easydiffraction/project/test_project_save.py index eb662dfc..421e8928 100644 --- a/tests/unit/easydiffraction/project/test_project_save.py +++ b/tests/unit/easydiffraction/project/test_project_save.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_project_save_uses_cwd_when_no_explicit_path(monkeypatch, tmp_path, capsys): # Default ProjectInfo.path is cwd; ensure save writes into a temp cwd, not repo root from easydiffraction.project.project import Project diff --git a/tests/unit/easydiffraction/summary/test_summary.py b/tests/unit/easydiffraction/summary/test_summary.py index 29da4e28..b57b5598 100644 --- a/tests/unit/easydiffraction/summary/test_summary.py +++ b/tests/unit/easydiffraction/summary/test_summary.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_summary_as_cif_returns_placeholder_string(): from easydiffraction.summary.summary import Summary @@ -46,11 +47,6 @@ class R: assert 'FITTING' in out - - - - - def test_module_import(): import easydiffraction.summary.summary as MUT diff --git a/tests/unit/easydiffraction/summary/test_summary_details.py b/tests/unit/easydiffraction/summary/test_summary_details.py index 0e9fcf04..d0e0c97b 100644 --- a/tests/unit/easydiffraction/summary/test_summary_details.py +++ b/tests/unit/easydiffraction/summary/test_summary_details.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_summary_crystallographic_and_experimental_sections(capsys): from easydiffraction.summary.summary import Summary diff --git a/tests/unit/easydiffraction/test___init__.py b/tests/unit/easydiffraction/test___init__.py index 5eb8c38f..75f273e1 100644 --- a/tests/unit/easydiffraction/test___init__.py +++ b/tests/unit/easydiffraction/test___init__.py @@ -1,7 +1,6 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Focused tests for package __init__: lazy attributes and error path import importlib from pathlib import Path diff --git a/tests/unit/easydiffraction/test___main__.py b/tests/unit/easydiffraction/test___main__.py index 885f73ef..76ba7cec 100644 --- a/tests/unit/easydiffraction/test___main__.py +++ b/tests/unit/easydiffraction/test___main__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause from typer.testing import CliRunner diff --git a/tests/unit/easydiffraction/utils/test_logging.py b/tests/unit/easydiffraction/utils/test_logging.py index 8503e178..6dd520c2 100644 --- a/tests/unit/easydiffraction/utils/test_logging.py +++ b/tests/unit/easydiffraction/utils/test_logging.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + def test_module_import(): import easydiffraction.utils.logging as MUT diff --git a/tests/unit/easydiffraction/utils/test_theme_detect.py b/tests/unit/easydiffraction/utils/test_theme_detect.py index e9899fb9..c3277a0a 100644 --- a/tests/unit/easydiffraction/utils/test_theme_detect.py +++ b/tests/unit/easydiffraction/utils/test_theme_detect.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause """Unit tests for theme detection module.""" @@ -23,12 +23,7 @@ def test_dark_theme_detection(self, tmp_path: Path) -> None: ) settings_dir = ( - tmp_path - / '.jupyter' - / 'lab' - / 'user-settings' - / '@jupyterlab' - / 'apputils-extension' + tmp_path / '.jupyter' / 'lab' / 'user-settings' / '@jupyterlab' / 'apputils-extension' ) settings_dir.mkdir(parents=True) @@ -45,12 +40,7 @@ def test_light_theme_detection(self, tmp_path: Path) -> None: ) settings_dir = ( - tmp_path - / '.jupyter' - / 'lab' - / 'user-settings' - / '@jupyterlab' - / 'apputils-extension' + tmp_path / '.jupyter' / 'lab' / 'user-settings' / '@jupyterlab' / 'apputils-extension' ) settings_dir.mkdir(parents=True) @@ -76,12 +66,7 @@ def test_comments_in_settings(self, tmp_path: Path) -> None: ) settings_dir = ( - tmp_path - / '.jupyter' - / 'lab' - / 'user-settings' - / '@jupyterlab' - / 'apputils-extension' + tmp_path / '.jupyter' / 'lab' / 'user-settings' / '@jupyterlab' / 'apputils-extension' ) settings_dir.mkdir(parents=True) @@ -119,9 +104,7 @@ def test_vscode_dark_theme(self, tmp_path: Path) -> None: vscode_dir.mkdir() settings_file = vscode_dir / 'settings.json' - settings_file.write_text( - json.dumps({'workbench.colorTheme': 'One Dark Pro'}) - ) + settings_file.write_text(json.dumps({'workbench.colorTheme': 'One Dark Pro'})) with mock.patch.dict(os.environ, {'VSCODE_PID': '12345'}): with mock.patch.object(Path, 'cwd', return_value=tmp_path): @@ -137,9 +120,7 @@ def test_vscode_light_theme(self, tmp_path: Path) -> None: vscode_dir.mkdir() settings_file = vscode_dir / 'settings.json' - settings_file.write_text( - json.dumps({'workbench.colorTheme': 'Light+ (default light)'}) - ) + settings_file.write_text(json.dumps({'workbench.colorTheme': 'Light+ (default light)'})) with mock.patch.dict(os.environ, {'VSCODE_PID': '12345'}): with mock.patch.object(Path, 'cwd', return_value=tmp_path): @@ -160,9 +141,7 @@ def test_vscode_nls_config_dark(self) -> None: class TestCheckSystemPreferences: """Tests for _check_system_preferences function.""" - @pytest.mark.skipif( - not os.sys.platform.startswith('darwin'), reason='macOS only test' - ) + @pytest.mark.skipif(not os.sys.platform.startswith('darwin'), reason='macOS only test') def test_macos_dark_mode(self) -> None: """Test macOS dark mode detection.""" from easydiffraction.utils._vendored.jupyter_dark_detect.detector import ( @@ -174,9 +153,7 @@ def test_macos_dark_mode(self) -> None: mock_run.return_value.stdout = 'Dark' assert _check_system_preferences() is True - @pytest.mark.skipif( - not os.sys.platform.startswith('darwin'), reason='macOS only test' - ) + @pytest.mark.skipif(not os.sys.platform.startswith('darwin'), reason='macOS only test') def test_macos_light_mode(self) -> None: """Test macOS light mode detection.""" from easydiffraction.utils._vendored.jupyter_dark_detect.detector import ( @@ -195,86 +172,82 @@ def test_default_to_false(self) -> None: """Test that is_dark defaults to False when no detection works.""" from easydiffraction.utils._vendored.theme_detect import is_dark - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_jupyterlab_settings', - return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_vscode_settings', + with ( + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_jupyterlab_settings', + return_value=None, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_vscode_settings', return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_javascript_detection', - return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_system_preferences', - return_value=None, - ): - assert is_dark() is False + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_javascript_detection', + return_value=None, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_system_preferences', + return_value=None, + ), + ): + assert is_dark() is False def test_jupyterlab_priority(self) -> None: """Test that JupyterLab settings take priority.""" from easydiffraction.utils._vendored.theme_detect import is_dark - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_jupyterlab_settings', - return_value=True, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_vscode_settings', + with ( + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_jupyterlab_settings', + return_value=True, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_vscode_settings', return_value=False, - ): - assert is_dark() is True + ), + ): + assert is_dark() is True def test_vscode_second_priority(self) -> None: """Test that VS Code settings are checked after JupyterLab.""" from easydiffraction.utils._vendored.theme_detect import is_dark - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_jupyterlab_settings', - return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_vscode_settings', + with ( + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_jupyterlab_settings', + return_value=None, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_vscode_settings', return_value=True, - ): - assert is_dark() is True + ), + ): + assert is_dark() is True def test_javascript_before_system(self) -> None: """Test that JS detection comes before system preferences.""" from easydiffraction.utils._vendored.theme_detect import is_dark - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_jupyterlab_settings', - return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_vscode_settings', + with ( + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_jupyterlab_settings', + return_value=None, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_vscode_settings', return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_javascript_detection', - return_value=True, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_system_preferences', - return_value=False, - ): - # JS detection should win over system prefs - assert is_dark() is True + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_javascript_detection', + return_value=True, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_system_preferences', + return_value=False, + ), + ): + # JS detection should win over system prefs + assert is_dark() is True class TestGetDetectionResult: @@ -284,37 +257,35 @@ def test_returns_dict_with_all_methods(self) -> None: """Test that get_detection_result returns all detection methods.""" from easydiffraction.utils._vendored.theme_detect import get_detection_result - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_jupyterlab_settings', - return_value=True, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_vscode_settings', + with ( + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_jupyterlab_settings', + return_value=True, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_vscode_settings', + return_value=None, + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_javascript_detection', return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_javascript_detection', - return_value=None, - ): - with mock.patch( - 'easydiffraction.utils._vendored.theme_detect.' - '_check_system_preferences', - return_value=False, - ): - result = get_detection_result() - - assert 'jupyterlab_settings' in result - assert 'vscode_settings' in result - assert 'javascript_dom' in result - assert 'system_preferences' in result - - assert result['jupyterlab_settings'] is True - assert result['vscode_settings'] is None - assert result['javascript_dom'] is None - assert result['system_preferences'] is False + ), + mock.patch( + 'easydiffraction.utils._vendored.theme_detect._check_system_preferences', + return_value=False, + ), + ): + result = get_detection_result() + + assert 'jupyterlab_settings' in result + assert 'vscode_settings' in result + assert 'javascript_dom' in result + assert 'system_preferences' in result + + assert result['jupyterlab_settings'] is True + assert result['vscode_settings'] is None + assert result['javascript_dom'] is None + assert result['system_preferences'] is False class TestImports: diff --git a/tests/unit/easydiffraction/utils/test_utils.py b/tests/unit/easydiffraction/utils/test_utils.py index 183178bd..48d5a182 100644 --- a/tests/unit/easydiffraction/utils/test_utils.py +++ b/tests/unit/easydiffraction/utils/test_utils.py @@ -1,11 +1,10 @@ -# SPDX-FileCopyrightText: 2021-2026 EasyDiffraction contributors +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause import numpy as np import pytest - def test_module_import(): import easydiffraction.utils.utils as MUT @@ -129,6 +128,7 @@ def test_is_pycharm_and_is_colab(monkeypatch): def test_render_table_terminal_branch(capsys, monkeypatch): import easydiffraction.utils.utils as MUT + # Ensure non-notebook rendering; on CI/default env it's terminal anyway. MUT.render_table( columns_data=[[1, 2], [3, 4]], @@ -354,7 +354,6 @@ def __exit__(self, *args): def test_resolve_tutorial_url(): - import easydiffraction.utils.utils as MUT # Test with a specific version url_template = 'https://example.com/{version}/tutorials/ed-1/ed-1.ipynb' @@ -362,4 +361,3 @@ def test_resolve_tutorial_url(): # So we just test that the function exists and replaces {version} result = url_template.replace('{version}', '0.8.0') assert result == 'https://example.com/0.8.0/tutorials/ed-1/ed-1.ipynb' - diff --git a/tmp/show_d401.py b/tmp/show_d401.py new file mode 100644 index 00000000..8ac806fc --- /dev/null +++ b/tmp/show_d401.py @@ -0,0 +1,26 @@ +files_lines = [ + ('src/easydiffraction/analysis/calculators/crysfml.py', [108,160,205]), + ('src/easydiffraction/analysis/calculators/cryspy.py', [357,377]), + ('src/easydiffraction/analysis/calculators/pdffit.py', [61]), + ('src/easydiffraction/analysis/minimizers/dfols.py', [55,77]), + ('src/easydiffraction/analysis/minimizers/lmfit.py', [40,66,96,117,140]), + ('src/easydiffraction/core/singleton.py', [28,45,49,65,107,116,138]), + ('src/easydiffraction/core/variable.py', [386]), + ('src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py', [312,317,329,334,339,344,494,567]), + ('src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py', [268,286,291,301,308,313,318]), + ('src/easydiffraction/datablocks/experiment/categories/data/total_pd.py', [195,200,212,217,340]), + ('src/easydiffraction/project/project_info.py', [119]), + ('src/easydiffraction/utils/environment.py', [35,47]), + ('src/easydiffraction/utils/logging.py', [685]), + ('src/easydiffraction/utils/utils.py', [308]), +] +for fname, lines in files_lines: + with open(fname) as f: + content = f.readlines() + for ln in lines: + lo = max(0, ln-2) + hi = min(len(content), ln+2) + for i, l in enumerate(content[lo:hi]): + print(f'{fname}:{lo+i+1}: {l}', end='') + print('---') + diff --git a/tmp/show_w505.py b/tmp/show_w505.py new file mode 100644 index 00000000..6e606803 --- /dev/null +++ b/tmp/show_w505.py @@ -0,0 +1,27 @@ +files_lines = [ + ('src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py', [196,277,324,527,602]), + ('src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py', [29,298,422]), + ('src/easydiffraction/datablocks/experiment/categories/data/total_pd.py', [207]), + ('src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py', [30]), + ('src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py', [20]), + ('src/easydiffraction/datablocks/experiment/item/base.py', [71,604]), + ('src/easydiffraction/datablocks/experiment/item/factory.py', [78]), + ('src/easydiffraction/datablocks/structure/item/base.py', [248]), + ('src/easydiffraction/display/base.py', [144]), + ('src/easydiffraction/display/utils.py', [21]), + ('src/easydiffraction/io/cif/serialize.py', [162]), + ('src/easydiffraction/project/project.py', [78]), + ('src/easydiffraction/summary/summary.py', [208]), + ('src/easydiffraction/utils/logging.py', [327,478]), + ('src/easydiffraction/utils/utils.py', [52,73]), +] +for fname, lines in files_lines: + with open(fname) as f: + content = f.readlines() + for ln in lines: + lo = max(0, ln-2) + hi = min(len(content), ln+1) + for i, l in enumerate(content[lo:hi]): + print(f'{fname}:{lo+i+1}: {l}', end='') + print('---') + diff --git a/tools/add_assets_to_docs.sh b/tools/add_assets_to_docs.sh deleted file mode 100755 index e13eb40a..00000000 --- a/tools/add_assets_to_docs.sh +++ /dev/null @@ -1,16 +0,0 @@ -echo "📥 Add files from ../assets-docs" -cp -R ../assets-docs/docs/assets/ docs/assets/ -cp -R ../assets-docs/includes/ includes/ -cp -R ../assets-docs/overrides/ overrides/ - -echo "📥 Add files from ../assets-branding" -mkdir -p docs/assets/images/ -cp ../assets-branding/easydiffraction/hero/dark.png docs/assets/images/hero_dark.png -cp ../assets-branding/easydiffraction/hero/light.png docs/assets/images/hero_light.png -cp ../assets-branding/easydiffraction/logos/dark.svg docs/assets/images/logo_dark.svg -cp ../assets-branding/easydiffraction/logos/light.svg docs/assets/images/logo_light.svg -cp ../assets-branding/easydiffraction/icons/color.png docs/assets/images/favicon.png - -mkdir -p overrides/.icons/ -cp ../assets-branding/easydiffraction/icons/bw.svg overrides/.icons/easydiffraction.svg -cp ../assets-branding/easyscience-org/icons/eso-icon_bw.svg overrides/.icons/easyscience.svg diff --git a/tools/cleanup_docs.sh b/tools/cleanup_docs.sh deleted file mode 100755 index 757ec3e4..00000000 --- a/tools/cleanup_docs.sh +++ /dev/null @@ -1,10 +0,0 @@ -echo "🧹 Clean up after building documentation" -rm -rf site/ -rm -rf docs/assets/javascripts -rm -rf docs/assets/stylesheets -rm -rf docs/assets/images/*.png -rm -rf docs/assets/images/*.svg -rm -rf includes/ -rm -rf overrides/ -rm -rf docs/tutorials/*.ipynb -rm mkdocs.yml diff --git a/tools/convert_google_docstrings_to_numpy.py b/tools/convert_google_docstrings_to_numpy.py new file mode 100644 index 00000000..5fcb14e2 --- /dev/null +++ b/tools/convert_google_docstrings_to_numpy.py @@ -0,0 +1,539 @@ +#!/usr/bin/env python3 +"""Convert Google-style Python docstrings to numpydoc style.""" + +from __future__ import annotations + +import ast +import inspect +import re +import sys +import textwrap +from pathlib import Path + +from docstring_parser import DocstringStyle +from docstring_parser import compose +from docstring_parser import parse +from format_docstring.docstring_rewriter import calc_abs_pos +from format_docstring.docstring_rewriter import calc_line_starts +from format_docstring.docstring_rewriter import find_docstring +from format_docstring.docstring_rewriter import rebuild_literal + +SECTION_NAMES = ( + 'Args', + 'Arguments', + 'Returns', + 'Raises', + 'Yields', + 'Attributes', + 'Examples', + 'Notes', +) +GOOGLE_SECTION_RE = re.compile( + r'(?m)^(?P[ \t]*)(?P
' + + '|'.join(SECTION_NAMES) + + r'):\s*(?P\S.*)?$' +) +NUMPY_SECTION_RE = re.compile(r'(?m)^[^\n]+\n-+\n') +SECTION_KINDS_WITH_ITEMS = {'Args', 'Arguments', 'Attributes'} +PRESERVE_BLOCK_SECTIONS = {'Examples', 'Notes'} +GENERIC_ITEM_SECTIONS = {'Raises', 'Returns', 'Yields'} +GENERIC_ITEM_RE = re.compile( + r'(?[A-Za-z_][A-Za-z0-9_\.\[\], \|\(\)]{0,80}?)\s*:' +) + + +def _iter_python_files(paths: list[Path]) -> list[Path]: + files: list[Path] = [] + for path in paths: + if path.is_file() and path.suffix == '.py': + files.append(path) + continue + + if not path.exists(): + continue + + for file_path in sorted(path.rglob('*.py')): + if '_vendored' in file_path.parts: + continue + if '.pixi' in file_path.parts: + continue + files.append(file_path) + + return files + + +def _collect_names(node: ast.AST) -> list[str]: + names: list[str] = [] + + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + args = list(node.args.posonlyargs) + list(node.args.args) + args += list(node.args.kwonlyargs) + names.extend(arg.arg for arg in args) + if node.args.vararg is not None: + names.append(node.args.vararg.arg) + if node.args.kwarg is not None: + names.append(node.args.kwarg.arg) + return [name for name in names if name not in {'self', 'cls'}] + + if isinstance(node, ast.ClassDef): + init_method = next( + ( + stmt + for stmt in node.body + if isinstance(stmt, ast.FunctionDef) and stmt.name == '__init__' + ), + None, + ) + if init_method is not None: + names.extend(_collect_names(init_method)) + + for stmt in node.body: + if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name): + names.append(stmt.target.id) + elif isinstance(stmt, ast.Assign): + for target in stmt.targets: + if isinstance(target, ast.Name): + names.append(target.id) + + return list(dict.fromkeys(names)) + + +def _strip_blank_edges(lines: list[str]) -> list[str]: + start = 0 + end = len(lines) + while start < end and not lines[start].strip(): + start += 1 + while end > start and not lines[end - 1].strip(): + end -= 1 + return lines[start:end] + + +def _join_wrapped_lines(lines: list[str]) -> str: + parts: list[str] = [] + for line in lines: + text = re.sub(r'\s+', ' ', line.strip()) + if not text: + continue + if parts and parts[-1].endswith('-') and not parts[-1].endswith(' -'): + parts[-1] = parts[-1][:-1] + text + else: + parts.append(text) + return ' '.join(parts) + + +def _collapse_whitespace(lines: list[str]) -> str: + return _join_wrapped_lines(lines) + + +def _repair_named_items(block_lines: list[str], names: list[str]) -> list[str] | None: + flat = _collapse_whitespace(block_lines) + if not flat or not names: + return None + + label_pattern = '|'.join(re.escape(name) for name in sorted(set(names), key=len, reverse=True)) + item_re = re.compile( + rf'(?\*{{0,2}}(?:{label_pattern})(?:\s*\([^)]*\))?)\s*:' + ) + matches = list(item_re.finditer(flat)) + if not matches or matches[0].start() != 0: + return None + + repaired: list[str] = [] + for index, match in enumerate(matches): + start = match.end() + end = matches[index + 1].start() if index + 1 < len(matches) else len(flat) + description = flat[start:end].strip() + repaired.append(f' {match.group("label")}: {description}' if description else f' {match.group("label")}:') + return repaired + + +def _repair_generic_items(block_lines: list[str]) -> list[str] | None: + flat = _collapse_whitespace(block_lines) + if not flat: + return None + + matches = list(GENERIC_ITEM_RE.finditer(flat)) + if not matches or matches[0].start() != 0: + return None + + repaired: list[str] = [] + for index, match in enumerate(matches): + start = match.end() + end = matches[index + 1].start() if index + 1 < len(matches) else len(flat) + description = flat[start:end].strip() + repaired.append(f' {match.group("label")}: {description}' if description else f' {match.group("label")}:') + return repaired + + +def _repair_section(section: str, block_lines: list[str], names: list[str]) -> list[str]: + stripped = _strip_blank_edges(block_lines) + if not stripped: + return [] + + if section in SECTION_KINDS_WITH_ITEMS: + flat = _collapse_whitespace(stripped).lower().rstrip('.') + if flat == 'none': + return [] + repaired = _repair_named_items(stripped, names) + if repaired is not None: + return repaired + + if section in GENERIC_ITEM_SECTIONS: + repaired = _repair_generic_items(stripped) + if repaired is not None: + return repaired + + if section in PRESERVE_BLOCK_SECTIONS: + return [f' {line}' if line else '' for line in stripped] + + flat = _collapse_whitespace(stripped) + return [f' {flat}'] if flat else [] + + +def _repair_inline_sections(docstring: str, names: list[str]) -> str: + cleaned = inspect.cleandoc(docstring.replace('\r\n', '\n')) + lines = cleaned.split('\n') + out: list[str] = [] + index = 0 + + while index < len(lines): + raw_line = lines[index] + heading = GOOGLE_SECTION_RE.match(raw_line) + if heading is None: + out.append(raw_line.rstrip()) + index += 1 + continue + + section = heading.group('section') + section_name = 'Args' if section == 'Arguments' else section + out.append(f'{section_name}:') + + block_lines: list[str] = [] + rest = heading.group('rest') + if rest: + block_lines.append(rest) + + index += 1 + while index < len(lines): + next_line = lines[index] + if GOOGLE_SECTION_RE.match(next_line): + break + if ( + section_name not in PRESERVE_BLOCK_SECTIONS + and not next_line.strip() + and index + 1 < len(lines) + and lines[index + 1].strip() + and GOOGLE_SECTION_RE.match(lines[index + 1]) is None + ): + break + block_lines.append(next_line.rstrip()) + index += 1 + + out.extend(_repair_section(section_name, block_lines, names)) + + return '\n'.join(out) + + +def _looks_google(docstring: str) -> bool: + return bool(GOOGLE_SECTION_RE.search(docstring)) + + +def _looks_numpydoc(docstring: str) -> bool: + return bool(NUMPY_SECTION_RE.search(docstring)) + + +def _meta_kinds(parsed) -> set[str]: + kinds: set[str] = set() + for meta in parsed.meta: + args = getattr(meta, 'args', None) or [] + if not args: + continue + kinds.add(str(args[0]).lower()) + return kinds + + +def _contains_unparsed_sections(parsed) -> bool: + for text in (parsed.short_description, parsed.long_description): + if text and GOOGLE_SECTION_RE.search(text): + return True + return False + + +def _has_section_heading(docstring: str, section: str) -> bool: + return re.search(rf'(?m)^[ \t]*{re.escape(section)}:\s*(?:\S.*)?$', docstring) is not None + + +def _is_safe_conversion(docstring: str, parsed) -> bool: + if '::' in docstring: + return False + + kinds = _meta_kinds(parsed) + if _contains_unparsed_sections(parsed): + return False + + expectations = { + 'Args': 'param', + 'Arguments': 'param', + 'Attributes': 'attribute', + 'Returns': 'returns', + 'Raises': 'raises', + 'Yields': 'yields', + 'Examples': 'examples', + } + for section, expected_kind in expectations.items(): + if _has_section_heading(docstring, section) and expected_kind not in kinds: + return False + + return True + + +def _is_section_header(lines: list[str], index: int) -> bool: + return index + 1 < len(lines) and bool(lines[index].strip()) and set(lines[index + 1].strip()) == {'-'} + + +def _wrap_paragraph(lines: list[str], width: int, indent: str = '') -> list[str]: + if not lines: + return [] + + text = _join_wrapped_lines(lines) + if not text: + return [''] if lines else [] + + return textwrap.wrap( + text, + width=width, + initial_indent=indent, + subsequent_indent=indent, + break_long_words=False, + break_on_hyphens=False, + ) + + +def _format_freeform_block(lines: list[str], width: int = 72, indent: str = '') -> list[str]: + stripped = _strip_blank_edges(lines) + if not stripped: + return [] + + formatted: list[str] = [] + paragraph: list[str] = [] + for line in stripped: + if not line.strip(): + if paragraph: + formatted.extend(_wrap_paragraph(paragraph, width=width, indent=indent)) + paragraph = [] + if formatted and formatted[-1] != '': + formatted.append('') + continue + + content = line.strip() + if content.startswith(('>>>', '...')): + if paragraph: + formatted.extend(_wrap_paragraph(paragraph, width=width, indent=indent)) + paragraph = [] + formatted.append(f'{indent}{content}') + continue + + paragraph.append(content) + + if paragraph: + formatted.extend(_wrap_paragraph(paragraph, width=width, indent=indent)) + + return formatted + + +def _format_named_section(block_lines: list[str]) -> list[str]: + lines = _strip_blank_edges(block_lines) + if not lines: + return [] + + formatted: list[str] = [] + index = 0 + while index < len(lines): + if not lines[index].strip(): + index += 1 + continue + + header = lines[index].strip() + formatted.append(header) + index += 1 + + description: list[str] = [] + while index < len(lines): + line = lines[index] + if not line.strip(): + index += 1 + if description: + break + continue + if not line.startswith(' ') and not line.startswith('\t'): + break + description.append(line.strip()) + index += 1 + + if description: + formatted.extend(_wrap_paragraph(description, width=68, indent=' ')) + elif formatted and formatted[-1] != '': + formatted.append('') + + if formatted and formatted[-1] == '': + formatted.pop() + return formatted + + +def _format_return_like_section(block_lines: list[str]) -> list[str]: + lines = _strip_blank_edges(block_lines) + if not lines: + return [] + + first = next((line for line in lines if line.strip()), '') + if first.startswith((' ', '\t')): + return _format_freeform_block(lines, width=68, indent=' ') + + return _format_named_section(lines) + + +def _format_numpydoc_output(docstring: str) -> str: + lines = docstring.strip('\n').splitlines() + formatted: list[str] = [] + index = 0 + + preamble: list[str] = [] + while index < len(lines) and not _is_section_header(lines, index): + preamble.append(lines[index]) + index += 1 + formatted.extend(_format_freeform_block(preamble)) + + while index < len(lines): + if not _is_section_header(lines, index): + index += 1 + continue + + if formatted and formatted[-1] != '': + formatted.append('') + heading = lines[index].strip() + underline = lines[index + 1].strip() + formatted.extend([heading, underline]) + index += 2 + + block: list[str] = [] + while index < len(lines) and not _is_section_header(lines, index): + block.append(lines[index]) + index += 1 + + if heading in {'Parameters', 'Attributes'}: + formatted.extend(_format_named_section(block)) + elif heading in {'Returns', 'Raises', 'Yields'}: + formatted.extend(_format_return_like_section(block)) + else: + formatted.extend(_format_freeform_block(block)) + + return '\n'.join(_strip_blank_edges(formatted)) + + +def _convert_docstring(docstring: str, names: list[str]) -> str | None: + cleaned = inspect.cleandoc(docstring) + if not _looks_google(cleaned): + return None + + repaired = _repair_inline_sections(cleaned, names) + + try: + parsed = parse(repaired, style=DocstringStyle.GOOGLE) + except Exception: + return None + + if not _is_safe_conversion(repaired, parsed): + return None + + converted = _format_numpydoc_output(compose(parsed, style=DocstringStyle.NUMPYDOC)) + return converted if converted != cleaned else None + + +def _reformat_numpydoc_docstring(docstring: str) -> str | None: + cleaned = inspect.cleandoc(docstring) + if not _looks_numpydoc(cleaned): + return None + + formatted = _format_numpydoc_output(cleaned) + return formatted if formatted != cleaned else None + + +def _format_multiline_docstring(content: str, indent: int) -> str: + indent_str = ' ' * indent + lines = content.strip('\n').splitlines() + body = '\n'.join(f'{indent_str}{line}' if line else '' for line in lines) + return f'\n{body}\n{indent_str}' + + +def _convert_file(path: Path) -> bool: + source_code = path.read_text() + tree = ast.parse(source_code, type_comments=True) + line_starts = calc_line_starts(source_code) + replacements: list[tuple[int, int, str]] = [] + + nodes: list[ast.AST] = [tree] + nodes.extend(ast.walk(tree)) + + for node in nodes: + if not isinstance(node, (ast.Module, ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): + continue + + docstring_obj = find_docstring(node) + if docstring_obj is None: + continue + + value = docstring_obj.value + end_lineno = getattr(value, 'end_lineno', None) + end_col_offset = getattr(value, 'end_col_offset', None) + if end_lineno is None or end_col_offset is None: + continue + + docstring = ast.get_docstring(node, clean=False) + if docstring is None: + continue + + converted = _convert_docstring(docstring, _collect_names(node)) + if converted is None: + converted = _reformat_numpydoc_docstring(docstring) + if converted is None: + continue + + start = calc_abs_pos(source_code, line_starts, value.lineno, value.col_offset) + end = calc_abs_pos(source_code, line_starts, end_lineno, end_col_offset) + original_literal = source_code[start:end] + leading_indent = getattr(value, 'col_offset', 0) + formatted = _format_multiline_docstring(converted, leading_indent) + new_literal = rebuild_literal(original_literal, formatted) + if new_literal is None or new_literal == original_literal: + continue + + replacements.append((start, end, new_literal)) + + if not replacements: + return False + + replacements.sort(reverse=True) + new_source = source_code + for start, end, replacement in replacements: + new_source = new_source[:start] + replacement + new_source[end:] + + compile(new_source, str(path), 'exec') + path.write_text(new_source) + return True + + +def main(argv: list[str]) -> int: + input_paths = [Path(arg) for arg in argv] if argv else [Path('src'), Path('tools')] + changed = 0 + + for path in _iter_python_files(input_paths): + if _convert_file(path): + changed += 1 + print(f'Converted {path}') + + print(f'Converted docstrings in {changed} file(s).') + return 0 + + +if __name__ == '__main__': + raise SystemExit(main(sys.argv[1:])) diff --git a/tools/create_mkdocs_yml.py b/tools/create_mkdocs_yml.py deleted file mode 100644 index fb2b8685..00000000 --- a/tools/create_mkdocs_yml.py +++ /dev/null @@ -1,158 +0,0 @@ -import os -import re -from pathlib import Path -from typing import Any -from typing import Dict -from typing import List - -import material.extensions.emoji # side-effect: register emoji tag -import pymdownx.superfences # side-effect: register superfence tag -import yaml - -# Side-effect imports above ensure tagged YAML constructors -# (e.g., !!python/name:...) can be resolved during load. - - -def _activate_yaml_tag_side_effects() -> None: # pragma: no cover - trivial - """Access imported modules' attributes so Ruff sees them as used. - - The primary purpose of importing these packages is to ensure the - tagged constructors are importable during YAML load. Accessing the - attributes makes the side-effect explicit without needing noqa. - """ - _ = material.extensions.emoji.twemoji # type: ignore[attr-defined] - _ = pymdownx.superfences.fence_code_format # type: ignore[attr-defined] - - -_activate_yaml_tag_side_effects() - - -def load_yaml_with_env_variables(file_path: str) -> Dict[str, Any]: - """Load YAML resolving env variables declared as !ENV ${VAR_NAME}. - - Args: - file_path (str): Path to the YAML file. - - Returns: - dict: Parsed YAML content with environment variables replaced. - """ - tag = '!ENV' - pattern = re.compile(r'.*?\${([A-Z0-9_]+)}.*?') - - def constructor_env_variables(loader, node): # type: ignore[all] - """YAML constructor replacing !ENV markers with values.""" - value = loader.construct_scalar(node) # type: ignore[attr-defined] - for var in pattern.findall(value): - value = value.replace(f'${{{var}}}', os.environ.get(var, var)) - return value - - loader = yaml.FullLoader - loader.add_implicit_resolver(tag, pattern, None) - loader.add_constructor(tag, constructor_env_variables) - - with Path(file_path).open('r', encoding='utf-8') as fh: - return yaml.full_load(fh) - - -def merge_yaml(base_config: Dict[str, Any], override_config: Dict[str, Any]) -> Dict[str, Any]: - """Deep merge two YAML dicts; override has priority.""" - if not isinstance(base_config, dict): - return override_config - - merged_config = base_config.copy() - - for key, override_value in override_config.items(): - if key in merged_config: - base_value = merged_config[key] - if isinstance(base_value, dict) and isinstance(override_value, dict): - merged_config[key] = merge_yaml(base_value, override_value) - elif isinstance(base_value, list) and isinstance(override_value, list): - merged_config[key] = merge_lists(base_value, override_value) - else: - merged_config[key] = override_value - else: - merged_config[key] = override_value - - return merged_config - - -def merge_lists(base_list: List[Any], override_list: List[Any]) -> List[Any]: - """Merge two lists with handling of single-key dict items. - - Single-key dicts sharing a key are deep-merged; other items are - appended if not already present. - - Args: - base_list (list): The base list. - override_list (list): The overriding list. - - Returns: - list: The merged list. - """ - merged_list = [] - seen_items = {} - - for item in base_list + override_list: - if isinstance(item, dict) and len(item) == 1: - key = next(iter(item)) # Extract dictionary key (e.g., "mkdocs-jupyter") - if key in seen_items: - seen_items[key] = merge_yaml(seen_items[key], item[key]) # Merge dictionaries - else: - seen_items[key] = item[key] - elif item not in merged_list: - merged_list.append(item) - - # Convert merged dictionary values back into list format - for key, value in seen_items.items(): - merged_list.append({key: value}) - - return merged_list - - -def save_yaml(data: Dict[str, Any], output_file: str) -> None: - """Write YAML preserving !!python/name tags and Unicode.""" - - class CustomDumper(yaml.Dumper): - """Custom dumper avoiding unnecessary anchors & quotes.""" - - def ignore_aliases(self, _): # noqa: D401 - simple override - return True - - out_path = Path(output_file) - with out_path.open('w', encoding='utf-8') as f: - f.write('# WARNING: This file is auto-generated during the build process.\n') - f.write('# DO NOT EDIT THIS FILE MANUALLY.\n') - f.write('# It is created by merging:\n') - f.write('# - Generic YAML file: ../assets-docs/mkdocs.yml\n') - f.write('# - Project specific YAML file: docs/mkdocs.yml\n\n') - - with out_path.open('a', encoding='utf-8') as f: - yaml.dump( - data, - f, - Dumper=CustomDumper, # Use custom dumper - allow_unicode=True, # Ensure Unicode characters like © are preserved - default_flow_style=False, # - sort_keys=False, # Preserve the order of keys - ) - - -def main() -> None: - """Main function to read, merge, and save YAML configurations.""" - generic_config_path = '../assets-docs/mkdocs.yml' - specific_config_path = 'docs/mkdocs.yml' - output_path = 'mkdocs.yml' - - print(f'Reading generic config: {generic_config_path}') - base_config = load_yaml_with_env_variables(generic_config_path) - - print(f'Reading project specific config: {specific_config_path}') - override_config = load_yaml_with_env_variables(specific_config_path) - - print(f'Saving merged config: {output_path}') - merged_config = merge_yaml(base_config, override_config) - save_yaml(merged_config, output_path) - - -if __name__ == '__main__': - main() diff --git a/tools/license_headers.py b/tools/license_headers.py new file mode 100644 index 00000000..47d23524 --- /dev/null +++ b/tools/license_headers.py @@ -0,0 +1,315 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Add, remove, or check SPDX headers in Python files.""" + +from __future__ import annotations + +import argparse +import fnmatch +import tomllib +from datetime import datetime +from pathlib import Path +from typing import Any +from typing import Optional +from typing import Union + +from git import Repo +from spdx_headers.core import find_repository_root +from spdx_headers.core import get_copyright_info +from spdx_headers.core import has_spdx_header +from spdx_headers.data import load_license_data +from spdx_headers.operations import add_header_to_single_file +from spdx_headers.operations import remove_header_from_single_file + +LICENSE_DATABASE = load_license_data() + + +def load_pyproject(repo_path: Union[str, Path]) -> dict[str, Any]: + """Load and return parsed ``pyproject.toml`` data for the repository.""" + repo_root = find_repository_root(repo_path) + pyproject_path = repo_root / 'pyproject.toml' + + with pyproject_path.open('rb') as file_handle: + return tomllib.load(file_handle) + + +def get_pyproject_value(pyproject_data: dict[str, Any], dotted_key: str) -> Any: + """Return a nested ``pyproject.toml`` value from a dotted key.""" + value: Any = pyproject_data + for part in dotted_key.split('.'): + if not isinstance(value, dict) or part not in value: + raise KeyError(dotted_key) + value = value[part] + return value + + +def normalize_pattern(pattern: str) -> str: + """Normalize an exclude pattern to a POSIX-style relative path.""" + normalized = Path(pattern).as_posix() + if normalized.startswith('./'): + normalized = normalized[2:] + return normalized.rstrip('/') + + +def get_exclude_patterns( + repo_path: Union[str, Path], + exclude_values: list[str], + exclude_from_pyproject_toml: Optional[str], +) -> list[str]: + """Return normalized exclude patterns from CLI and ``pyproject.toml``.""" + pyproject_data = load_pyproject(repo_path) + patterns: list[str] = [] + + if exclude_from_pyproject_toml: + value = get_pyproject_value(pyproject_data, exclude_from_pyproject_toml) + if not isinstance(value, list) or not all(isinstance(item, str) for item in value): + raise ValueError( + f'{exclude_from_pyproject_toml} in pyproject.toml must be a list of strings.', + ) + patterns.extend(value) + + for item in exclude_values: + try: + value = get_pyproject_value(pyproject_data, item) + except KeyError: + patterns.append(item) + continue + + if not isinstance(value, list) or not all(isinstance(entry, str) for entry in value): + raise ValueError(f'{item} in pyproject.toml must be a list of strings.') + patterns.extend(value) + + normalized_patterns: list[str] = [] + seen: set[str] = set() + for pattern in patterns: + normalized = normalize_pattern(pattern) + if normalized and normalized not in seen: + normalized_patterns.append(normalized) + seen.add(normalized) + + return normalized_patterns + + +def get_file_creation_year(file_path: Union[str, Path]) -> str: + """Return the year the file was first added to Git history. + + If the year cannot be determined, fall back to the current year. + """ + file_path = Path(file_path) + + repo = Repo(file_path, search_parent_directories=True) + root = Path(repo.working_tree_dir).resolve() + rel_path = file_path.resolve().relative_to(root) + + rel_path_git = rel_path.as_posix() + + log_output = repo.git.log( + '--follow', + '--diff-filter=A', + '--reverse', + '--format=%ad', + '--date=format:%Y', + '--', + rel_path_git, + ).strip() + + year = log_output.splitlines()[0].strip() if log_output else '' + + return year or str(datetime.now().year) + + +def get_org_url(repo_path: Union[str, Path]) -> str: + """Return the organization URL derived from the repository source URL.""" + pyproject_data = load_pyproject(repo_path) + repo_url = pyproject_data['project']['urls']['Source Code'] + return repo_url.rsplit('/', 1)[0] + + +def get_project_license(repo_path: Union[str, Path]) -> str: + """Return the project license value from ``pyproject.toml``.""" + pyproject_data = load_pyproject(repo_path) + return pyproject_data['project']['license'] + + +def get_copyright_holder(repo_path: Union[str, Path]) -> str: + """Return the repository copyright holder name.""" + _, name, _ = get_copyright_info(repo_path) + return name + + +def add_spdx_header( + target_file: Union[str, Path], + *, + license_key: str, + copyright_holder: str, + org_url: str, +) -> None: + """Add SPDX headers to one file.""" + year = get_file_creation_year(target_file) + + add_header_to_single_file( + filepath=target_file, + license_key=license_key, + license_data=LICENSE_DATABASE, + year=year, + name=copyright_holder, + email=org_url, + ) + + +def is_excluded(relative_path: str, exclude_patterns: list[str]) -> bool: + """Return whether a relative path should be excluded.""" + for pattern in exclude_patterns: + if fnmatch.fnmatch(relative_path, pattern): + return True + if relative_path == pattern: + return True + if relative_path.startswith(f'{pattern}/'): + return True + return False + + +def iter_python_files( + paths: list[str], + *, + repo_root: Path, + exclude_patterns: list[str], + parser: argparse.ArgumentParser, +) -> list[Path]: + """Collect Python files under the given paths after exclusions.""" + files: list[Path] = [] + seen: set[Path] = set() + + for base_dir in paths: + base_path = Path(base_dir) + if not base_path.exists(): + parser.error(f'Path does not exist: {base_dir}') + + if base_path.is_file(): + candidates = [base_path] if base_path.suffix == '.py' else [] + else: + candidates = sorted(base_path.rglob('*.py')) + + for py_file in candidates: + resolved = py_file.resolve() + try: + relative_path = resolved.relative_to(repo_root).as_posix() + except ValueError: + relative_path = py_file.as_posix() + + if is_excluded(relative_path, exclude_patterns): + continue + + if resolved not in seen: + files.append(py_file) + seen.add(resolved) + + return files + + +def run_add( + files: list[Path], + *, + license_key: str, + copyright_holder: str, + org_url: str, +) -> int: + """Add SPDX headers to all selected files.""" + for py_file in files: + add_spdx_header( + py_file, + license_key=license_key, + copyright_holder=copyright_holder, + org_url=org_url, + ) + return 0 + + +def run_remove(files: list[Path]) -> int: + """Remove SPDX headers from all selected files.""" + for py_file in files: + remove_header_from_single_file(py_file) + return 0 + + +def run_check(files: list[Path]) -> int: + """Check SPDX headers in all selected files.""" + missing_files = [py_file for py_file in files if not has_spdx_header(py_file)] + + if not missing_files: + print('✓ All Python files have valid SPDX headers.') + return 0 + + print('✗ The following files are missing SPDX headers:') + for py_file in missing_files: + print(f' - {py_file.as_posix()}') + print(f'\nFound {len(missing_files)} files without SPDX headers.') + return 1 + + +def build_parser() -> argparse.ArgumentParser: + """Build the CLI argument parser.""" + parser = argparse.ArgumentParser( + description='Add, remove, or check SPDX headers in Python files.', + ) + subparsers = parser.add_subparsers(dest='command', required=True) + + for command_name in ('check', 'remove', 'add'): + command_parser = subparsers.add_parser(command_name) + command_parser.add_argument( + 'paths', + nargs='+', + help='Relative paths to scan (e.g. src tests)', + ) + command_parser.add_argument( + '--exclude', + nargs='*', + default=[], + help='Exclude paths, glob patterns, or pyproject dotted keys.', + ) + command_parser.add_argument( + '--exclude-from-pyproject-toml', + help='Read exclude patterns from a dotted key in pyproject.toml.', + ) + + return parser + + +def main(argv: Optional[list[str]] = None) -> int: + """Run the SPDX header CLI.""" + parser = build_parser() + args = parser.parse_args(argv) + + repo_path = Path('.').resolve() + repo_root = find_repository_root(repo_path).resolve() + exclude_patterns = get_exclude_patterns( + repo_path, + args.exclude, + args.exclude_from_pyproject_toml, + ) + files = iter_python_files( + args.paths, + repo_root=repo_root, + exclude_patterns=exclude_patterns, + parser=parser, + ) + + if args.command == 'check': + return run_check(files) + + if args.command == 'remove': + return run_remove(files) + + license_key = get_project_license(repo_path) + copyright_holder = get_copyright_holder(repo_path) + org_url = get_org_url(repo_path) + return run_add( + files, + license_key=license_key, + copyright_holder=copyright_holder, + org_url=org_url, + ) + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/tools/param_consistency.py b/tools/param_consistency.py new file mode 100644 index 00000000..cd63989c --- /dev/null +++ b/tools/param_consistency.py @@ -0,0 +1,677 @@ +"""Check and fix consistency between Parameter/Descriptor +definitions and their public property docstrings and type +annotations. + +Usage:: + + python param_consistency.py --check + python param_consistency.py --fix + python param_consistency.py src/mypackage/ --check + +Template (see architecture.md §9.8 for the full spec) +------------------------------------------------------ +Given ``description='Length of the a axis of the unit +cell.'``, ``units='Å'``, and type ``Parameter``: + +Writable:: + + @property + def length_a(self) -> Parameter: + \"""Length of the a axis of the unit cell (Å). + + Reading this property returns the underlying + ``Parameter`` object. Assigning to it updates + the parameter value. + \""" + return self._length_a + + @length_a.setter + def length_a(self, value: float) -> None: + self._length_a.value = value + +Read-only:: + + @property + def length_a(self) -> Parameter: + \"""Length of the a axis of the unit cell (Å). + + Reading this property returns the underlying + ``Parameter`` object. + \""" + return self._length_a + +Rules: + +- ``{desc}`` = description without trailing period + (single source of truth). +- ``{units}`` = units string; omit ``({units})`` when + absent or empty. +- Getter summary: ``{desc} ({units}).`` or ``{desc}.`` +- Getter body mentions the descriptor class and, for + writable properties, notes that assignment updates + the value. +- Setter has **no** docstring. +- Getter return annotation: the descriptor class name. +- Setter value annotation: ``float`` for numeric, + ``str`` for string. +- Setter return annotation: ``None``. + +Exit code 0 when all checks pass (or fix succeeds), +1 otherwise. +""" + +from __future__ import annotations + +import argparse +import ast +import sys +from dataclasses import dataclass +from dataclasses import field +from pathlib import Path + +# --------------------------------------------------------- +# Constants +# --------------------------------------------------------- + +_SRC_ROOT = ( + Path(__file__).resolve().parents[1] + / 'src' + / 'easydiffraction' +) + +_DESCRIPTOR_TYPES = frozenset( + {'Parameter', 'NumericDescriptor', 'StringDescriptor'} +) + +# Canonical setter value annotation per descriptor family. +_SETTER_ANN: dict[str, str] = { + 'Parameter': 'float', + 'NumericDescriptor': 'float', + 'StringDescriptor': 'str', +} + + +# --------------------------------------------------------- +# Data structures +# --------------------------------------------------------- + + +@dataclass +class DescriptorInfo: + """Descriptor definition from ``__init__``.""" + + attr_name: str # e.g. '_length_a' + prop_name: str # e.g. 'length_a' + type_name: str # 'Parameter' | … + description: str # e.g. 'Length of …' + units: str | None # e.g. 'Å', or None + + +@dataclass +class PropertyInfo: + """Property getter / setter AST nodes.""" + + name: str + getter: ast.FunctionDef + setter: ast.FunctionDef | None = None + + +@dataclass +class Edit: + """A source-level edit. + + Replace ``lines[start:end]`` with *new_text*. + When ``start == end`` the edit is an insertion + before that line. + """ + + start: int # 0-based inclusive + end: int # 0-based exclusive + new_text: str + + +@dataclass +class FileResult: + """Analysis result for one source file.""" + + path: Path + issues: list[str] = field(default_factory=list) + edits: list[Edit] = field(default_factory=list) + + +# --------------------------------------------------------- +# Template helpers +# --------------------------------------------------------- + + +def _strip_dot(s: str) -> str: + """Remove trailing period and whitespace.""" + s = s.rstrip() + if s.endswith('.'): + s = s[:-1].rstrip() + return s + + +def _getter_docstring( + desc: str, + units: str | None, + type_name: str, + has_setter: bool, + indent: str, +) -> str: + """Build the expected getter docstring.""" + d = _strip_dot(desc) + summary = f'{d} ({units}).' if units else f'{d}.' + + if has_setter: + body = ( + f'Reading this property returns the underlying ' + f'``{type_name}`` object. ' + f'Assigning to it updates the parameter value.' + ) + else: + body = ( + f'Reading this property returns the underlying ' + f'``{type_name}`` object.' + ) + + return ( + f'{indent}"""{summary}\n' + f'\n' + f'{indent}{body}\n' + f'{indent}"""\n' + ) + + +def _normalize(text: str) -> str: + """Collapse whitespace for comparison.""" + return ' '.join(text.split()).lower() + + +# --------------------------------------------------------- +# AST helpers +# --------------------------------------------------------- + + +def _call_name(node: ast.Call) -> str | None: + """Return the simple name of a Call's func.""" + if isinstance(node.func, ast.Name): + return node.func.id + if isinstance(node.func, ast.Attribute): + return node.func.attr + return None + + +def _kwarg_str( + call: ast.Call, + name: str, +) -> str | None: + """Extract a string keyword argument.""" + for kw in call.keywords: + if ( + kw.arg == name + and isinstance(kw.value, ast.Constant) + and isinstance(kw.value.value, str) + ): + return kw.value.value + return None + + +def _ann_str(ann: ast.expr | None) -> str | None: + """Return annotation as a source string.""" + if ann is None: + return None + if isinstance(ann, ast.Name): + return ann.id + if isinstance(ann, ast.Constant) and isinstance( + ann.value, str + ): + return ann.value # forward reference + return ast.unparse(ann) + + +def _body_indent( + func: ast.FunctionDef, + lines: list[str], +) -> str: + """Compute the indentation for the body.""" + def_line = lines[func.lineno - 1] + return ' ' * ( + len(def_line) - len(def_line.lstrip()) + 4 + ) + + +def _def_line_range( + func: ast.FunctionDef, + lines: list[str], +) -> tuple[int, int]: + """Return 0-based [start, end) of the def.""" + start = func.lineno - 1 + for i in range(start, min(start + 10, len(lines))): + if lines[i].rstrip().endswith(':'): + return start, i + 1 + if func.body and i + 1 >= func.body[0].lineno: + break + return start, start + 1 + + +def _docstring_range( + func: ast.FunctionDef, +) -> tuple[str | None, int, int]: + """Return (text, start_0, end_exclusive_0).""" + if not func.body: + return None, -1, -1 + first = func.body[0] + if ( + isinstance(first, ast.Expr) + and isinstance(first.value, ast.Constant) + and isinstance(first.value.value, str) + ): + # end_lineno is 1-based inclusive + return ( + first.value.value, + first.lineno - 1, + first.end_lineno, + ) + return None, -1, -1 + + +# --------------------------------------------------------- +# Extraction +# --------------------------------------------------------- + + +def _extract_descriptors( + cls: ast.ClassDef, +) -> dict[str, DescriptorInfo]: + """Find self._xxx = DescriptorType(...) in init.""" + result: dict[str, DescriptorInfo] = {} + + init = next( + ( + n + for n in cls.body + if isinstance(n, ast.FunctionDef) + and n.name == '__init__' + ), + None, + ) + if init is None: + return result + + for stmt in ast.walk(init): + if ( + isinstance(stmt, ast.Assign) + and len(stmt.targets) == 1 + ): + target = stmt.targets[0] + value = stmt.value + elif ( + isinstance(stmt, ast.AnnAssign) + and stmt.value is not None + ): + target = stmt.target + value = stmt.value + else: + continue + + # Target must be self._xxx + if not ( + isinstance(target, ast.Attribute) + and isinstance(target.value, ast.Name) + and target.value.id == 'self' + and target.attr.startswith('_') + ): + continue + + if not isinstance(value, ast.Call): + continue + + name = _call_name(value) + if name not in _DESCRIPTOR_TYPES: + continue + + desc_str = _kwarg_str(value, 'description') + if not desc_str or not _strip_dot(desc_str): + continue + + units = _kwarg_str(value, 'units') or None + prop = target.attr.lstrip('_') + result[prop] = DescriptorInfo( + target.attr, prop, name, desc_str, units + ) + + return result + + +def _extract_properties( + cls: ast.ClassDef, +) -> dict[str, PropertyInfo]: + """Find property getters and setters.""" + result: dict[str, PropertyInfo] = {} + + for item in cls.body: + if not isinstance(item, ast.FunctionDef): + continue + for dec in item.decorator_list: + # @property + if ( + isinstance(dec, ast.Name) + and dec.id == 'property' + ): + result[item.name] = PropertyInfo( + item.name, item + ) + break + # @xxx.setter + if ( + isinstance(dec, ast.Attribute) + and dec.attr == 'setter' + and isinstance(dec.value, ast.Name) + and dec.value.id in result + ): + result[dec.value.id].setter = item + break + + return result + + +# --------------------------------------------------------- +# Analysis (shared by --check and --fix) +# --------------------------------------------------------- + + +def _analyze_file(path: Path) -> FileResult: + """Analyze one file, return issues and edits.""" + result = FileResult(path) + try: + source = path.read_text(encoding='utf-8') + except Exception: # noqa: BLE001 + return result + + lines = source.splitlines(keepends=True) + + try: + tree = ast.parse(source, filename=str(path)) + except SyntaxError: + return result + + for node in tree.body: + if not isinstance(node, ast.ClassDef): + continue + + descriptors = _extract_descriptors(node) + properties = _extract_properties(node) + + for prop_name, prop in properties.items(): + if prop_name not in descriptors: + continue + desc = descriptors[prop_name] + _analyze_property( + node.name, prop, desc, lines, result + ) + + return result + + +def _analyze_property( + cls_name: str, + prop: PropertyInfo, + desc: DescriptorInfo, + lines: list[str], + result: FileResult, +) -> None: + """Check one property against the template.""" + loc = f'{cls_name}.{prop.name}' + indent = _body_indent(prop.getter, lines) + has_setter = prop.setter is not None + + # --- Getter return annotation --- + actual_ret = _ann_str(prop.getter.returns) + expected_ret = desc.type_name + if actual_ret != expected_ret: + result.issues.append( + f'{loc}: getter annotation ' + f'-> {actual_ret} (expected {expected_ret})' + ) + ds, de = _def_line_range(prop.getter, lines) + def_indent = lines[ds][ + : len(lines[ds]) - len(lines[ds].lstrip()) + ] + new_def = ( + f'{def_indent}def {prop.name}' + f'(self) -> {expected_ret}:\n' + ) + result.edits.append(Edit(ds, de, new_def)) + + # --- Getter docstring --- + expected_doc = _getter_docstring( + desc.description, + desc.units, + desc.type_name, + has_setter, + indent, + ) + actual_doc_text, doc_s, doc_e = _docstring_range( + prop.getter + ) + + if actual_doc_text is None: + result.issues.append( + f'{loc}: getter missing docstring' + ) + _, def_end = _def_line_range(prop.getter, lines) + result.edits.append( + Edit(def_end, def_end, expected_doc) + ) + else: + actual_src = ''.join(lines[doc_s:doc_e]) + if _normalize(actual_src) != _normalize( + expected_doc + ): + result.issues.append( + f'{loc}: getter docstring ' + 'does not match template' + ) + result.edits.append( + Edit(doc_s, doc_e, expected_doc) + ) + + # --- Setter --- + if prop.setter is None: + return + + # Setter def-line annotations + setter_args = prop.setter.args.args + setter_param = ( + setter_args[1].arg + if len(setter_args) >= 2 + else 'value' + ) + expected_ann = _SETTER_ANN[desc.type_name] + + actual_val_ann = None + if ( + len(setter_args) >= 2 + and setter_args[1].annotation + ): + actual_val_ann = _ann_str( + setter_args[1].annotation + ) + + actual_ret_ann = _ann_str(prop.setter.returns) + + if ( + actual_val_ann != expected_ann + or actual_ret_ann != 'None' + ): + parts: list[str] = [] + if actual_val_ann != expected_ann: + parts.append( + f'value: {actual_val_ann} ' + f'(expected {expected_ann})' + ) + if actual_ret_ann != 'None': + parts.append( + f'return: {actual_ret_ann} ' + '(expected None)' + ) + result.issues.append( + f'{loc}: setter annotation ' + f'— {", ".join(parts)}' + ) + + ds, de = _def_line_range(prop.setter, lines) + def_indent = lines[ds][ + : len(lines[ds]) - len(lines[ds].lstrip()) + ] + new_def = ( + f'{def_indent}def {prop.name}' + f'(self, {setter_param}: ' + f'{expected_ann}) -> None:\n' + ) + result.edits.append(Edit(ds, de, new_def)) + + # Setter docstring — should not exist + setter_doc_text, sd_s, sd_e = _docstring_range( + prop.setter + ) + if setter_doc_text is not None: + result.issues.append( + f'{loc}: setter has docstring ' + '(should have none)' + ) + result.edits.append(Edit(sd_s, sd_e, '')) + + +# --------------------------------------------------------- +# Apply edits +# --------------------------------------------------------- + + +def _apply_edits( + lines: list[str], + edits: list[Edit], +) -> list[str]: + """Apply edits bottom-up to preserve line numbers.""" + sorted_edits = sorted( + edits, key=lambda e: e.start, reverse=True + ) + result = list(lines) + for edit in sorted_edits: + new_lines = edit.new_text.splitlines(keepends=True) + result[edit.start : edit.end] = new_lines + return result + + +# --------------------------------------------------------- +# Entry point +# --------------------------------------------------------- + + +def _collect_py_files(paths: list[str]) -> list[Path]: + """Resolve paths to a sorted list of .py files. + + Each entry can be a directory (recursively globbed) + or a single .py file. When *paths* is empty, + defaults to ``_SRC_ROOT``. + """ + if not paths: + return sorted(_SRC_ROOT.rglob('*.py')) + + result: list[Path] = [] + for raw in paths: + p = Path(raw).resolve() + if p.is_dir(): + result.extend(p.rglob('*.py')) + elif p.is_file() and p.suffix == '.py': + result.append(p) + return sorted(set(result)) + + +def main() -> int: + """Run param-consistency check or fix.""" + parser = argparse.ArgumentParser( + description=( + 'Parameter / property consistency: ' + 'docstrings and type hints.' + ), + ) + parser.add_argument( + 'paths', + nargs='*', + help=( + 'Directories or .py files to scan ' + '(default: src/easydiffraction/)' + ), + ) + group = parser.add_mutually_exclusive_group() + group.add_argument( + '--check', + action='store_true', + help='Validate consistency (default)', + ) + group.add_argument( + '--fix', + action='store_true', + help='Auto-fix docstrings and type hints', + ) + args = parser.parse_args() + + py_files = _collect_py_files(args.paths) + repo_root = Path(__file__).resolve().parents[1] + total_issues = 0 + total_fixed = 0 + files_touched = 0 + + for path in py_files: + result = _analyze_file(path) + if not result.issues: + continue + + try: + rel = path.relative_to(repo_root) + except ValueError: + rel = path + + if args.fix: + source_lines = path.read_text( + encoding='utf-8' + ).splitlines(keepends=True) + fixed_lines = _apply_edits( + source_lines, result.edits + ) + path.write_text( + ''.join(fixed_lines), encoding='utf-8' + ) + count = len(result.issues) + total_fixed += count + files_touched += 1 + print( + f'📝 {rel}: fixed {count} issue(s)' + ) + else: + for issue in result.issues: + print(f' ❌ {rel}: {issue}') + total_issues += len(result.issues) + + # Summary + print() + if args.fix: + print( + f'✅ Fixed {total_fixed} issue(s) ' + f'in {files_touched} file(s).' + ) + return 0 + if total_issues: + print( + f'❌ {total_issues} consistency ' + 'issue(s) found.' + ) + return 1 + print('✅ All properties match the template.') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/test_scripts.py b/tools/test_scripts.py index 327bafd2..e24b28e2 100644 --- a/tools/test_scripts.py +++ b/tools/test_scripts.py @@ -1,3 +1,5 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause """Test runner for tutorial scripts in the 'tutorials' directory. This test discovers and executes all Python scripts located under the @@ -16,15 +18,13 @@ import pytest -# Mark this module as 'integration' so it's excluded by default -# (see pytest.ini) -pytestmark = pytest.mark.integration - _repo_root = Path(__file__).resolve().parents[1] _src_root = _repo_root / 'src' # Discover tutorial scripts, excluding temporary checkpoint files -TUTORIALS = [p for p in Path('tutorials').rglob('*.py') if '.ipynb_checkpoints' not in p.parts] +TUTORIALS = [ + p for p in Path('docs/docs/tutorials').rglob('*.py') if '.ipynb_checkpoints' not in p.parts +] @pytest.mark.parametrize('script_path', TUTORIALS) diff --git a/tools/update_docs_assets.py b/tools/update_docs_assets.py new file mode 100644 index 00000000..b274e038 --- /dev/null +++ b/tools/update_docs_assets.py @@ -0,0 +1,91 @@ +""" +Update documentation assets from the assets-branding repository. + +This script fetches branding assets (logos, icons, images) from the +easyscience/assets-branding GitHub repository and copies them to the +appropriate locations in the documentation directory. +""" + +import shutil +from pathlib import Path + +import pooch + +# Configuration: Define what to fetch and where to copy +GITHUB_REPO = 'easyscience/assets-branding' +GITHUB_BRANCH = 'master' +BASE_URL = f'https://raw.githubusercontent.com/{GITHUB_REPO}/refs/heads/{GITHUB_BRANCH}' +PROJECT_NAME = 'easydiffraction' + +# Mapping of source files to destination paths +# Format: "source_path_in_repo": "destination_path_in_project" +ASSETS_MAP = { + # Logos + f'{PROJECT_NAME}/logos/dark.svg': 'docs/docs/assets/images/logo_dark.svg', + f'{PROJECT_NAME}/logos/light.svg': 'docs/docs/assets/images/logo_light.svg', + # Favicon + f'{PROJECT_NAME}/icons/color.png': 'docs/docs/assets/images/favicon.png', + # Icon overrides + f'{PROJECT_NAME}/icons/bw.svg': f'docs/overrides/.icons/{PROJECT_NAME}.svg', + 'easyscience-org/icons/eso-icon_bw.svg': 'docs/overrides/.icons/easyscience.svg', +} + + +def fetch_and_copy_asset( + source_path: str, + dest_path: str, + cache_dir: Path, +) -> None: + """ + Fetch an asset from GitHub and copy it to the destination. + + Args: + source_path: Path to the file in the GitHub repository + dest_path: Destination path in the project + cache_dir: Directory to cache downloaded files + """ + url = f'{BASE_URL}/{source_path}' + + # Create a unique cache filename based on source path + cache_filename = source_path.replace('/', '_') + + # Download file using pooch + file_path = pooch.retrieve( + url=url, + known_hash=None, # Skip hash verification + path=cache_dir, + fname=cache_filename, + ) + + # Create destination directory if it doesn't exist + dest = Path(dest_path) + dest.parent.mkdir(parents=True, exist_ok=True) + + # Copy the file to destination + shutil.copy2(file_path, dest) + print(f'Copied {file_path} -> {dest_path}') + + +def main(): + """Main function to update all documentation assets.""" + print('📥 Updating documentation assets...') + print(f' Repository: {GITHUB_REPO}') + print(f' Branch: {GITHUB_BRANCH}\n') + + # Use a temporary cache directory + cache_dir = Path.home() / '.cache' / GITHUB_REPO + cache_dir.mkdir(parents=True, exist_ok=True) + + # Fetch and copy each asset + for source_path, dest_path in ASSETS_MAP.items(): + try: + fetch_and_copy_asset(source_path, dest_path, cache_dir) + print() + except Exception as e: + print(f'❌ Failed to fetch {source_path}: {e}') + + print('\n✅ Documentation assets updated successfully!') + + +if __name__ == '__main__': + main() diff --git a/tools/update_github_labels.py b/tools/update_github_labels.py new file mode 100644 index 00000000..84de575e --- /dev/null +++ b/tools/update_github_labels.py @@ -0,0 +1,341 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Set/update GitHub labels for current or specified easyscience +repository. + +Requires: + - gh CLI installed + - gh auth login completed + +Usage: + python update_github_labels.py + python update_github_labels.py --dry-run + python update_github_labels.py --repo easyscience/my-repo + python update_github_labels.py --repo easyscience/my-repo --dry-run +""" + +from __future__ import annotations + +import argparse +import json +import shlex +import subprocess # noqa: S404 +import sys +from dataclasses import dataclass + +EASYSCIENCE_ORG = 'easyscience' + + +# Data structures + + +@dataclass(frozen=True) +class Label: + """A GitHub label with name, color, and description.""" + + name: str + color: str + description: str = '' + + +@dataclass(frozen=True) +class LabelRename: + """Mapping from old label name to new label name.""" + + old: str + new: str + + +class Colors: + """Hex color codes for label groups.""" + + SCOPE = 'd73a4a' + MAINTAINER = '0e8a16' + PRIORITY = 'fbca04' + BOT = '5319e7' + + +LABEL_RENAMES = [ + # Default GitHub labels to rename (if they exist) + LabelRename('bug', '[scope] bug'), + LabelRename('documentation', '[scope] documentation'), + LabelRename('duplicate', '[maintainer] duplicate'), + LabelRename('enhancement', '[scope] enhancement'), + LabelRename('good first issue', '[maintainer] good first issue'), + LabelRename('help wanted', '[maintainer] help wanted'), + LabelRename('invalid', '[maintainer] invalid'), + LabelRename('question', '[maintainer] question'), + LabelRename('wontfix', '[maintainer] wontfix'), + # Custom label renames (if they exist) + LabelRename('[bot] pull request', '[bot] release'), +] + +LABELS = [ + # Scope labels + Label( + '[scope] bug', + Colors.SCOPE, + 'Bug report or fix (major.minor.PATCH)', + ), + Label( + '[scope] documentation', + Colors.SCOPE, + 'Documentation only changes (major.minor.patch.POST)', + ), + Label( + '[scope] enhancement', + Colors.SCOPE, + 'Adds/improves features (major.MINOR.patch)', + ), + Label( + '[scope] maintenance', + Colors.SCOPE, + 'Code/tooling cleanup, no feature or bugfix (major.minor.PATCH)', + ), + Label( + '[scope] significant', + Colors.SCOPE, + 'Breaking or major changes (MAJOR.minor.patch)', + ), + Label( + '[scope] ⚠️ label needed', + Colors.SCOPE, + 'Automatically added to issues and PRs without a [scope] label', + ), + # Maintainer labels + Label( + '[maintainer] duplicate', + Colors.MAINTAINER, + 'Already reported or submitted', + ), + Label( + '[maintainer] good first issue', + Colors.MAINTAINER, + 'Good entry-level issue for newcomers', + ), + Label( + '[maintainer] help wanted', + Colors.MAINTAINER, + 'Needs additional help to resolve or implement', + ), + Label( + '[maintainer] invalid', + Colors.MAINTAINER, + 'Invalid, incorrect or outdated', + ), + Label( + '[maintainer] question', + Colors.MAINTAINER, + 'Needs clarification, discussion, or more information', + ), + Label( + '[maintainer] wontfix', + Colors.MAINTAINER, + 'Will not be fixed or continued', + ), + # Priority labels + Label( + '[priority] lowest', + Colors.PRIORITY, + 'Very low urgency', + ), + Label( + '[priority] low', + Colors.PRIORITY, + 'Low importance', + ), + Label( + '[priority] medium', + Colors.PRIORITY, + 'Normal/default priority', + ), + Label( + '[priority] high', + Colors.PRIORITY, + 'Should be prioritized soon', + ), + Label( + '[priority] highest', + Colors.PRIORITY, + 'Urgent. Needs attention ASAP', + ), + Label( + '[priority] ⚠️ label needed', + Colors.PRIORITY, + 'Automatically added to issues without a [priority] label', + ), + # Bot label + Label( + '[bot] release', + Colors.BOT, + 'Automated release PR. Excluded from changelog/versioning', + ), + Label( + '[bot] backmerge', + Colors.BOT, + 'Automated backmerge master → develop failed due to conflicts', + ), +] + + +# Helpers + + +@dataclass(frozen=True) +class CmdResult: + """Result of a shell command execution.""" + + returncode: int + stdout: str + stderr: str + + +def run_cmd( + args: list[str], + *, + dry_run: bool, + check: bool = True, +) -> CmdResult: + """Run a command (or print it in dry-run mode).""" + cmd_str = ' '.join(shlex.quote(a) for a in args) + + if dry_run: + print(f' [dry-run] {cmd_str}') + return CmdResult(0, '', '') + + proc = subprocess.run( + args=args, + text=True, + capture_output=True, + ) + result = CmdResult( + proc.returncode, + proc.stdout.strip(), + proc.stderr.strip(), + ) + + if check and proc.returncode != 0: + raise RuntimeError(f'Command failed ({proc.returncode}): {cmd_str}\n{result.stderr}') + + return result + + +def get_current_repo() -> str: + """Get the current repository name in 'owner/repo' format.""" + result = subprocess.run( + args=[ + 'gh', + 'repo', + 'view', + '--json', + 'nameWithOwner', + ], + text=True, + capture_output=True, + check=True, + ) + data = json.loads(result.stdout) + name_with_owner = data.get('nameWithOwner', '') + + if '/' not in name_with_owner: + raise RuntimeError('Could not determine current repository name') + + return name_with_owner + + +def rename_label( + repo: str, + rename: LabelRename, + *, + dry_run: bool, +) -> None: + """Rename a label, silently skipping if it doesn't exist.""" + result = run_cmd( + args=[ + 'gh', + 'label', + 'edit', + rename.old, + '--name', + rename.new, + '--repo', + repo, + ], + dry_run=dry_run, + check=False, + ) + + if dry_run or result.returncode == 0: + print(f' Rename: {rename.old!r} → {rename.new!r}') + else: + print(f' Skip (not found): {rename.old!r}') + + +def upsert_label( + repo: str, + label: Label, + *, + dry_run: bool, +) -> None: + """Create or update a label.""" + run_cmd( + [ + 'gh', + 'label', + 'create', + label.name, + '--color', + label.color, + '--description', + label.description, + '--force', + '--repo', + repo, + ], + dry_run=dry_run, + ) + print(f' Upsert: {label.name!r}') + + +# Main + + +def main() -> int: + """Entry point: parse arguments and sync labels.""" + parser = argparse.ArgumentParser(description='Sync GitHub labels for easyscience repos') + parser.add_argument( + '--repo', + help='Target repository (owner/name)', + ) + parser.add_argument( + '--dry-run', + action='store_true', + help='Print actions without applying changes', + ) + args = parser.parse_args() + + repo = args.repo or get_current_repo() + org = repo.split('/')[0] + + if org.lower() != EASYSCIENCE_ORG: + print(f"Error: repository '{repo}' is not under '{EASYSCIENCE_ORG}'", file=sys.stderr) + return 2 + + print(f'Repository: {repo}') + if args.dry_run: + print('Mode: DRY-RUN (no changes will be made)\n') + + print('\nRenaming default labels...') + for rename in LABEL_RENAMES: + rename_label(repo, rename, dry_run=args.dry_run) + + print('\nUpserting labels...') + for label in LABELS: + upsert_label(repo, label, dry_run=args.dry_run) + + print('\nDone.') + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/tools/update_spdx.py b/tools/update_spdx.py deleted file mode 100644 index df9236af..00000000 --- a/tools/update_spdx.py +++ /dev/null @@ -1,109 +0,0 @@ -"""Update or insert SPDX headers in Python files. - -- Ensures SPDX-FileCopyrightText has the current year. -- Ensures SPDX-License-Identifier is set to BSD-3-Clause. -""" - -import datetime -import fnmatch -import re -from pathlib import Path - -CURRENT_YEAR = datetime.datetime.now().year -COPYRIGHT_TEXT = ( - f'# SPDX-FileCopyrightText: 2021-{CURRENT_YEAR} EasyDiffraction contributors ' - '' -) -LICENSE_TEXT = '# SPDX-License-Identifier: BSD-3-Clause' - -# Patterns to exclude from SPDX header updates (vendored code) -EXCLUDE_PATTERNS = [ - '*/_vendored/jupyter_dark_detect/*', -] - - -def should_exclude(file_path: Path) -> bool: - """Check if a file should be excluded from SPDX header updates.""" - path_str = str(file_path) - return any(fnmatch.fnmatch(path_str, pattern) for pattern in EXCLUDE_PATTERNS) - - -def update_spdx_header(file_path: Path): - # Use Path.open to satisfy lint rule PTH123. - with file_path.open('r', encoding='utf-8') as f: - original_lines = f.readlines() - - # Regexes for SPDX lines - copy_re = re.compile(r'^#\s*SPDX-FileCopyrightText:.*$') - lic_re = re.compile(r'^#\s*SPDX-License-Identifier:.*$') - - # 1) Preserve any leading shebang / coding cookie lines - prefix = [] - body_start = 0 - if original_lines: - # Shebang line like "#!/usr/bin/env python3" - if original_lines[0].startswith('#!'): - prefix.append(original_lines[0]) - body_start = 1 - # PEP 263 coding cookie on first or second line - # e.g. "# -*- coding: utf-8 -*-" or "# coding: utf-8" - for _ in range(2): # at most one more line to inspect - if body_start < len(original_lines): - line = original_lines[body_start] - if re.match(r'^#.*coding[:=]\s*[-\w.]+', line): - prefix.append(line) - body_start += 1 - else: - break - - # 2) Work on the remaining body - body = original_lines[body_start:] - - # Remove any existing SPDX lines anywhere in the body - body = [ln for ln in body if not (copy_re.match(ln) or lic_re.match(ln))] - - # Strip leading blank lines in the body so header is tight - while body and not body[0].strip(): - body.pop(0) - - # 3) Build canonical SPDX block: two lines + exactly one blank - spdx_block = [ - COPYRIGHT_TEXT + '\n', - LICENSE_TEXT + '\n', - '\n', - ] - - # 4) New content: prefix + SPDX + body - new_lines = prefix + spdx_block + body - - # 5) Normalize: collapse any extra blank lines immediately after - # LICENSE to exactly one. This keeps the script idempotent. - # Find the index of LICENSE we just inserted (prefix may be 0, 1, - # or 2 lines) - lic_idx = len(prefix) + 1 # spdx_block[1] is the license line - # Ensure exactly one blank line after LICENSE - # Remove all blank lines after lic_idx, then insert a single blank. - j = lic_idx + 1 - # Remove any number of blank lines following - while j < len(new_lines) and not new_lines[j].strip(): - new_lines.pop(j) - # Insert exactly one blank line at this position - new_lines.insert(j, '\n') - - with file_path.open('w', encoding='utf-8') as f: - f.writelines(new_lines) - - -def main(): - """Recursively update or insert SPDX headers in all Python files - under the 'src' and 'tests' directories. - """ - for base_dir in ('src', 'tests'): - for py_file in Path(base_dir).rglob('*.py'): - if should_exclude(py_file): - continue - update_spdx_header(py_file) - - -if __name__ == '__main__': - main() diff --git a/tutorials/data/ed-3.xye b/tutorials/data/ed-3.xye deleted file mode 100644 index 0b9b63e3..00000000 --- a/tutorials/data/ed-3.xye +++ /dev/null @@ -1,3099 +0,0 @@ -# 2theta intensity su - 10.00 167.00 12.60 - 10.05 157.00 12.50 - 10.10 187.00 13.30 - 10.15 197.00 14.00 - 10.20 164.00 12.50 - 10.25 171.00 13.00 - 10.30 190.00 13.40 - 10.35 182.00 13.50 - 10.40 166.00 12.60 - 10.45 203.00 14.30 - 10.50 156.00 12.20 - 10.55 190.00 13.90 - 10.60 175.00 13.00 - 10.65 161.00 12.90 - 10.70 187.00 13.50 - 10.75 166.00 13.10 - 10.80 171.00 13.00 - 10.85 177.00 13.60 - 10.90 159.00 12.60 - 10.95 184.00 13.90 - 11.00 160.00 12.60 - 11.05 182.00 13.90 - 11.10 167.00 13.00 - 11.15 169.00 13.40 - 11.20 186.00 13.70 - 11.25 167.00 13.30 - 11.30 169.00 13.10 - 11.35 159.00 13.10 - 11.40 170.00 13.20 - 11.45 179.00 13.90 - 11.50 178.00 13.50 - 11.55 188.00 14.20 - 11.60 176.00 13.50 - 11.65 196.00 14.60 - 11.70 182.00 13.70 - 11.75 183.00 14.00 - 11.80 195.00 14.10 - 11.85 144.00 12.40 - 11.90 178.00 13.50 - 11.95 175.00 13.70 - 12.00 200.00 14.20 - 12.05 157.00 12.90 - 12.10 195.00 14.00 - 12.15 164.00 13.10 - 12.20 188.00 13.70 - 12.25 168.00 13.10 - 12.30 191.00 13.70 - 12.35 178.00 13.40 - 12.40 182.00 13.30 - 12.45 174.00 13.30 - 12.50 171.00 12.90 - 12.55 174.00 13.20 - 12.60 184.00 13.30 - 12.65 164.00 12.80 - 12.70 166.00 12.50 - 12.75 177.00 13.20 - 12.80 174.00 12.80 - 12.85 187.00 13.50 - 12.90 183.00 13.10 - 12.95 187.00 13.50 - 13.00 175.00 12.80 - 13.05 165.00 12.70 - 13.10 177.00 12.80 - 13.15 182.00 13.30 - 13.20 195.00 13.50 - 13.25 163.00 12.60 - 13.30 180.00 12.90 - 13.35 171.00 12.90 - 13.40 182.00 13.00 - 13.45 179.00 13.10 - 13.50 161.00 12.20 - 13.55 156.00 12.30 - 13.60 197.00 13.50 - 13.65 167.00 12.70 - 13.70 180.00 12.80 - 13.75 182.00 13.20 - 13.80 176.00 12.70 - 13.85 153.00 12.10 - 13.90 179.00 12.80 - 13.95 156.00 12.30 - 14.00 187.00 13.10 - 14.05 170.00 12.80 - 14.10 185.00 13.00 - 14.15 180.00 13.20 - 14.20 167.00 12.40 - 14.25 159.00 12.40 - 14.30 152.00 11.80 - 14.35 173.00 13.00 - 14.40 169.00 12.50 - 14.45 185.00 13.40 - 14.50 168.00 12.40 - 14.55 193.00 13.70 - 14.60 177.00 12.80 - 14.65 161.00 12.50 - 14.70 180.00 12.90 - 14.75 165.00 12.60 - 14.80 178.00 12.80 - 14.85 157.00 12.30 - 14.90 163.00 12.30 - 14.95 143.00 11.70 - 15.00 155.00 11.90 - 15.05 168.00 12.80 - 15.10 160.00 12.10 - 15.15 155.00 12.20 - 15.20 203.00 13.70 - 15.25 164.00 12.60 - 15.30 158.00 12.10 - 15.35 152.00 12.10 - 15.40 173.00 12.60 - 15.45 160.00 12.50 - 15.50 172.00 12.60 - 15.55 164.00 12.60 - 15.60 163.00 12.30 - 15.65 173.00 13.00 - 15.70 177.00 12.80 - 15.75 184.00 13.40 - 15.80 173.00 12.70 - 15.85 182.00 13.30 - 15.90 156.00 12.10 - 15.95 152.00 12.20 - 16.00 201.00 13.70 - 16.05 156.00 12.30 - 16.10 169.00 12.50 - 16.15 178.00 13.20 - 16.20 150.00 11.80 - 16.25 163.00 12.60 - 16.30 165.00 12.40 - 16.35 160.00 12.50 - 16.40 171.00 12.60 - 16.45 168.00 12.80 - 16.50 159.00 12.20 - 16.55 166.00 12.80 - 16.60 156.00 12.10 - 16.65 156.00 12.40 - 16.70 154.00 12.10 - 16.75 173.00 13.10 - 16.80 173.00 12.80 - 16.85 161.00 12.70 - 16.90 177.00 13.00 - 16.95 159.00 12.70 - 17.00 162.00 12.50 - 17.05 166.00 13.00 - 17.10 167.00 12.70 - 17.15 166.00 13.10 - 17.20 168.00 12.80 - 17.25 188.00 14.00 - 17.30 165.00 12.80 - 17.35 171.00 13.40 - 17.40 171.00 13.10 - 17.45 162.00 13.10 - 17.50 161.00 12.80 - 17.55 177.00 13.80 - 17.60 176.00 13.40 - 17.65 175.00 13.70 - 17.70 140.00 12.00 - 17.75 177.00 13.90 - 17.80 150.00 12.40 - 17.85 154.00 12.90 - 17.90 138.00 11.90 - 17.95 161.00 13.20 - 18.00 171.00 13.30 - 18.05 144.00 12.50 - 18.10 148.00 12.40 - 18.15 169.00 13.50 - 18.20 162.00 12.90 - 18.25 171.00 13.50 - 18.30 155.00 12.60 - 18.35 143.00 12.30 - 18.40 162.00 12.80 - 18.45 177.00 13.60 - 18.50 158.00 12.60 - 18.55 142.00 12.20 - 18.60 153.00 12.40 - 18.65 169.00 13.30 - 18.70 144.00 12.00 - 18.75 171.00 13.30 - 18.80 159.00 12.50 - 18.85 169.00 13.10 - 18.90 163.00 12.60 - 18.95 154.00 12.50 - 19.00 146.00 11.90 - 19.05 154.00 12.50 - 19.10 156.00 12.20 - 19.15 195.00 14.00 - 19.20 154.00 12.10 - 19.25 167.00 12.90 - 19.30 156.00 12.20 - 19.35 148.00 12.10 - 19.40 173.00 12.80 - 19.45 155.00 12.40 - 19.50 146.00 11.70 - 19.55 173.00 13.10 - 19.60 179.00 13.00 - 19.65 152.00 12.30 - 19.70 182.00 13.10 - 19.75 183.00 13.40 - 19.80 150.00 11.90 - 19.85 155.00 12.30 - 19.90 158.00 12.20 - 19.95 161.00 12.60 - 20.00 164.00 12.40 - 20.05 166.00 12.80 - 20.10 172.00 12.70 - 20.15 148.00 12.10 - 20.20 161.00 12.30 - 20.25 160.00 12.60 - 20.30 185.00 13.20 - 20.35 165.00 12.80 - 20.40 155.00 12.10 - 20.45 172.00 13.00 - 20.50 170.00 12.70 - 20.55 180.00 13.40 - 20.60 184.00 13.20 - 20.65 164.00 12.80 - 20.70 177.00 13.00 - 20.75 150.00 12.20 - 20.80 176.00 12.90 - 20.85 174.00 13.20 - 20.90 173.00 12.80 - 20.95 167.00 12.90 - 21.00 158.00 12.20 - 21.05 174.00 13.20 - 21.10 160.00 12.30 - 21.15 174.00 13.20 - 21.20 160.00 12.30 - 21.25 182.00 13.40 - 21.30 155.00 12.10 - 21.35 182.00 13.40 - 21.40 157.00 12.20 - 21.45 174.00 13.20 - 21.50 173.00 12.80 - 21.55 165.00 12.80 - 21.60 182.00 13.10 - 21.65 176.00 13.20 - 21.70 150.00 11.90 - 21.75 162.00 12.60 - 21.80 172.00 12.70 - 21.85 162.00 12.70 - 21.90 171.00 12.70 - 21.95 165.00 12.80 - 22.00 180.00 13.00 - 22.05 167.00 12.80 - 22.10 159.00 12.20 - 22.15 159.00 12.50 - 22.20 160.00 12.30 - 22.25 174.00 13.10 - 22.30 175.00 12.90 - 22.35 172.00 13.10 - 22.40 176.00 12.90 - 22.45 140.00 11.80 - 22.50 163.00 12.40 - 22.55 180.00 13.50 - 22.60 211.00 14.20 - 22.65 190.00 13.90 - 22.70 179.00 13.10 - 22.75 195.00 14.10 - 22.80 198.00 13.90 - 22.85 181.00 13.70 - 22.90 203.00 14.10 - 22.95 193.00 14.10 - 23.00 155.00 12.40 - 23.05 159.00 12.90 - 23.10 184.00 13.50 - 23.15 145.00 12.30 - 23.20 145.00 12.00 - 23.25 179.00 13.70 - 23.30 185.00 13.60 - 23.35 168.00 13.30 - 23.40 185.00 13.60 - 23.45 170.00 13.40 - 23.50 174.00 13.30 - 23.55 164.00 13.20 - 23.60 168.00 13.10 - 23.65 185.00 14.10 - 23.70 183.00 13.70 - 23.75 172.00 13.70 - 23.80 156.00 12.70 - 23.85 182.00 14.00 - 23.90 182.00 13.70 - 23.95 149.00 12.70 - 24.00 160.00 12.80 - 24.05 168.00 13.50 - 24.10 178.00 13.60 - 24.15 169.00 13.60 - 24.20 172.00 13.40 - 24.25 170.00 13.60 - 24.30 161.00 12.90 - 24.35 168.00 13.50 - 24.40 162.00 13.00 - 24.45 157.00 13.00 - 24.50 162.00 12.90 - 24.55 159.00 13.10 - 24.60 168.00 13.20 - 24.65 170.00 13.50 - 24.70 166.00 13.00 - 24.75 146.00 12.50 - 24.80 154.00 12.50 - 24.85 154.00 12.70 - 24.90 198.00 14.10 - 24.95 195.00 14.30 - 25.00 148.00 12.20 - 25.05 161.00 12.90 - 25.10 160.00 12.60 - 25.15 160.00 12.80 - 25.20 149.00 12.10 - 25.25 179.00 13.50 - 25.30 174.00 13.00 - 25.35 168.00 13.00 - 25.40 146.00 11.90 - 25.45 160.00 12.70 - 25.50 145.00 11.80 - 25.55 151.00 12.30 - 25.60 161.00 12.40 - 25.65 187.00 13.60 - 25.70 154.00 12.10 - 25.75 157.00 12.40 - 25.80 169.00 12.60 - 25.85 181.00 13.40 - 25.90 156.00 12.10 - 25.95 185.00 13.40 - 26.00 192.00 13.40 - 26.05 153.00 12.20 - 26.10 149.00 11.80 - 26.15 154.00 12.20 - 26.20 152.00 11.90 - 26.25 179.00 13.20 - 26.30 180.00 12.90 - 26.35 160.00 12.50 - 26.40 174.00 12.60 - 26.45 145.00 11.80 - 26.50 171.00 12.50 - 26.55 162.00 12.50 - 26.60 154.00 11.80 - 26.65 153.00 12.10 - 26.70 162.00 12.10 - 26.75 160.00 12.40 - 26.80 150.00 11.70 - 26.85 189.00 13.40 - 26.90 168.00 12.40 - 26.95 144.00 11.70 - 27.00 147.00 11.60 - 27.05 155.00 12.20 - 27.10 174.00 12.60 - 27.15 169.00 12.70 - 27.20 174.00 12.60 - 27.25 164.00 12.60 - 27.30 146.00 11.60 - 27.35 149.00 12.00 - 27.40 155.00 11.90 - 27.45 155.00 12.20 - 27.50 168.00 12.40 - 27.55 131.00 11.20 - 27.60 159.00 12.10 - 27.65 181.00 13.20 - 27.70 146.00 11.60 - 27.75 188.00 13.50 - 27.80 162.00 12.20 - 27.85 161.00 12.50 - 27.90 176.00 12.70 - 27.95 152.00 12.10 - 28.00 170.00 12.40 - 28.05 152.00 12.00 - 28.10 158.00 12.00 - 28.15 168.00 12.60 - 28.20 161.00 12.10 - 28.25 184.00 13.30 - 28.30 166.00 12.30 - 28.35 193.00 13.60 - 28.40 157.00 12.00 - 28.45 167.00 12.60 - 28.50 158.00 12.00 - 28.55 135.00 11.40 - 28.60 150.00 11.70 - 28.65 167.00 12.70 - 28.70 161.00 12.20 - 28.75 157.00 12.30 - 28.80 153.00 11.80 - 28.85 161.00 12.50 - 28.90 163.00 12.20 - 28.95 133.00 11.40 - 29.00 169.00 12.50 - 29.05 162.00 12.50 - 29.10 161.00 12.20 - 29.15 163.00 12.60 - 29.20 144.00 11.60 - 29.25 178.00 13.20 - 29.30 161.00 12.20 - 29.35 141.00 11.80 - 29.40 169.00 12.50 - 29.45 160.00 12.50 - 29.50 177.00 12.90 - 29.55 174.00 13.10 - 29.60 157.00 12.10 - 29.65 176.00 13.20 - 29.70 179.00 13.00 - 29.75 166.00 12.90 - 29.80 162.00 12.40 - 29.85 147.00 12.20 - 29.90 152.00 12.00 - 29.95 171.00 13.20 - 30.00 178.00 13.10 - 30.05 208.00 14.60 - 30.10 178.00 13.20 - 30.15 149.00 12.40 - 30.20 181.00 13.30 - 30.25 162.00 13.00 - 30.30 177.00 13.20 - 30.35 165.00 13.10 - 30.40 177.00 13.30 - 30.45 158.00 12.90 - 30.50 157.00 12.60 - 30.55 163.00 13.10 - 30.60 144.00 12.00 - 30.65 156.00 12.80 - 30.70 176.00 13.30 - 30.75 179.00 13.70 - 30.80 174.00 13.20 - 30.85 182.00 13.80 - 30.90 161.00 12.70 - 30.95 166.00 13.10 - 31.00 168.00 13.00 - 31.05 153.00 12.60 - 31.10 156.00 12.40 - 31.15 174.00 13.40 - 31.20 167.00 12.80 - 31.25 192.00 14.00 - 31.30 154.00 12.30 - 31.35 166.00 13.00 - 31.40 169.00 12.90 - 31.45 185.00 13.70 - 31.50 165.00 12.60 - 31.55 163.00 12.80 - 31.60 173.00 12.90 - 31.65 169.00 13.00 - 31.70 188.00 13.40 - 31.75 195.00 13.90 - 31.80 195.00 13.60 - 31.85 221.00 14.70 - 31.90 229.00 14.70 - 31.95 302.00 17.20 - 32.00 327.00 17.50 - 32.05 380.00 19.30 - 32.10 358.00 18.30 - 32.15 394.00 19.60 - 32.20 373.00 18.70 - 32.25 362.00 18.70 - 32.30 306.00 16.90 - 32.35 276.00 16.40 - 32.40 237.00 14.80 - 32.45 203.00 14.00 - 32.50 178.00 12.80 - 32.55 199.00 13.90 - 32.60 167.00 12.40 - 32.65 185.00 13.40 - 32.70 180.00 12.90 - 32.75 178.00 13.10 - 32.80 145.00 11.50 - 32.85 176.00 13.00 - 32.90 177.00 12.70 - 32.95 182.00 13.20 - 33.00 167.00 12.40 - 33.05 152.00 12.10 - 33.10 144.00 11.50 - 33.15 170.00 12.80 - 33.20 156.00 11.90 - 33.25 154.00 12.20 - 33.30 180.00 12.80 - 33.35 176.00 13.00 - 33.40 183.00 12.90 - 33.45 162.00 12.40 - 33.50 180.00 12.80 - 33.55 165.00 12.60 - 33.60 174.00 12.50 - 33.65 179.00 13.00 - 33.70 152.00 11.70 - 33.75 182.00 13.10 - 33.80 184.00 12.90 - 33.85 166.00 12.50 - 33.90 182.00 12.80 - 33.95 162.00 12.40 - 34.00 174.00 12.50 - 34.05 153.00 12.00 - 34.10 182.00 12.80 - 34.15 180.00 13.00 - 34.20 167.00 12.20 - 34.25 173.00 12.70 - 34.30 153.00 11.70 - 34.35 160.00 12.30 - 34.40 180.00 12.70 - 34.45 168.00 12.50 - 34.50 167.00 12.20 - 34.55 176.00 12.80 - 34.60 165.00 12.10 - 34.65 174.00 12.80 - 34.70 161.00 12.00 - 34.75 178.00 12.90 - 34.80 170.00 12.30 - 34.85 166.00 12.50 - 34.90 173.00 12.40 - 34.95 158.00 12.20 - 35.00 166.00 12.20 - 35.05 170.00 12.60 - 35.10 162.00 12.00 - 35.15 183.00 13.10 - 35.20 176.00 12.50 - 35.25 171.00 12.60 - 35.30 174.00 12.50 - 35.35 179.00 12.90 - 35.40 176.00 12.50 - 35.45 193.00 13.40 - 35.50 180.00 12.70 - 35.55 188.00 13.30 - 35.60 177.00 12.60 - 35.65 176.00 12.90 - 35.70 171.00 12.40 - 35.75 185.00 13.30 - 35.80 178.00 12.70 - 35.85 152.00 12.10 - 35.90 160.00 12.10 - 35.95 187.00 13.50 - 36.00 167.00 12.40 - 36.05 181.00 13.30 - 36.10 166.00 12.40 - 36.15 165.00 12.80 - 36.20 170.00 12.70 - 36.25 197.00 14.10 - 36.30 179.00 13.10 - 36.35 172.00 13.20 - 36.40 181.00 13.30 - 36.45 174.00 13.40 - 36.50 162.00 12.60 - 36.55 166.00 13.10 - 36.60 158.00 12.50 - 36.65 199.00 14.40 - 36.70 188.00 13.70 - 36.75 177.00 13.70 - 36.80 167.00 12.90 - 36.85 156.00 12.90 - 36.90 174.00 13.20 - 36.95 176.00 13.70 - 37.00 152.00 12.40 - 37.05 191.00 14.40 - 37.10 151.00 12.50 - 37.15 202.00 14.80 - 37.20 191.00 14.00 - 37.25 161.00 13.20 - 37.30 199.00 14.30 - 37.35 175.00 13.70 - 37.40 146.00 12.30 - 37.45 181.00 14.00 - 37.50 221.00 15.00 - 37.55 194.00 14.40 - 37.60 158.00 12.70 - 37.65 171.00 13.50 - 37.70 172.00 13.20 - 37.75 168.00 13.30 - 37.80 192.00 13.90 - 37.85 185.00 13.90 - 37.90 193.00 13.90 - 37.95 178.00 13.60 - 38.00 195.00 13.90 - 38.05 175.00 13.40 - 38.10 178.00 13.20 - 38.15 173.00 13.30 - 38.20 195.00 13.70 - 38.25 194.00 13.90 - 38.30 191.00 13.50 - 38.35 178.00 13.30 - 38.40 184.00 13.30 - 38.45 186.00 13.50 - 38.50 202.00 13.80 - 38.55 200.00 14.00 - 38.60 210.00 14.00 - 38.65 198.00 13.90 - 38.70 225.00 14.50 - 38.75 209.00 14.30 - 38.80 229.00 14.60 - 38.85 197.00 13.90 - 38.90 220.00 14.30 - 38.95 215.00 14.40 - 39.00 242.00 15.00 - 39.05 340.00 18.10 - 39.10 441.00 20.20 - 39.15 654.00 25.10 - 39.20 962.00 29.70 - 39.25 1477.00 37.70 - 39.30 2012.00 43.00 - 39.35 2634.00 50.20 - 39.40 3115.00 53.40 - 39.45 3467.00 57.50 - 39.50 3532.00 56.70 - 39.55 3337.00 56.30 - 39.60 2595.00 48.60 - 39.65 1943.00 42.90 - 39.70 1251.00 33.70 - 39.75 828.00 28.00 - 39.80 525.00 21.80 - 39.85 377.00 18.80 - 39.90 294.00 16.30 - 39.95 233.00 14.80 - 40.00 233.00 14.50 - 40.05 253.00 15.40 - 40.10 253.00 15.10 - 40.15 213.00 14.10 - 40.20 196.00 13.20 - 40.25 222.00 14.40 - 40.30 172.00 12.40 - 40.35 218.00 14.30 - 40.40 206.00 13.60 - 40.45 195.00 13.60 - 40.50 209.00 13.70 - 40.55 192.00 13.50 - 40.60 197.00 13.30 - 40.65 188.00 13.30 - 40.70 202.00 13.50 - 40.75 208.00 14.00 - 40.80 184.00 12.90 - 40.85 177.00 13.00 - 40.90 202.00 13.50 - 40.95 198.00 13.80 - 41.00 203.00 13.60 - 41.05 193.00 13.60 - 41.10 188.00 13.10 - 41.15 211.00 14.20 - 41.20 189.00 13.10 - 41.25 200.00 13.90 - 41.30 198.00 13.50 - 41.35 203.00 14.00 - 41.40 197.00 13.40 - 41.45 190.00 13.60 - 41.50 212.00 14.00 - 41.55 185.00 13.40 - 41.60 228.00 14.50 - 41.65 167.00 12.80 - 41.70 207.00 13.90 - 41.75 187.00 13.60 - 41.80 190.00 13.30 - 41.85 192.00 13.80 - 41.90 185.00 13.20 - 41.95 161.00 12.70 - 42.00 187.00 13.30 - 42.05 191.00 13.80 - 42.10 159.00 12.30 - 42.15 170.00 13.10 - 42.20 182.00 13.20 - 42.25 186.00 13.70 - 42.30 192.00 13.60 - 42.35 178.00 13.50 - 42.40 186.00 13.40 - 42.45 180.00 13.50 - 42.50 178.00 13.10 - 42.55 182.00 13.60 - 42.60 179.00 13.20 - 42.65 203.00 14.50 - 42.70 191.00 13.70 - 42.75 207.00 14.60 - 42.80 183.00 13.40 - 42.85 180.00 13.60 - 42.90 191.00 13.70 - 42.95 187.00 13.90 - 43.00 184.00 13.50 - 43.05 182.00 13.80 - 43.10 178.00 13.30 - 43.15 169.00 13.30 - 43.20 158.00 12.60 - 43.25 180.00 13.70 - 43.30 174.00 13.20 - 43.35 184.00 14.00 - 43.40 178.00 13.40 - 43.45 180.00 13.80 - 43.50 144.00 12.00 - 43.55 169.00 13.40 - 43.60 177.00 13.30 - 43.65 156.00 12.80 - 43.70 148.00 12.20 - 43.75 159.00 12.90 - 43.80 195.00 14.00 - 43.85 186.00 14.00 - 43.90 180.00 13.40 - 43.95 192.00 14.10 - 44.00 186.00 13.50 - 44.05 180.00 13.60 - 44.10 174.00 13.10 - 44.15 181.00 13.60 - 44.20 178.00 13.20 - 44.25 189.00 13.80 - 44.30 206.00 14.10 - 44.35 183.00 13.60 - 44.40 161.00 12.40 - 44.45 170.00 13.00 - 44.50 203.00 13.90 - 44.55 168.00 12.90 - 44.60 199.00 13.70 - 44.65 192.00 13.70 - 44.70 192.00 13.40 - 44.75 200.00 14.00 - 44.80 206.00 13.90 - 44.85 193.00 13.70 - 44.90 188.00 13.20 - 44.95 200.00 13.90 - 45.00 193.00 13.40 - 45.05 203.00 14.00 - 45.10 212.00 14.00 - 45.15 197.00 13.80 - 45.20 219.00 14.20 - 45.25 219.00 14.60 - 45.30 226.00 14.50 - 45.35 282.00 16.50 - 45.40 353.00 18.10 - 45.45 469.00 21.30 - 45.50 741.00 26.20 - 45.55 1176.00 33.70 - 45.60 1577.00 38.10 - 45.65 2122.00 45.30 - 45.70 2726.00 50.10 - 45.75 2990.00 53.70 - 45.80 2991.00 52.50 - 45.85 2796.00 52.00 - 45.90 2372.00 46.80 - 45.95 1752.00 41.20 - 46.00 1209.00 33.40 - 46.05 824.00 28.30 - 46.10 512.00 21.80 - 46.15 353.00 18.60 - 46.20 273.00 15.90 - 46.25 259.00 15.90 - 46.30 233.00 14.80 - 46.35 220.00 14.70 - 46.40 228.00 14.60 - 46.45 231.00 15.10 - 46.50 218.00 14.30 - 46.55 210.00 14.40 - 46.60 212.00 14.20 - 46.65 187.00 13.60 - 46.70 207.00 14.00 - 46.75 212.00 14.50 - 46.80 188.00 13.40 - 46.85 178.00 13.30 - 46.90 186.00 13.30 - 46.95 192.00 13.80 - 47.00 192.00 13.50 - 47.05 186.00 13.60 - 47.10 208.00 14.10 - 47.15 199.00 14.10 - 47.20 165.00 12.50 - 47.25 212.00 14.50 - 47.30 191.00 13.50 - 47.35 185.00 13.60 - 47.40 171.00 12.70 - 47.45 176.00 13.20 - 47.50 179.00 13.00 - 47.55 187.00 13.60 - 47.60 181.00 13.10 - 47.65 173.00 13.10 - 47.70 167.00 12.50 - 47.75 182.00 13.40 - 47.80 171.00 12.70 - 47.85 185.00 13.50 - 47.90 177.00 12.90 - 47.95 154.00 12.40 - 48.00 200.00 13.70 - 48.05 177.00 13.30 - 48.10 184.00 13.20 - 48.15 166.00 12.80 - 48.20 181.00 13.10 - 48.25 208.00 14.40 - 48.30 186.00 13.20 - 48.35 164.00 12.70 - 48.40 196.00 13.60 - 48.45 169.00 12.90 - 48.50 173.00 12.70 - 48.55 200.00 14.10 - 48.60 163.00 12.40 - 48.65 173.00 13.10 - 48.70 187.00 13.30 - 48.75 177.00 13.30 - 48.80 200.00 13.80 - 48.85 171.00 13.00 - 48.90 192.00 13.50 - 48.95 178.00 13.30 - 49.00 169.00 12.70 - 49.05 160.00 12.70 - 49.10 182.00 13.20 - 49.15 173.00 13.20 - 49.20 170.00 12.80 - 49.25 181.00 13.60 - 49.30 170.00 12.90 - 49.35 164.00 13.00 - 49.40 166.00 12.70 - 49.45 174.00 13.40 - 49.50 173.00 13.10 - 49.55 137.00 11.90 - 49.60 166.00 12.80 - 49.65 194.00 14.20 - 49.70 160.00 12.60 - 49.75 152.00 12.50 - 49.80 180.00 13.30 - 49.85 160.00 12.90 - 49.90 149.00 12.20 - 49.95 172.00 13.40 - 50.00 170.00 13.00 - 50.05 175.00 13.50 - 50.10 162.00 12.70 - 50.15 168.00 13.20 - 50.20 186.00 13.60 - 50.25 179.00 13.60 - 50.30 165.00 12.70 - 50.35 155.00 12.60 - 50.40 170.00 12.90 - 50.45 162.00 12.80 - 50.50 157.00 12.30 - 50.55 173.00 13.20 - 50.60 149.00 12.00 - 50.65 167.00 13.00 - 50.70 165.00 12.60 - 50.75 157.00 12.50 - 50.80 177.00 13.00 - 50.85 187.00 13.60 - 50.90 155.00 12.10 - 50.95 194.00 13.70 - 51.00 147.00 11.70 - 51.05 169.00 12.80 - 51.10 166.00 12.40 - 51.15 193.00 13.60 - 51.20 168.00 12.40 - 51.25 188.00 13.40 - 51.30 182.00 12.80 - 51.35 180.00 13.10 - 51.40 177.00 12.70 - 51.45 188.00 13.30 - 51.50 187.00 13.00 - 51.55 178.00 12.90 - 51.60 177.00 12.60 - 51.65 184.00 13.10 - 51.70 172.00 12.40 - 51.75 188.00 13.30 - 51.80 194.00 13.20 - 51.85 179.00 12.90 - 51.90 176.00 12.50 - 51.95 180.00 12.90 - 52.00 169.00 12.20 - 52.05 178.00 12.90 - 52.10 165.00 12.10 - 52.15 149.00 11.70 - 52.20 168.00 12.20 - 52.25 157.00 12.10 - 52.30 151.00 11.60 - 52.35 181.00 13.00 - 52.40 172.00 12.40 - 52.45 178.00 12.90 - 52.50 179.00 12.60 - 52.55 171.00 12.60 - 52.60 129.00 10.70 - 52.65 180.00 13.00 - 52.70 154.00 11.70 - 52.75 182.00 13.10 - 52.80 166.00 12.20 - 52.85 156.00 12.10 - 52.90 164.00 12.10 - 52.95 166.00 12.50 - 53.00 176.00 12.50 - 53.05 182.00 13.10 - 53.10 173.00 12.50 - 53.15 160.00 12.30 - 53.20 169.00 12.30 - 53.25 162.00 12.30 - 53.30 164.00 12.10 - 53.35 165.00 12.40 - 53.40 177.00 12.60 - 53.45 173.00 12.80 - 53.50 158.00 11.90 - 53.55 164.00 12.40 - 53.60 175.00 12.50 - 53.65 166.00 12.50 - 53.70 161.00 12.00 - 53.75 167.00 12.50 - 53.80 136.00 11.00 - 53.85 167.00 12.50 - 53.90 152.00 11.70 - 53.95 159.00 12.20 - 54.00 172.00 12.40 - 54.05 179.00 12.90 - 54.10 169.00 12.20 - 54.15 165.00 12.40 - 54.20 166.00 12.10 - 54.25 162.00 12.30 - 54.30 175.00 12.40 - 54.35 162.00 12.30 - 54.40 145.00 11.40 - 54.45 148.00 11.70 - 54.50 157.00 11.80 - 54.55 176.00 12.80 - 54.60 162.00 12.00 - 54.65 153.00 12.00 - 54.70 178.00 12.60 - 54.75 147.00 11.80 - 54.80 146.00 11.50 - 54.85 170.00 12.70 - 54.90 155.00 11.80 - 54.95 170.00 12.70 - 55.00 142.00 11.30 - 55.05 154.00 12.10 - 55.10 150.00 11.70 - 55.15 145.00 11.80 - 55.20 151.00 11.80 - 55.25 162.00 12.50 - 55.30 153.00 11.90 - 55.35 170.00 12.90 - 55.40 153.00 11.90 - 55.45 156.00 12.40 - 55.50 163.00 12.40 - 55.55 149.00 12.20 - 55.60 135.00 11.30 - 55.65 158.00 12.60 - 55.70 144.00 11.70 - 55.75 152.00 12.40 - 55.80 165.00 12.70 - 55.85 164.00 13.00 - 55.90 175.00 13.10 - 55.95 150.00 12.40 - 56.00 168.00 12.90 - 56.05 159.00 12.90 - 56.10 187.00 13.60 - 56.15 170.00 13.30 - 56.20 159.00 12.60 - 56.25 148.00 12.50 - 56.30 159.00 12.60 - 56.35 174.00 13.50 - 56.40 195.00 14.00 - 56.45 219.00 15.10 - 56.50 216.00 14.70 - 56.55 271.00 16.80 - 56.60 337.00 18.30 - 56.65 417.00 20.80 - 56.70 390.00 19.70 - 56.75 414.00 20.70 - 56.80 388.00 19.60 - 56.85 317.00 18.10 - 56.90 307.00 17.40 - 56.95 250.00 16.00 - 57.00 205.00 14.20 - 57.05 167.00 13.00 - 57.10 179.00 13.20 - 57.15 159.00 12.70 - 57.20 170.00 12.80 - 57.25 168.00 13.00 - 57.30 180.00 13.10 - 57.35 144.00 12.00 - 57.40 178.00 13.00 - 57.45 203.00 14.20 - 57.50 159.00 12.30 - 57.55 165.00 12.80 - 57.60 164.00 12.40 - 57.65 135.00 11.60 - 57.70 157.00 12.20 - 57.75 162.00 12.70 - 57.80 175.00 12.90 - 57.85 161.00 12.60 - 57.90 174.00 12.80 - 57.95 187.00 13.70 - 58.00 164.00 12.50 - 58.05 188.00 13.70 - 58.10 163.00 12.40 - 58.15 177.00 13.30 - 58.20 181.00 13.10 - 58.25 156.00 12.50 - 58.30 163.00 12.40 - 58.35 190.00 13.80 - 58.40 162.00 12.40 - 58.45 186.00 13.70 - 58.50 169.00 12.70 - 58.55 160.00 12.70 - 58.60 171.00 12.80 - 58.65 160.00 12.60 - 58.70 174.00 12.90 - 58.75 163.00 12.70 - 58.80 180.00 13.10 - 58.85 176.00 13.20 - 58.90 174.00 12.80 - 58.95 177.00 13.30 - 59.00 186.00 13.30 - 59.05 157.00 12.40 - 59.10 188.00 13.30 - 59.15 162.00 12.60 - 59.20 160.00 12.20 - 59.25 196.00 13.90 - 59.30 178.00 12.90 - 59.35 188.00 13.50 - 59.40 161.00 12.30 - 59.45 157.00 12.30 - 59.50 183.00 13.00 - 59.55 169.00 12.80 - 59.60 150.00 11.80 - 59.65 195.00 13.70 - 59.70 175.00 12.70 - 59.75 160.00 12.40 - 59.80 168.00 12.40 - 59.85 191.00 13.50 - 59.90 181.00 12.80 - 59.95 168.00 12.70 - 60.00 181.00 12.80 - 60.05 158.00 12.20 - 60.10 160.00 12.00 - 60.15 151.00 12.00 - 60.20 171.00 12.40 - 60.25 167.00 12.60 - 60.30 160.00 12.00 - 60.35 157.00 12.10 - 60.40 172.00 12.40 - 60.45 140.00 11.50 - 60.50 172.00 12.40 - 60.55 150.00 11.90 - 60.60 179.00 12.70 - 60.65 153.00 12.00 - 60.70 170.00 12.40 - 60.75 184.00 13.10 - 60.80 158.00 11.90 - 60.85 177.00 12.90 - 60.90 159.00 12.00 - 60.95 157.00 12.20 - 61.00 168.00 12.30 - 61.05 154.00 12.00 - 61.10 170.00 12.40 - 61.15 147.00 11.80 - 61.20 161.00 12.10 - 61.25 175.00 12.90 - 61.30 170.00 12.40 - 61.35 153.00 12.10 - 61.40 165.00 12.30 - 61.45 164.00 12.50 - 61.50 174.00 12.60 - 61.55 160.00 12.40 - 61.60 188.00 13.20 - 61.65 182.00 13.30 - 61.70 197.00 13.50 - 61.75 163.00 12.60 - 61.80 176.00 12.80 - 61.85 157.00 12.40 - 61.90 166.00 12.40 - 61.95 173.00 13.10 - 62.00 167.00 12.50 - 62.05 175.00 13.20 - 62.10 143.00 11.60 - 62.15 148.00 12.10 - 62.20 178.00 13.00 - 62.25 180.00 13.40 - 62.30 141.00 11.60 - 62.35 202.00 14.30 - 62.40 172.00 12.80 - 62.45 169.00 13.00 - 62.50 143.00 11.80 - 62.55 146.00 12.20 - 62.60 169.00 12.80 - 62.65 146.00 12.30 - 62.70 156.00 12.30 - 62.75 147.00 12.30 - 62.80 158.00 12.40 - 62.85 178.00 13.50 - 62.90 163.00 12.60 - 62.95 168.00 13.10 - 63.00 164.00 12.60 - 63.05 180.00 13.60 - 63.10 189.00 13.60 - 63.15 164.00 12.90 - 63.20 181.00 13.20 - 63.25 179.00 13.50 - 63.30 147.00 11.90 - 63.35 179.00 13.50 - 63.40 150.00 12.00 - 63.45 168.00 12.90 - 63.50 156.00 12.20 - 63.55 181.00 13.40 - 63.60 170.00 12.70 - 63.65 181.00 13.30 - 63.70 184.00 13.10 - 63.75 153.00 12.20 - 63.80 166.00 12.40 - 63.85 166.00 12.60 - 63.90 169.00 12.50 - 63.95 175.00 12.90 - 64.00 157.00 12.00 - 64.05 165.00 12.40 - 64.10 169.00 12.30 - 64.15 164.00 12.40 - 64.20 181.00 12.80 - 64.25 189.00 13.30 - 64.30 179.00 12.60 - 64.35 157.00 12.10 - 64.40 189.00 13.00 - 64.45 167.00 12.50 - 64.50 178.00 12.50 - 64.55 144.00 11.60 - 64.60 180.00 12.60 - 64.65 182.00 12.90 - 64.70 199.00 13.20 - 64.75 172.00 12.60 - 64.80 191.00 12.90 - 64.85 166.00 12.30 - 64.90 157.00 11.70 - 64.95 197.00 13.50 - 65.00 204.00 13.40 - 65.05 183.00 13.00 - 65.10 189.00 12.90 - 65.15 189.00 13.20 - 65.20 170.00 12.20 - 65.25 188.00 13.20 - 65.30 176.00 12.40 - 65.35 172.00 12.60 - 65.40 182.00 12.70 - 65.45 205.00 13.80 - 65.50 191.00 13.00 - 65.55 192.00 13.30 - 65.60 190.00 12.90 - 65.65 194.00 13.40 - 65.70 212.00 13.70 - 65.75 221.00 14.30 - 65.80 227.00 14.20 - 65.85 227.00 14.60 - 65.90 239.00 14.60 - 65.95 261.00 15.60 - 66.00 301.00 16.40 - 66.05 409.00 19.60 - 66.10 559.00 22.30 - 66.15 820.00 27.80 - 66.20 1276.00 33.90 - 66.25 1776.00 41.00 - 66.30 2322.00 45.70 - 66.35 2880.00 52.20 - 66.40 3051.00 52.50 - 66.45 2980.00 53.10 - 66.50 2572.00 48.20 - 66.55 1961.00 43.20 - 66.60 1315.00 34.50 - 66.65 919.00 29.60 - 66.70 548.00 22.40 - 66.75 405.00 19.70 - 66.80 299.00 16.50 - 66.85 309.00 17.20 - 66.90 279.00 15.90 - 66.95 281.00 16.40 - 67.00 235.00 14.70 - 67.05 239.00 15.10 - 67.10 212.00 14.00 - 67.15 228.00 14.80 - 67.20 231.00 14.50 - 67.25 198.00 13.80 - 67.30 223.00 14.30 - 67.35 201.00 13.90 - 67.40 208.00 13.80 - 67.45 207.00 14.10 - 67.50 217.00 14.10 - 67.55 196.00 13.70 - 67.60 182.00 12.90 - 67.65 182.00 13.20 - 67.70 186.00 13.10 - 67.75 176.00 13.00 - 67.80 192.00 13.30 - 67.85 215.00 14.50 - 67.90 178.00 12.90 - 67.95 191.00 13.70 - 68.00 178.00 12.90 - 68.05 185.00 13.50 - 68.10 171.00 12.70 - 68.15 174.00 13.30 - 68.20 193.00 13.60 - 68.25 182.00 13.60 - 68.30 178.00 13.10 - 68.35 196.00 14.10 - 68.40 178.00 13.10 - 68.45 173.00 13.30 - 68.50 175.00 13.10 - 68.55 178.00 13.60 - 68.60 177.00 13.20 - 68.65 176.00 13.60 - 68.70 200.00 14.10 - 68.75 177.00 13.60 - 68.80 185.00 13.60 - 68.85 167.00 13.20 - 68.90 158.00 12.60 - 68.95 176.00 13.60 - 69.00 192.00 13.80 - 69.05 174.00 13.50 - 69.10 154.00 12.40 - 69.15 153.00 12.70 - 69.20 167.00 12.90 - 69.25 168.00 13.30 - 69.30 167.00 12.90 - 69.35 163.00 13.10 - 69.40 157.00 12.50 - 69.45 185.00 13.90 - 69.50 151.00 12.30 - 69.55 176.00 13.50 - 69.60 187.00 13.60 - 69.65 170.00 13.20 - 69.70 164.00 12.70 - 69.75 204.00 14.50 - 69.80 169.00 12.80 - 69.85 191.00 13.90 - 69.90 177.00 13.10 - 69.95 157.00 12.60 - 70.00 173.00 12.80 - 70.05 199.00 14.10 - 70.10 168.00 12.60 - 70.15 191.00 13.70 - 70.20 165.00 12.40 - 70.25 156.00 12.30 - 70.30 163.00 12.30 - 70.35 149.00 12.00 - 70.40 199.00 13.60 - 70.45 158.00 12.30 - 70.50 158.00 12.10 - 70.55 150.00 12.00 - 70.60 197.00 13.50 - 70.65 167.00 12.60 - 70.70 180.00 12.80 - 70.75 187.00 13.40 - 70.80 190.00 13.20 - 70.85 169.00 12.70 - 70.90 214.00 14.00 - 70.95 188.00 13.50 - 71.00 200.00 13.50 - 71.05 186.00 13.30 - 71.10 169.00 12.40 - 71.15 166.00 12.60 - 71.20 175.00 12.60 - 71.25 170.00 12.80 - 71.30 191.00 13.20 - 71.35 185.00 13.30 - 71.40 191.00 13.20 - 71.45 181.00 13.20 - 71.50 188.00 13.10 - 71.55 164.00 12.60 - 71.60 185.00 13.00 - 71.65 168.00 12.70 - 71.70 168.00 12.40 - 71.75 167.00 12.60 - 71.80 158.00 12.00 - 71.85 173.00 12.90 - 71.90 177.00 12.70 - 71.95 193.00 13.60 - 72.00 190.00 13.20 - 72.05 174.00 12.90 - 72.10 161.00 12.10 - 72.15 147.00 11.80 - 72.20 165.00 12.30 - 72.25 188.00 13.40 - 72.30 172.00 12.50 - 72.35 176.00 12.90 - 72.40 167.00 12.30 - 72.45 186.00 13.30 - 72.50 178.00 12.70 - 72.55 158.00 12.20 - 72.60 168.00 12.30 - 72.65 180.00 13.10 - 72.70 154.00 11.80 - 72.75 162.00 12.40 - 72.80 168.00 12.30 - 72.85 194.00 13.50 - 72.90 164.00 12.10 - 72.95 169.00 12.60 - 73.00 160.00 12.00 - 73.05 164.00 12.50 - 73.10 171.00 12.40 - 73.15 169.00 12.60 - 73.20 167.00 12.30 - 73.25 150.00 12.00 - 73.30 173.00 12.50 - 73.35 183.00 13.20 - 73.40 169.00 12.40 - 73.45 180.00 13.10 - 73.50 173.00 12.50 - 73.55 195.00 13.70 - 73.60 178.00 12.80 - 73.65 193.00 13.60 - 73.70 179.00 12.80 - 73.75 153.00 12.20 - 73.80 169.00 12.40 - 73.85 165.00 12.60 - 73.90 172.00 12.60 - 73.95 171.00 12.80 - 74.00 178.00 12.80 - 74.05 180.00 13.20 - 74.10 168.00 12.50 - 74.15 169.00 12.80 - 74.20 190.00 13.20 - 74.25 170.00 12.80 - 74.30 178.00 12.80 - 74.35 158.00 12.40 - 74.40 185.00 13.10 - 74.45 181.00 13.30 - 74.50 173.00 12.70 - 74.55 163.00 12.60 - 74.60 184.00 13.10 - 74.65 181.00 13.40 - 74.70 192.00 13.50 - 74.75 166.00 12.90 - 74.80 168.00 12.60 - 74.85 200.00 14.20 - 74.90 188.00 13.40 - 74.95 190.00 13.90 - 75.00 211.00 14.30 - 75.05 172.00 13.20 - 75.10 198.00 13.90 - 75.15 230.00 15.40 - 75.20 264.00 16.10 - 75.25 227.00 15.20 - 75.30 289.00 16.80 - 75.35 290.00 17.20 - 75.40 284.00 16.70 - 75.45 250.00 16.10 - 75.50 233.00 15.10 - 75.55 239.00 15.70 - 75.60 239.00 15.30 - 75.65 204.00 14.40 - 75.70 178.00 13.20 - 75.75 189.00 13.90 - 75.80 202.00 14.00 - 75.85 181.00 13.50 - 75.90 190.00 13.50 - 75.95 177.00 13.30 - 76.00 199.00 13.80 - 76.05 193.00 13.90 - 76.10 170.00 12.70 - 76.15 170.00 13.00 - 76.20 165.00 12.50 - 76.25 192.00 13.70 - 76.30 171.00 12.70 - 76.35 169.00 12.80 - 76.40 168.00 12.50 - 76.45 183.00 13.30 - 76.50 173.00 12.60 - 76.55 178.00 13.10 - 76.60 175.00 12.70 - 76.65 191.00 13.50 - 76.70 166.00 12.30 - 76.75 187.00 13.40 - 76.80 191.00 13.20 - 76.85 184.00 13.30 - 76.90 168.00 12.40 - 76.95 177.00 13.00 - 77.00 205.00 13.70 - 77.05 188.00 13.40 - 77.10 166.00 12.30 - 77.15 180.00 13.10 - 77.20 179.00 12.80 - 77.25 179.00 13.10 - 77.30 163.00 12.20 - 77.35 188.00 13.40 - 77.40 169.00 12.40 - 77.45 179.00 13.00 - 77.50 169.00 12.40 - 77.55 201.00 13.80 - 77.60 184.00 12.90 - 77.65 187.00 13.30 - 77.70 207.00 13.70 - 77.75 170.00 12.70 - 77.80 193.00 13.20 - 77.85 189.00 13.50 - 77.90 205.00 13.70 - 77.95 183.00 13.20 - 78.00 179.00 12.80 - 78.05 188.00 13.40 - 78.10 194.00 13.30 - 78.15 220.00 14.50 - 78.20 195.00 13.40 - 78.25 176.00 13.00 - 78.30 208.00 13.80 - 78.35 185.00 13.30 - 78.40 217.00 14.10 - 78.45 203.00 14.00 - 78.50 200.00 13.50 - 78.55 196.00 13.70 - 78.60 197.00 13.40 - 78.65 217.00 14.40 - 78.70 179.00 12.80 - 78.75 184.00 13.30 - 78.80 187.00 13.10 - 78.85 219.00 14.40 - 78.90 193.00 13.30 - 78.95 214.00 14.30 - 79.00 207.00 13.70 - 79.05 199.00 13.80 - 79.10 224.00 14.30 - 79.15 244.00 15.20 - 79.20 217.00 14.10 - 79.25 266.00 15.90 - 79.30 281.00 16.00 - 79.35 425.00 20.10 - 79.40 527.00 21.90 - 79.45 735.00 26.50 - 79.50 1057.00 31.10 - 79.55 1483.00 37.70 - 79.60 1955.00 42.20 - 79.65 2315.00 47.10 - 79.70 2552.00 48.30 - 79.75 2506.00 49.00 - 79.80 2261.00 45.50 - 79.85 1842.00 42.10 - 79.90 1328.00 34.90 - 79.95 911.00 29.60 - 80.00 592.00 23.40 - 80.05 430.00 20.40 - 80.10 312.00 17.00 - 80.15 284.00 16.60 - 80.20 285.00 16.20 - 80.25 247.00 15.50 - 80.30 250.00 15.20 - 80.35 231.00 15.00 - 80.40 272.00 15.90 - 80.45 235.00 15.20 - 80.50 188.00 13.20 - 80.55 223.00 14.80 - 80.60 218.00 14.30 - 80.65 221.00 14.80 - 80.70 210.00 14.10 - 80.75 199.00 14.00 - 80.80 207.00 14.00 - 80.85 208.00 14.40 - 80.90 178.00 13.00 - 80.95 194.00 14.00 - 81.00 202.00 13.90 - 81.05 226.00 15.10 - 81.10 209.00 14.20 - 81.15 194.00 14.10 - 81.20 179.00 13.20 - 81.25 183.00 13.70 - 81.30 187.00 13.50 - 81.35 198.00 14.30 - 81.40 198.00 14.00 - 81.45 209.00 14.70 - 81.50 187.00 13.60 - 81.55 211.00 14.90 - 81.60 198.00 14.10 - 81.65 164.00 13.10 - 81.70 200.00 14.10 - 81.75 212.00 14.90 - 81.80 197.00 14.00 - 81.85 191.00 14.20 - 81.90 195.00 14.00 - 81.95 217.00 15.10 - 82.00 189.00 13.80 - 82.05 182.00 13.80 - 82.10 174.00 13.20 - 82.15 182.00 13.80 - 82.20 199.00 14.00 - 82.25 179.00 13.60 - 82.30 197.00 13.90 - 82.35 228.00 15.30 - 82.40 170.00 12.90 - 82.45 203.00 14.40 - 82.50 232.00 15.10 - 82.55 178.00 13.50 - 82.60 216.00 14.50 - 82.65 205.00 14.30 - 82.70 185.00 13.30 - 82.75 212.00 14.60 - 82.80 199.00 13.70 - 82.85 169.00 12.90 - 82.90 165.00 12.50 - 82.95 203.00 14.10 - 83.00 215.00 14.20 - 83.05 199.00 13.90 - 83.10 200.00 13.60 - 83.15 174.00 12.90 - 83.20 192.00 13.30 - 83.25 206.00 14.10 - 83.30 191.00 13.20 - 83.35 203.00 13.90 - 83.40 210.00 13.90 - 83.45 194.00 13.60 - 83.50 245.00 14.90 - 83.55 242.00 15.10 - 83.60 255.00 15.20 - 83.65 310.00 17.10 - 83.70 408.00 19.20 - 83.75 498.00 21.70 - 83.80 729.00 25.60 - 83.85 934.00 29.60 - 83.90 1121.00 31.70 - 83.95 1320.00 35.20 - 84.00 1476.00 36.30 - 84.05 1276.00 34.60 - 84.10 1129.00 31.80 - 84.15 887.00 28.80 - 84.20 643.00 23.90 - 84.25 490.00 21.40 - 84.30 343.00 17.50 - 84.35 284.00 16.30 - 84.40 263.00 15.30 - 84.45 229.00 14.60 - 84.50 235.00 14.50 - 84.55 246.00 15.10 - 84.60 205.00 13.50 - 84.65 217.00 14.20 - 84.70 217.00 13.90 - 84.75 197.00 13.50 - 84.80 195.00 13.10 - 84.85 232.00 14.70 - 84.90 182.00 12.70 - 84.95 192.00 13.40 - 85.00 172.00 12.40 - 85.05 191.00 13.30 - 85.10 200.00 13.30 - 85.15 186.00 13.10 - 85.20 190.00 13.00 - 85.25 211.00 14.00 - 85.30 184.00 12.80 - 85.35 180.00 12.90 - 85.40 182.00 12.70 - 85.45 184.00 13.10 - 85.50 175.00 12.40 - 85.55 176.00 12.80 - 85.60 166.00 12.10 - 85.65 180.00 12.90 - 85.70 195.00 13.10 - 85.75 183.00 13.10 - 85.80 182.00 12.70 - 85.85 168.00 12.50 - 85.90 177.00 12.60 - 85.95 190.00 13.30 - 86.00 178.00 12.60 - 86.05 180.00 13.00 - 86.10 181.00 12.70 - 86.15 177.00 12.90 - 86.20 171.00 12.40 - 86.25 193.00 13.50 - 86.30 181.00 12.70 - 86.35 180.00 13.00 - 86.40 198.00 13.30 - 86.45 177.00 12.90 - 86.50 161.00 12.00 - 86.55 166.00 12.50 - 86.60 176.00 12.60 - 86.65 190.00 13.40 - 86.70 185.00 12.90 - 86.75 173.00 12.90 - 86.80 176.00 12.60 - 86.85 159.00 12.30 - 86.90 188.00 13.10 - 86.95 199.00 13.90 - 87.00 180.00 12.90 - 87.05 164.00 12.60 - 87.10 180.00 12.90 - 87.15 190.00 13.60 - 87.20 179.00 12.90 - 87.25 177.00 13.20 - 87.30 183.00 13.10 - 87.35 174.00 13.20 - 87.40 164.00 12.50 - 87.45 165.00 12.90 - 87.50 185.00 13.30 - 87.55 191.00 13.90 - 87.60 181.00 13.20 - 87.65 143.00 12.10 - 87.70 170.00 12.90 - 87.75 150.00 12.40 - 87.80 187.00 13.50 - 87.85 181.00 13.60 - 87.90 171.00 12.90 - 87.95 179.00 13.60 - 88.00 146.00 12.00 - 88.05 175.00 13.40 - 88.10 182.00 13.40 - 88.15 176.00 13.50 - 88.20 164.00 12.70 - 88.25 152.00 12.60 - 88.30 188.00 13.60 - 88.35 152.00 12.50 - 88.40 172.00 13.00 - 88.45 140.00 12.00 - 88.50 176.00 13.10 - 88.55 168.00 13.10 - 88.60 197.00 13.80 - 88.65 190.00 13.90 - 88.70 176.00 13.10 - 88.75 167.00 13.00 - 88.80 182.00 13.30 - 88.85 175.00 13.20 - 88.90 154.00 12.10 - 88.95 168.00 12.90 - 89.00 187.00 13.30 - 89.05 163.00 12.70 - 89.10 173.00 12.80 - 89.15 161.00 12.50 - 89.20 170.00 12.60 - 89.25 178.00 13.10 - 89.30 174.00 12.70 - 89.35 172.00 12.80 - 89.40 167.00 12.40 - 89.45 168.00 12.60 - 89.50 164.00 12.20 - 89.55 183.00 13.10 - 89.60 141.00 11.30 - 89.65 173.00 12.80 - 89.70 190.00 13.10 - 89.75 180.00 13.00 - 89.80 162.00 12.10 - 89.85 166.00 12.50 - 89.90 164.00 12.10 - 89.95 166.00 12.50 - 90.00 170.00 12.40 - 90.05 176.00 12.90 - 90.10 181.00 12.80 - 90.15 175.00 12.90 - 90.20 161.00 12.10 - 90.25 170.00 12.70 - 90.30 166.00 12.30 - 90.35 175.00 12.90 - 90.40 171.00 12.50 - 90.45 172.00 12.80 - 90.50 183.00 12.90 - 90.55 165.00 12.50 - 90.60 181.00 12.80 - 90.65 168.00 12.70 - 90.70 179.00 12.70 - 90.75 157.00 12.20 - 90.80 172.00 12.50 - 90.85 187.00 13.30 - 90.90 181.00 12.80 - 90.95 163.00 12.40 - 91.00 163.00 12.10 - 91.05 166.00 12.50 - 91.10 161.00 12.00 - 91.15 167.00 12.50 - 91.20 148.00 11.50 - 91.25 175.00 12.80 - 91.30 195.00 13.20 - 91.35 181.00 13.00 - 91.40 173.00 12.50 - 91.45 160.00 12.30 - 91.50 180.00 12.70 - 91.55 183.00 13.10 - 91.60 156.00 11.90 - 91.65 163.00 12.40 - 91.70 175.00 12.50 - 91.75 189.00 13.30 - 91.80 181.00 12.70 - 91.85 186.00 13.20 - 91.90 184.00 12.80 - 91.95 187.00 13.20 - 92.00 191.00 13.10 - 92.05 203.00 13.70 - 92.10 194.00 13.10 - 92.15 237.00 14.80 - 92.20 242.00 14.60 - 92.25 307.00 16.90 - 92.30 299.00 16.30 - 92.35 340.00 17.70 - 92.40 357.00 17.70 - 92.45 354.00 18.10 - 92.50 370.00 18.00 - 92.55 375.00 18.60 - 92.60 303.00 16.30 - 92.65 264.00 15.60 - 92.70 243.00 14.60 - 92.75 207.00 13.90 - 92.80 199.00 13.20 - 92.85 180.00 12.90 - 92.90 202.00 13.30 - 92.95 188.00 13.20 - 93.00 183.00 12.70 - 93.05 170.00 12.60 - 93.10 180.00 12.60 - 93.15 182.00 13.10 - 93.20 186.00 12.90 - 93.25 196.00 13.60 - 93.30 177.00 12.60 - 93.35 198.00 13.70 - 93.40 182.00 12.80 - 93.45 183.00 13.20 - 93.50 184.00 12.90 - 93.55 181.00 13.20 - 93.60 190.00 13.20 - 93.65 176.00 13.10 - 93.70 197.00 13.50 - 93.75 174.00 13.10 - 93.80 159.00 12.20 - 93.85 171.00 13.00 - 93.90 159.00 12.20 - 93.95 170.00 13.00 - 94.00 172.00 12.70 - 94.05 159.00 12.60 - 94.10 160.00 12.30 - 94.15 173.00 13.20 - 94.20 147.00 11.90 - 94.25 143.00 12.00 - 94.30 150.00 12.00 - 94.35 155.00 12.50 - 94.40 160.00 12.40 - 94.45 155.00 12.60 - 94.50 176.00 13.00 - 94.55 198.00 14.20 - 94.60 179.00 13.20 - 94.65 161.00 12.80 - 94.70 175.00 13.10 - 94.75 157.00 12.70 - 94.80 173.00 13.00 - 94.85 168.00 13.10 - 94.90 171.00 12.90 - 94.95 173.00 13.20 - 95.00 183.00 13.30 - 95.05 148.00 12.20 - 95.10 160.00 12.40 - 95.15 171.00 13.10 - 95.20 167.00 12.60 - 95.25 195.00 13.90 - 95.30 175.00 12.90 - 95.35 200.00 14.10 - 95.40 176.00 12.90 - 95.45 175.00 13.10 - 95.50 194.00 13.50 - 95.55 190.00 13.60 - 95.60 154.00 12.00 - 95.65 166.00 12.70 - 95.70 164.00 12.30 - 95.75 166.00 12.60 - 95.80 162.00 12.20 - 95.85 183.00 13.20 - 95.90 149.00 11.60 - 95.95 171.00 12.80 - 96.00 165.00 12.30 - 96.05 181.00 13.10 - 96.10 188.00 13.00 - 96.15 184.00 13.20 - 96.20 162.00 12.10 - 96.25 163.00 12.40 - 96.30 165.00 12.20 - 96.35 183.00 13.10 - 96.40 182.00 12.80 - 96.45 156.00 12.10 - 96.50 159.00 11.90 - 96.55 139.00 11.40 - 96.60 165.00 12.10 - 96.65 164.00 12.40 - 96.70 184.00 12.80 - 96.75 159.00 12.10 - 96.80 159.00 11.90 - 96.85 155.00 12.00 - 96.90 162.00 12.00 - 96.95 157.00 12.00 - 97.00 160.00 11.90 - 97.05 168.00 12.50 - 97.10 168.00 12.20 - 97.15 151.00 11.80 - 97.20 162.00 11.90 - 97.25 163.00 12.20 - 97.30 166.00 12.10 - 97.35 161.00 12.20 - 97.40 158.00 11.80 - 97.45 151.00 11.80 - 97.50 163.00 12.00 - 97.55 179.00 12.80 - 97.60 166.00 12.10 - 97.65 155.00 11.90 - 97.70 160.00 11.80 - 97.75 152.00 11.80 - 97.80 184.00 12.70 - 97.85 175.00 12.60 - 97.90 161.00 11.80 - 97.95 166.00 12.30 - 98.00 150.00 11.40 - 98.05 179.00 12.80 - 98.10 184.00 12.70 - 98.15 151.00 11.80 - 98.20 173.00 12.30 - 98.25 164.00 12.30 - 98.30 178.00 12.50 - 98.35 176.00 12.80 - 98.40 162.00 11.90 - 98.45 173.00 12.70 - 98.50 154.00 11.60 - 98.55 184.00 13.10 - 98.60 142.00 11.20 - 98.65 184.00 13.00 - 98.70 156.00 11.70 - 98.75 177.00 12.80 - 98.80 163.00 12.00 - 98.85 173.00 12.70 - 98.90 180.00 12.70 - 98.95 181.00 13.00 - 99.00 165.00 12.10 - 99.05 177.00 12.90 - 99.10 155.00 11.80 - 99.15 147.00 11.70 - 99.20 163.00 12.10 - 99.25 172.00 12.70 - 99.30 145.00 11.40 - 99.35 156.00 12.10 - 99.40 161.00 12.00 - 99.45 189.00 13.50 - 99.50 182.00 12.90 - 99.55 172.00 12.80 - 99.60 176.00 12.70 - 99.65 166.00 12.60 - 99.70 190.00 13.20 - 99.75 154.00 12.20 - 99.80 198.00 13.50 - 99.85 152.00 12.20 - 99.90 160.00 12.20 - 99.95 174.00 13.00 - 100.00 187.00 13.20 - 100.05 178.00 13.20 - 100.10 149.00 11.80 - 100.15 171.00 13.00 - 100.20 185.00 13.20 - 100.25 207.00 14.40 - 100.30 184.00 13.20 - 100.35 187.00 13.70 - 100.40 231.00 14.90 - 100.45 226.00 15.10 - 100.50 203.00 14.00 - 100.55 214.00 14.80 - 100.60 279.00 16.50 - 100.65 319.00 18.10 - 100.70 397.00 19.70 - 100.75 435.00 21.20 - 100.80 539.00 23.00 - 100.85 665.00 26.30 - 100.90 724.00 26.80 - 100.95 723.00 27.50 - 101.00 783.00 27.90 - 101.05 719.00 27.50 - 101.10 585.00 24.20 - 101.15 465.00 22.10 - 101.20 371.00 19.30 - 101.25 328.00 18.50 - 101.30 277.00 16.70 - 101.35 248.00 16.10 - 101.40 209.00 14.40 - 101.45 221.00 15.10 - 101.50 198.00 14.00 - 101.55 203.00 14.50 - 101.60 188.00 13.60 - 101.65 207.00 14.50 - 101.70 195.00 13.80 - 101.75 170.00 13.10 - 101.80 192.00 13.60 - 101.85 172.00 13.10 - 101.90 185.00 13.30 - 101.95 183.00 13.40 - 102.00 211.00 14.10 - 102.05 147.00 12.00 - 102.10 176.00 12.80 - 102.15 186.00 13.40 - 102.20 171.00 12.60 - 102.25 169.00 12.70 - 102.30 192.00 13.20 - 102.35 215.00 14.30 - 102.40 146.00 11.50 - 102.45 169.00 12.60 - 102.50 188.00 13.10 - 102.55 175.00 12.80 - 102.60 165.00 12.20 - 102.65 184.00 13.10 - 102.70 172.00 12.40 - 102.75 179.00 13.00 - 102.80 163.00 12.10 - 102.85 167.00 12.50 - 102.90 179.00 12.70 - 102.95 171.00 12.70 - 103.00 181.00 12.70 - 103.05 171.00 12.70 - 103.10 180.00 12.70 - 103.15 173.00 12.80 - 103.20 167.00 12.20 - 103.25 186.00 13.20 - 103.30 176.00 12.50 - 103.35 191.00 13.40 - 103.40 170.00 12.30 - 103.45 167.00 12.50 - 103.50 165.00 12.10 - 103.55 182.00 13.00 - 103.60 173.00 12.40 - 103.65 186.00 13.20 - 103.70 161.00 12.00 - 103.75 166.00 12.40 - 103.80 157.00 11.80 - 103.85 170.00 12.50 - 103.90 183.00 12.70 - 103.95 179.00 12.90 - 104.00 164.00 12.00 - 104.05 169.00 12.50 - 104.10 161.00 11.90 - 104.15 156.00 12.00 - 104.20 163.00 12.00 - 104.25 174.00 12.70 - 104.30 161.00 11.90 - 104.35 169.00 12.50 - 104.40 158.00 11.80 - 104.45 180.00 12.90 - 104.50 171.00 12.30 - 104.55 165.00 12.30 - 104.60 163.00 12.00 - 104.65 172.00 12.60 - 104.70 164.00 12.00 - 104.75 174.00 12.60 - 104.80 178.00 12.50 - 104.85 154.00 11.90 - 104.90 176.00 12.40 - 104.95 142.00 11.40 - 105.00 163.00 12.00 - 105.05 177.00 12.80 - 105.10 194.00 13.00 - 105.15 176.00 12.70 - 105.20 207.00 13.50 - 105.25 158.00 12.10 - 105.30 151.00 11.50 - 105.35 183.00 13.00 - 105.40 159.00 11.80 - 105.45 179.00 12.90 - 105.50 170.00 12.20 - 105.55 192.00 13.30 - 105.60 160.00 11.90 - 105.65 168.00 12.40 - 105.70 183.00 12.70 - 105.75 163.00 12.30 - 105.80 162.00 11.90 - 105.85 182.00 12.90 - 105.90 154.00 11.60 - 105.95 180.00 12.90 - 106.00 168.00 12.20 - 106.05 166.00 12.40 - 106.10 155.00 11.70 - 106.15 190.00 13.30 - 106.20 165.00 12.10 - 106.25 163.00 12.30 - 106.30 183.00 12.80 - 106.35 165.00 12.50 - 106.40 173.00 12.50 - 106.45 163.00 12.50 - 106.50 151.00 11.70 - 106.55 198.00 13.80 - 106.60 165.00 12.20 - 106.65 157.00 12.30 - 106.70 159.00 12.10 - 106.75 177.00 13.10 - 106.80 156.00 12.00 - 106.85 182.00 13.40 - 106.90 181.00 13.00 - 106.95 158.00 12.50 - 107.00 176.00 12.80 - 107.05 163.00 12.70 - 107.10 156.00 12.10 - 107.15 213.00 14.60 - 107.20 172.00 12.80 - 107.25 170.00 13.00 - 107.30 168.00 12.60 - 107.35 169.00 13.00 - 107.40 169.00 12.70 - 107.45 168.00 13.00 - 107.50 155.00 12.10 - 107.55 164.00 12.80 - 107.60 168.00 12.70 - 107.65 144.00 12.00 - 107.70 166.00 12.60 - 107.75 172.00 13.10 - 107.80 156.00 12.20 - 107.85 154.00 12.40 - 107.90 143.00 11.60 - 107.95 152.00 12.30 - 108.00 174.00 12.80 - 108.05 168.00 12.80 - 108.10 164.00 12.40 - 108.15 160.00 12.50 - 108.20 176.00 12.80 - 108.25 174.00 13.00 - 108.30 175.00 12.70 - 108.35 163.00 12.60 - 108.40 169.00 12.50 - 108.45 180.00 13.10 - 108.50 159.00 12.00 - 108.55 173.00 12.80 - 108.60 148.00 11.60 - 108.65 169.00 12.60 - 108.70 167.00 12.30 - 108.75 168.00 12.50 - 108.80 175.00 12.50 - 108.85 163.00 12.30 - 108.90 164.00 12.10 - 108.95 189.00 13.30 - 109.00 192.00 13.10 - 109.05 181.00 13.00 - 109.10 202.00 13.40 - 109.15 190.00 13.30 - 109.20 163.00 12.00 - 109.25 216.00 14.10 - 109.30 220.00 14.00 - 109.35 230.00 14.60 - 109.40 255.00 15.00 - 109.45 253.00 15.30 - 109.50 273.00 15.50 - 109.55 296.00 16.50 - 109.60 300.00 16.30 - 109.65 331.00 17.50 - 109.70 347.00 17.50 - 109.75 349.00 18.00 - 109.80 341.00 17.40 - 109.85 332.00 17.50 - 109.90 298.00 16.20 - 109.95 259.00 15.50 - 110.00 227.00 14.10 - 110.05 203.00 13.70 - 110.10 222.00 14.00 - 110.15 175.00 12.70 - 110.20 183.00 12.70 - 110.25 197.00 13.50 - 110.30 176.00 12.40 - 110.35 179.00 12.90 - 110.40 176.00 12.50 - 110.45 178.00 12.80 - 110.50 210.00 13.60 - 110.55 181.00 13.00 - 110.60 167.00 12.20 - 110.65 165.00 12.40 - 110.70 172.00 12.30 - 110.75 175.00 12.80 - 110.80 177.00 12.50 - 110.85 194.00 13.40 - 110.90 171.00 12.30 - 110.95 177.00 12.80 - 111.00 188.00 12.90 - 111.05 175.00 12.80 - 111.10 194.00 13.10 - 111.15 179.00 12.90 - 111.20 171.00 12.30 - 111.25 165.00 12.40 - 111.30 183.00 12.70 - 111.35 184.00 13.00 - 111.40 187.00 12.90 - 111.45 178.00 12.80 - 111.50 172.00 12.30 - 111.55 179.00 12.90 - 111.60 205.00 13.40 - 111.65 168.00 12.50 - 111.70 161.00 11.90 - 111.75 182.00 13.00 - 111.80 167.00 12.20 - 111.85 193.00 13.40 - 111.90 188.00 12.90 - 111.95 204.00 13.80 - 112.00 179.00 12.60 - 112.05 176.00 12.80 - 112.10 185.00 12.80 - 112.15 174.00 12.70 - 112.20 175.00 12.50 - 112.25 198.00 13.60 - 112.30 199.00 13.30 - 112.35 207.00 13.90 - 112.40 204.00 13.50 - 112.45 180.00 13.00 - 112.50 137.00 11.10 - 112.55 179.00 13.00 - 112.60 183.00 12.80 - 112.65 166.00 12.60 - 112.70 166.00 12.30 - 112.75 189.00 13.40 - 112.80 181.00 12.80 - 112.85 194.00 13.60 - 112.90 171.00 12.50 - 112.95 202.00 13.90 - 113.00 216.00 14.10 - 113.05 198.00 14.00 - 113.10 189.00 13.30 - 113.15 170.00 13.00 - 113.20 182.00 13.10 - 113.25 195.00 14.00 - 113.30 177.00 13.00 - 113.35 180.00 13.50 - 113.40 195.00 13.70 - 113.45 201.00 14.30 - 113.50 203.00 14.00 - 113.55 200.00 14.30 - 113.60 209.00 14.20 - 113.65 231.00 15.40 - 113.70 281.00 16.60 - 113.75 287.00 17.20 - 113.80 324.00 17.80 - 113.85 395.00 20.20 - 113.90 457.00 21.20 - 113.95 580.00 24.40 - 114.00 685.00 26.00 - 114.05 873.00 30.00 - 114.10 964.00 30.80 - 114.15 1126.00 34.00 - 114.20 1266.00 35.20 - 114.25 1307.00 36.50 - 114.30 1221.00 34.50 - 114.35 1096.00 33.30 - 114.40 978.00 30.70 - 114.45 792.00 28.20 - 114.50 600.00 24.00 - 114.55 487.00 22.00 - 114.60 358.00 18.50 - 114.65 279.00 16.60 - 114.70 265.00 15.80 - 114.75 258.00 15.90 - 114.80 244.00 15.10 - 114.85 226.00 14.80 - 114.90 227.00 14.50 - 114.95 188.00 13.50 - 115.00 195.00 13.40 - 115.05 211.00 14.20 - 115.10 205.00 13.70 - 115.15 198.00 13.70 - 115.20 218.00 14.00 - 115.25 200.00 13.70 - 115.30 200.00 13.40 - 115.35 188.00 13.30 - 115.40 209.00 13.70 - 115.45 184.00 13.10 - 115.50 186.00 12.90 - 115.55 202.00 13.70 - 115.60 183.00 12.70 - 115.65 187.00 13.10 - 115.70 182.00 12.60 - 115.75 185.00 13.10 - 115.80 213.00 13.70 - 115.85 177.00 12.80 - 115.90 199.00 13.20 - 115.95 185.00 13.00 - 116.00 184.00 12.70 - 116.05 191.00 13.30 - 116.10 173.00 12.30 - 116.15 196.00 13.50 - 116.20 201.00 13.30 - 116.25 173.00 12.70 - 116.30 178.00 12.60 - 116.35 161.00 12.30 - 116.40 208.00 13.60 - 116.45 183.00 13.10 - 116.50 183.00 12.80 - 116.55 173.00 12.80 - 116.60 184.00 12.80 - 116.65 215.00 14.20 - 116.70 201.00 13.40 - 116.75 193.00 13.40 - 116.80 190.00 13.00 - 116.85 216.00 14.20 - 116.90 195.00 13.10 - 116.95 203.00 13.80 - 117.00 183.00 12.80 - 117.05 203.00 13.70 - 117.10 187.00 12.90 - 117.15 216.00 14.20 - 117.20 191.00 13.00 - 117.25 189.00 13.30 - 117.30 189.00 13.00 - 117.35 226.00 14.50 - 117.40 185.00 12.90 - 117.45 194.00 13.50 - 117.50 185.00 12.80 - 117.55 213.00 14.10 - 117.60 197.00 13.30 - 117.65 198.00 14.50 - 117.70 168.00 13.00 - 117.75 209.00 14.90 - 117.80 185.00 13.70 - 117.85 208.00 14.90 - 117.90 213.00 14.70 - 117.95 203.00 14.70 - 118.00 225.00 15.10 - 118.05 214.00 15.10 - 118.10 233.00 15.40 - 118.15 245.00 16.20 - 118.20 236.00 15.50 - 118.25 245.00 16.20 - 118.30 305.00 17.60 - 118.35 287.00 17.10 - 118.40 317.00 17.40 - 118.45 421.00 20.60 - 118.50 422.00 20.10 - 118.55 590.00 24.40 - 118.60 701.00 26.80 - 118.65 861.00 28.60 - 118.70 1054.00 31.00 - 118.75 1232.00 34.30 - 118.80 1483.00 36.80 - 118.85 1694.00 40.30 - 118.90 1819.00 40.80 - 118.95 1845.00 42.30 - 119.00 1866.00 41.50 - 119.05 1726.00 41.00 - 119.10 1492.00 37.20 - 119.15 1232.00 34.80 - 119.20 971.00 30.10 - 119.25 753.00 27.20 - 119.30 626.00 24.20 - 119.35 487.00 21.90 - 119.40 409.00 19.60 - 119.45 342.00 18.50 - 119.50 307.00 17.10 - 119.55 296.00 17.20 - 119.60 231.00 14.90 - 119.65 246.00 15.80 - 119.70 220.00 14.50 - 119.75 255.00 16.10 - 119.80 214.00 14.40 - 119.85 247.00 15.90 - 119.90 238.00 15.20 - 119.95 218.00 15.00 - 120.00 222.00 14.70 - 120.05 218.00 15.00 - 120.10 253.00 15.80 - 120.15 197.00 14.30 - 120.20 190.00 13.60 - 120.25 221.00 15.10 - 120.30 204.00 14.20 - 120.35 206.00 14.60 - 120.40 189.00 13.60 - 120.45 231.00 15.40 - 120.50 190.00 13.60 - 120.55 191.00 13.90 - 120.60 211.00 14.30 - 120.65 204.00 14.30 - 120.70 200.00 13.90 - 120.75 199.00 14.10 - 120.80 190.00 13.50 - 120.85 195.00 13.90 - 120.90 179.00 13.00 - 120.95 189.00 13.60 - 121.00 190.00 13.30 - 121.05 195.00 13.80 - 121.10 193.00 13.40 - 121.15 173.00 12.80 - 121.20 183.00 13.00 - 121.25 181.00 13.10 - 121.30 203.00 13.50 - 121.35 177.00 12.90 - 121.40 201.00 13.40 - 121.45 179.00 12.90 - 121.50 179.00 12.60 - 121.55 194.00 13.40 - 121.60 158.00 11.90 - 121.65 195.00 13.40 - 121.70 201.00 13.40 - 121.75 192.00 13.40 - 121.80 189.00 13.00 - 121.85 186.00 13.10 - 121.90 170.00 12.30 - 121.95 166.00 12.40 - 122.00 185.00 12.80 - 122.05 197.00 13.60 - 122.10 177.00 12.60 - 122.15 198.00 13.60 - 122.20 174.00 12.50 - 122.25 171.00 12.60 - 122.30 190.00 13.00 - 122.35 214.00 14.20 - 122.40 189.00 13.00 - 122.45 174.00 12.80 - 122.50 171.00 12.40 - 122.55 163.00 12.40 - 122.60 174.00 12.40 - 122.65 177.00 12.80 - 122.70 180.00 12.60 - 122.75 186.00 13.10 - 122.80 190.00 13.00 - 122.85 170.00 12.60 - 122.90 175.00 12.50 - 122.95 194.00 13.40 - 123.00 175.00 12.50 - 123.05 194.00 13.40 - 123.10 189.00 12.90 - 123.15 222.00 14.30 - 123.20 178.00 12.50 - 123.25 158.00 12.10 - 123.30 191.00 13.00 - 123.35 184.00 13.00 - 123.40 190.00 12.90 - 123.45 183.00 13.00 - 123.50 178.00 12.50 - 123.55 204.00 13.70 - 123.60 192.00 13.00 - 123.65 200.00 13.50 - 123.70 182.00 12.60 - 123.75 171.00 12.50 - 123.80 186.00 12.70 - 123.85 197.00 13.40 - 123.90 174.00 12.30 - 123.95 167.00 12.30 - 124.00 178.00 12.40 - 124.05 198.00 13.40 - 124.10 205.00 13.30 - 124.15 216.00 14.00 - 124.20 200.00 13.20 - 124.25 204.00 13.60 - 124.30 190.00 12.80 - 124.35 188.00 13.10 - 124.40 191.00 12.90 - 124.45 186.00 13.00 - 124.50 175.00 12.30 - 124.55 175.00 12.60 - 124.60 174.00 12.30 - 124.65 194.00 13.30 - 124.70 181.00 12.50 - 124.75 161.00 12.10 - 124.80 186.00 12.70 - 124.85 200.00 13.50 - 124.90 168.00 12.10 - 124.95 177.00 12.70 - 125.00 188.00 12.80 - 125.05 177.00 12.70 - 125.10 163.00 11.90 - 125.15 175.00 12.70 - 125.20 188.00 12.80 - 125.25 176.00 12.80 - 125.30 172.00 12.30 - 125.35 172.00 12.60 - 125.40 181.00 12.70 - 125.45 186.00 13.20 - 125.50 181.00 12.70 - 125.55 193.00 13.40 - 125.60 177.00 12.60 - 125.65 176.00 12.90 - 125.70 194.00 13.20 - 125.75 179.00 13.00 - 125.80 147.00 11.50 - 125.85 186.00 13.30 - 125.90 182.00 12.90 - 125.95 165.00 12.70 - 126.00 164.00 12.30 - 126.05 199.00 13.90 - 126.10 167.00 12.40 - 126.15 184.00 13.40 - 126.20 203.00 13.80 - 126.25 190.00 13.70 - 126.30 182.00 13.10 - 126.35 180.00 13.40 - 126.40 179.00 13.00 - 126.45 179.00 13.40 - 126.50 170.00 12.70 - 126.55 176.00 13.30 - 126.60 178.00 13.10 - 126.65 185.00 13.70 - 126.70 193.00 13.60 - 126.75 192.00 14.00 - 126.80 198.00 13.80 - 126.85 195.00 14.00 - 126.90 165.00 12.60 - 126.95 189.00 13.80 - 127.00 175.00 13.00 - 127.05 176.00 13.30 - 127.10 184.00 13.30 - 127.15 179.00 13.40 - 127.20 187.00 13.40 - 127.25 176.00 13.20 - 127.30 191.00 13.50 - 127.35 194.00 13.90 - 127.40 177.00 12.90 - 127.45 177.00 13.20 - 127.50 180.00 13.00 - 127.55 158.00 12.40 - 127.60 193.00 13.40 - 127.65 177.00 13.10 - 127.70 185.00 13.10 - 127.75 178.00 13.10 - 127.80 184.00 13.00 - 127.85 188.00 13.40 - 127.90 182.00 12.90 - 127.95 190.00 13.50 - 128.00 191.00 13.20 - 128.05 165.00 12.50 - 128.10 174.00 12.50 - 128.15 158.00 12.20 - 128.20 197.00 13.30 - 128.25 183.00 13.10 - 128.30 196.00 13.30 - 128.35 166.00 12.50 - 128.40 218.00 14.00 - 128.45 206.00 13.80 - 128.50 184.00 12.80 - 128.55 176.00 12.70 - 128.60 198.00 13.20 - 128.65 215.00 14.10 - 128.70 179.00 12.60 - 128.75 192.00 13.30 - 128.80 201.00 13.30 - 128.85 221.00 14.20 - 128.90 227.00 14.10 - 128.95 229.00 14.40 - 129.00 254.00 14.90 - 129.05 256.00 15.30 - 129.10 272.00 15.40 - 129.15 239.00 14.80 - 129.20 228.00 14.10 - 129.25 255.00 15.20 - 129.30 213.00 13.60 - 129.35 203.00 13.60 - 129.40 228.00 14.10 - 129.45 220.00 14.10 - 129.50 185.00 12.60 - 129.55 192.00 13.20 - 129.60 187.00 12.70 - 129.65 182.00 12.80 - 129.70 209.00 13.40 - 129.75 173.00 12.50 - 129.80 202.00 13.20 - 129.85 178.00 12.70 - 129.90 189.00 12.80 - 129.95 177.00 12.60 - 130.00 177.00 12.30 - 130.05 190.00 13.10 - 130.10 178.00 12.40 - 130.15 177.00 12.60 - 130.20 164.00 11.90 - 130.25 185.00 12.90 - 130.30 153.00 11.40 - 130.35 174.00 12.50 - 130.40 197.00 13.00 - 130.45 192.00 13.10 - 130.50 174.00 12.20 - 130.55 177.00 12.60 - 130.60 172.00 12.10 - 130.65 173.00 12.50 - 130.70 178.00 12.40 - 130.75 180.00 12.80 - 130.80 203.00 13.20 - 130.85 192.00 13.20 - 130.90 184.00 12.60 - 130.95 197.00 13.30 - 131.00 169.00 12.10 - 131.05 187.00 13.00 - 131.10 175.00 12.30 - 131.15 177.00 12.60 - 131.20 199.00 13.10 - 131.25 180.00 12.80 - 131.30 203.00 13.20 - 131.35 175.00 12.60 - 131.40 183.00 12.50 - 131.45 192.00 13.20 - 131.50 174.00 12.30 - 131.55 180.00 12.80 - 131.60 179.00 12.50 - 131.65 191.00 13.20 - 131.70 182.00 12.60 - 131.75 174.00 12.60 - 131.80 191.00 12.90 - 131.85 195.00 13.40 - 131.90 171.00 12.30 - 131.95 198.00 13.60 - 132.00 193.00 13.10 - 132.05 175.00 12.80 - 132.10 207.00 13.60 - 132.15 189.00 13.40 - 132.20 174.00 12.50 - 132.25 196.00 13.70 - 132.30 175.00 12.60 - 132.35 196.00 13.80 - 132.40 183.00 13.00 - 132.45 198.00 13.80 - 132.50 196.00 13.40 - 132.55 169.00 12.90 - 132.60 189.00 13.30 - 132.65 171.00 13.00 - 132.70 193.00 13.50 - 132.75 170.00 13.00 - 132.80 175.00 12.90 - 132.85 166.00 12.90 - 132.90 188.00 13.40 - 132.95 186.00 13.70 - 133.00 165.00 12.60 - 133.05 201.00 14.20 - 133.10 182.00 13.20 - 133.15 151.00 12.40 - 133.20 156.00 12.20 - 133.25 187.00 13.70 - 133.30 153.00 12.10 - 133.35 193.00 14.00 - 133.40 200.00 13.90 - 133.45 165.00 12.90 - 133.50 172.00 12.90 - 133.55 162.00 12.70 - 133.60 165.00 12.50 - 133.65 218.00 14.70 - 133.70 197.00 13.60 - 133.75 206.00 14.20 - 133.80 186.00 13.20 - 133.85 162.00 12.50 - 133.90 176.00 12.80 - 133.95 174.00 12.90 - 134.00 196.00 13.40 - 134.05 174.00 12.90 - 134.10 177.00 12.70 - 134.15 183.00 13.10 - 134.20 184.00 12.90 - 134.25 185.00 13.10 - 134.30 200.00 13.40 - 134.35 175.00 12.70 - 134.40 190.00 13.00 - 134.45 195.00 13.40 - 134.50 192.00 13.00 - 134.55 171.00 12.50 - 134.60 194.00 13.00 - 134.65 190.00 13.10 - 134.70 165.00 12.00 - 134.75 192.00 13.20 - 134.80 160.00 11.70 - 134.85 192.00 13.10 - 134.90 181.00 12.50 - 134.95 208.00 13.70 - 135.00 179.00 12.40 - 135.05 172.00 12.40 - 135.10 183.00 12.50 - 135.15 187.00 12.90 - 135.20 185.00 12.50 - 135.25 182.00 12.70 - 135.30 184.00 12.50 - 135.35 163.00 11.90 - 135.40 201.00 13.00 - 135.45 189.00 12.80 - 135.50 204.00 13.10 - 135.55 178.00 12.50 - 135.60 178.00 12.20 - 135.65 193.00 13.00 - 135.70 215.00 13.40 - 135.75 203.00 13.30 - 135.80 216.00 13.40 - 135.85 165.00 12.10 - 135.90 196.00 12.80 - 135.95 178.00 12.50 - 136.00 170.00 11.90 - 136.05 173.00 12.40 - 136.10 188.00 12.60 - 136.15 176.00 12.50 - 136.20 186.00 12.50 - 136.25 189.00 12.90 - 136.30 166.00 11.80 - 136.35 177.00 12.50 - 136.40 169.00 11.90 - 136.45 171.00 12.30 - 136.50 194.00 12.80 - 136.55 187.00 12.90 - 136.60 162.00 11.70 - 136.65 160.00 11.90 - 136.70 183.00 12.40 - 136.75 150.00 11.50 - 136.80 180.00 12.40 - 136.85 194.00 13.20 - 136.90 185.00 12.60 - 136.95 158.00 11.90 - 137.00 193.00 12.90 - 137.05 165.00 12.20 - 137.10 178.00 12.30 - 137.15 183.00 12.90 - 137.20 180.00 12.40 - 137.25 176.00 12.70 - 137.30 183.00 12.60 - 137.35 189.00 13.20 - 137.40 180.00 12.50 - 137.45 160.00 12.20 - 137.50 202.00 13.30 - 137.55 201.00 13.60 - 137.60 173.00 12.30 - 137.65 176.00 12.80 - 137.70 195.00 13.10 - 137.75 197.00 13.50 - 137.80 186.00 12.80 - 137.85 183.00 13.00 - 137.90 175.00 12.40 - 137.95 178.00 12.80 - 138.00 190.00 12.90 - 138.05 174.00 12.70 - 138.10 163.00 12.00 - 138.15 190.00 13.30 - 138.20 169.00 12.20 - 138.25 198.00 13.60 - 138.30 199.00 13.30 - 138.35 184.00 13.10 - 138.40 216.00 13.90 - 138.45 183.00 13.10 - 138.50 200.00 13.40 - 138.55 186.00 13.30 - 138.60 177.00 12.70 - 138.65 186.00 13.40 - 138.70 193.00 13.30 - 138.75 200.00 14.00 - 138.80 180.00 12.90 - 138.85 178.00 13.20 - 138.90 198.00 13.60 - 138.95 236.00 15.30 - 139.00 203.00 13.80 - 139.05 207.00 14.30 - 139.10 190.00 13.40 - 139.15 171.00 13.10 - 139.20 203.00 13.90 - 139.25 203.00 14.20 - 139.30 198.00 13.70 - 139.35 200.00 14.20 - 139.40 187.00 13.30 - 139.45 214.00 14.70 - 139.50 198.00 13.70 - 139.55 220.00 14.80 - 139.60 196.00 13.70 - 139.65 239.00 15.50 - 139.70 212.00 14.20 - 139.75 219.00 14.80 - 139.80 248.00 15.40 - 139.85 220.00 14.80 - 139.90 241.00 15.10 - 139.95 245.00 15.50 - 140.00 269.00 15.90 - 140.05 294.00 17.00 - 140.10 323.00 17.40 - 140.15 302.00 17.20 - 140.20 312.00 17.10 - 140.25 371.00 18.90 - 140.30 420.00 19.70 - 140.35 516.00 22.30 - 140.40 596.00 23.40 - 140.45 644.00 24.70 - 140.50 711.00 25.40 - 140.55 833.00 28.10 - 140.60 895.00 28.40 - 140.65 1010.00 30.70 - 140.70 1058.00 30.80 - 140.75 1183.00 33.10 - 140.80 1278.00 33.70 - 140.85 1298.00 34.60 - 140.90 1419.00 35.40 - 140.95 1381.00 35.60 - 141.00 1299.00 33.80 - 141.05 1371.00 35.40 - 141.10 1273.00 33.30 - 141.15 1131.00 32.10 - 141.20 992.00 29.40 - 141.25 918.00 28.90 - 141.30 832.00 26.90 - 141.35 655.00 24.50 - 141.40 629.00 23.50 - 141.45 522.00 21.90 - 141.50 472.00 20.30 - 141.55 409.00 19.30 - 141.60 371.00 18.00 - 141.65 325.00 17.30 - 141.70 306.00 16.30 - 141.75 270.00 15.70 - 141.80 238.00 14.40 - 141.85 231.00 14.50 - 141.90 232.00 14.20 - 141.95 223.00 14.30 - 142.00 221.00 13.90 - 142.05 244.00 14.90 - 142.10 228.00 14.10 - 142.15 212.00 13.90 - 142.20 226.00 14.00 - 142.25 197.00 13.40 - 142.30 204.00 13.30 - 142.35 189.00 13.10 - 142.40 201.00 13.20 - 142.45 226.00 14.30 - 142.50 210.00 13.50 - 142.55 213.00 13.90 - 142.60 202.00 13.30 - 142.65 206.00 13.70 - 142.70 189.00 12.80 - 142.75 213.00 13.90 - 142.80 193.00 12.90 - 142.85 206.00 13.70 - 142.90 204.00 13.30 - 142.95 188.00 13.10 - 143.00 221.00 13.80 - 143.05 203.00 13.60 - 143.10 192.00 12.90 - 143.15 197.00 13.40 - 143.20 187.00 12.70 - 143.25 206.00 13.70 - 143.30 197.00 13.10 - 143.35 182.00 12.80 - 143.40 186.00 12.70 - 143.45 228.00 14.40 - 143.50 201.00 13.20 - 143.55 176.00 12.60 - 143.60 193.00 12.90 - 143.65 200.00 13.50 - 143.70 189.00 12.80 - 143.75 198.00 13.40 - 143.80 188.00 12.80 - 143.85 169.00 12.40 - 143.90 183.00 12.60 - 143.95 198.00 13.40 - 144.00 156.00 11.60 - 144.05 172.00 12.50 - 144.10 190.00 12.80 - 144.15 166.00 12.30 - 144.20 163.00 11.90 - 144.25 184.00 13.00 - 144.30 182.00 12.60 - 144.35 173.00 12.60 - 144.40 182.00 12.60 - 144.45 183.00 13.00 - 144.50 186.00 12.80 - 144.55 195.00 13.40 - 144.60 204.00 13.40 - 144.65 179.00 13.00 - 144.70 192.00 13.10 - 144.75 213.00 14.10 - 144.80 187.00 12.90 - 144.85 194.00 13.50 - 144.90 185.00 12.90 - 144.95 183.00 13.20 - 145.00 192.00 13.20 - 145.05 201.00 13.90 - 145.10 211.00 13.90 - 145.15 163.00 12.50 - 145.20 202.00 13.60 - 145.25 197.00 13.80 - 145.30 183.00 13.00 - 145.35 177.00 13.20 - 145.40 188.00 13.20 - 145.45 158.00 12.50 - 145.50 184.00 13.20 - 145.55 162.00 12.70 - 145.60 169.00 12.70 - 145.65 171.00 13.10 - 145.70 188.00 13.40 - 145.75 167.00 13.00 - 145.80 182.00 13.20 - 145.85 197.00 14.10 - 145.90 179.00 13.10 - 145.95 172.00 13.20 - 146.00 163.00 12.50 - 146.05 172.00 13.10 - 146.10 178.00 13.00 - 146.15 179.00 13.40 - 146.20 171.00 12.80 - 146.25 189.00 13.70 - 146.30 190.00 13.40 - 146.35 185.00 13.50 - 146.40 169.00 12.60 - 146.45 165.00 12.70 - 146.50 185.00 13.10 - 146.55 158.00 12.40 - 146.60 190.00 13.30 - 146.65 165.00 12.60 - 146.70 173.00 12.60 - 146.75 206.00 14.10 - 146.80 170.00 12.50 - 146.85 193.00 13.60 - 146.90 167.00 12.30 - 146.95 182.00 13.10 - 147.00 191.00 13.20 - 147.05 175.00 12.90 - 147.10 184.00 12.90 - 147.15 163.00 12.40 - 147.20 174.00 12.50 - 147.25 176.00 12.90 - 147.30 163.00 12.10 - 147.35 174.00 12.80 - 147.40 155.00 11.80 - 147.45 153.00 12.00 - 147.50 190.00 13.00 - 147.55 190.00 13.30 - 147.60 169.00 12.30 - 147.65 189.00 13.30 - 147.70 177.00 12.60 - 147.75 167.00 12.50 - 147.80 163.00 12.00 - 147.85 196.00 13.50 - 147.90 175.00 12.50 - 147.95 146.00 11.60 - 148.00 170.00 12.20 - 148.05 179.00 12.90 - 148.10 182.00 12.60 - 148.15 175.00 12.70 - 148.20 171.00 12.30 - 148.25 201.00 13.60 - 148.30 181.00 12.60 - 148.35 152.00 11.80 - 148.40 194.00 13.00 - 148.45 160.00 12.20 - 148.50 179.00 12.50 - 148.55 181.00 12.90 - 148.60 175.00 12.40 - 148.65 178.00 12.80 - 148.70 186.00 12.80 - 148.75 195.00 13.40 - 148.80 166.00 12.00 - 148.85 184.00 13.00 - 148.90 215.00 13.70 - 148.95 183.00 12.90 - 149.00 184.00 12.60 - 149.05 174.00 12.60 - 149.10 175.00 12.30 - 149.15 171.00 12.50 - 149.20 166.00 12.00 - 149.25 188.00 13.00 - 149.30 165.00 11.90 - 149.35 184.00 12.90 - 149.40 181.00 12.60 - 149.45 174.00 12.60 - 149.50 178.00 12.40 - 149.55 191.00 13.20 - 149.60 181.00 12.50 - 149.65 174.00 12.60 - 149.70 180.00 12.50 - 149.75 177.00 12.70 - 149.80 164.00 11.90 - 149.85 203.00 13.60 - 149.90 178.00 12.40 - 149.95 162.00 12.20 - 150.00 192.00 12.90 - 150.05 164.00 12.20 - 150.10 151.00 11.40 - 150.15 170.00 12.50 - 150.20 166.00 12.00 - 150.25 194.00 13.30 - 150.30 168.00 12.10 - 150.35 173.00 12.50 - 150.40 175.00 12.30 - 150.45 193.00 13.30 - 150.50 177.00 12.40 - 150.55 185.00 13.00 - 150.60 178.00 12.40 - 150.65 178.00 12.70 - 150.70 179.00 12.50 - 150.75 180.00 12.90 - 150.80 169.00 12.20 - 150.85 177.00 12.80 - 150.90 159.00 11.80 - 150.95 167.00 12.40 - 151.00 180.00 12.60 - 151.05 158.00 12.20 - 151.10 173.00 12.40 - 151.15 172.00 12.70 - 151.20 163.00 12.10 - 151.25 168.00 12.60 - 151.30 166.00 12.20 - 151.35 179.00 13.00 - 151.40 159.00 12.00 - 151.45 173.00 12.90 - 151.50 170.00 12.40 - 151.55 151.00 12.10 - 151.60 174.00 12.60 - 151.65 182.00 13.20 - 151.70 182.00 12.90 - 151.75 172.00 12.90 - 151.80 157.00 12.00 - 151.85 156.00 12.30 - 151.90 168.00 12.50 - 151.95 194.00 13.80 - 152.00 177.00 12.80 - 152.05 170.00 12.90 - 152.10 169.00 12.60 - 152.15 173.00 13.00 - 152.20 161.00 12.30 - 152.25 169.00 12.90 - 152.30 167.00 12.50 - 152.35 194.00 13.80 - 152.40 150.00 11.90 - 152.45 159.00 12.50 - 152.50 181.00 13.10 - 152.55 180.00 13.30 - 152.60 193.00 13.40 - 152.65 192.00 13.70 - 152.70 152.00 11.90 - 152.75 159.00 12.50 - 152.80 147.00 11.70 - 152.85 190.00 13.60 - 152.90 167.00 12.40 - 152.95 193.00 13.60 - 153.00 159.00 12.10 - 153.05 195.00 13.60 - 153.10 172.00 12.50 - 153.15 148.00 11.90 - 153.20 174.00 12.50 - 153.25 194.00 13.50 - 153.30 159.00 11.90 - 153.35 190.00 13.30 - 153.40 181.00 12.70 - 153.45 159.00 12.10 - 153.50 168.00 12.20 - 153.55 175.00 12.70 - 153.60 184.00 12.70 - 153.65 200.00 13.50 - 153.70 161.00 11.90 - 153.75 162.00 12.10 - 153.80 152.00 11.50 - 153.85 177.00 12.70 - 153.90 173.00 12.20 - 153.95 184.00 12.90 - 154.00 169.00 12.10 - 154.05 163.00 12.10 - 154.10 177.00 12.40 - 154.15 171.00 12.50 - 154.20 180.00 12.50 - 154.25 201.00 13.40 - 154.30 206.00 13.30 - 154.35 181.00 12.70 - 154.40 170.00 12.00 - 154.45 177.00 12.60 - 154.50 196.00 12.90 - 154.55 201.00 13.40 - 154.60 161.00 11.70 - 154.65 179.00 12.60 - 154.70 185.00 12.50 - 154.75 167.00 12.10 - 154.80 162.00 11.70 - 154.85 178.00 12.60 - 154.90 203.00 13.10 - 154.95 193.00 13.10 - 155.00 164.00 11.70 - 155.05 191.00 13.00 - 155.10 173.00 12.10 - 155.15 165.00 12.00 - 155.20 178.00 12.20 - 155.25 196.00 13.20 - 155.30 188.00 12.50 - 155.35 183.00 12.70 - 155.40 188.00 12.60 - 155.45 166.00 12.10 - 155.50 189.00 12.60 - 155.55 175.00 12.40 - 155.60 173.00 12.00 - 155.65 201.00 13.30 - 155.70 177.00 12.20 - 155.75 202.00 13.30 - 155.80 169.00 11.90 - 155.85 198.00 13.20 - 155.90 191.00 12.70 - 155.95 207.00 13.50 - 156.00 226.00 13.80 - 156.05 184.00 12.80 - 156.10 218.00 13.50 - 156.15 215.00 13.80 - 156.20 239.00 14.20 - 156.25 292.00 16.10 - 156.30 251.00 14.60 - 156.35 255.00 15.10 - 156.40 244.00 14.40 - 156.45 259.00 15.20 - 156.50 260.00 14.90 - 156.55 294.00 16.30 - 156.60 303.00 16.10 - 156.65 282.00 15.90 - 156.70 312.00 16.40 - 156.75 317.00 16.90 - 156.80 342.00 17.20 - 156.85 338.00 17.50 - 156.90 351.00 17.40 - 156.95 359.00 18.10 - 157.00 394.00 18.50 - 157.05 316.00 17.00 - 157.10 379.00 18.20 - 157.15 359.00 18.20 - 157.20 404.00 18.80 - 157.25 381.00 18.80 - 157.30 359.00 17.80 - 157.35 364.00 18.40 - 157.40 347.00 17.60 - 157.45 328.00 17.50 - 157.50 344.00 17.50 - 157.55 320.00 17.40 - 157.60 333.00 17.40 - 157.65 319.00 17.50 - 157.70 289.00 16.30 - 157.75 284.00 16.60 - 157.80 283.00 16.20 - 157.85 305.00 17.20 - 157.90 281.00 16.20 - 157.95 244.00 15.60 - 158.00 253.00 15.40 - 158.05 245.00 15.60 - 158.10 210.00 14.10 - 158.15 201.00 14.20 - 158.20 226.00 14.70 - 158.25 206.00 14.40 - 158.30 218.00 14.40 - 158.35 201.00 14.30 - 158.40 226.00 14.70 - 158.45 201.00 14.20 - 158.50 210.00 14.20 - 158.55 207.00 14.40 - 158.60 176.00 13.00 - 158.65 172.00 13.10 - 158.70 173.00 12.90 - 158.75 195.00 13.90 - 158.80 168.00 12.70 - 158.85 177.00 13.30 - 158.90 186.00 13.30 - 158.95 170.00 13.00 - 159.00 190.00 13.40 - 159.05 175.00 13.10 - 159.10 191.00 13.40 - 159.15 164.00 12.70 - 159.20 189.00 13.30 - 159.25 176.00 13.10 - 159.30 175.00 12.80 - 159.35 162.00 12.50 - 159.40 184.00 13.00 - 159.45 163.00 12.50 - 159.50 179.00 12.80 - 159.55 194.00 13.60 - 159.60 165.00 12.20 - 159.65 180.00 13.00 - 159.70 174.00 12.60 - 159.75 180.00 13.00 - 159.80 179.00 12.60 - 159.85 189.00 13.30 - 159.90 185.00 12.90 - 159.95 151.00 11.80 - 160.00 176.00 12.50 - 160.05 165.00 12.30 - 160.10 163.00 12.00 - 160.15 184.00 13.00 - 160.20 157.00 11.70 - 160.25 166.00 12.30 - 160.30 160.00 11.80 - 160.35 183.00 12.90 - 160.40 167.00 12.10 - 160.45 180.00 12.80 - 160.50 183.00 12.60 - 160.55 163.00 12.20 - 160.60 178.00 12.40 - 160.65 179.00 12.80 - 160.70 161.00 11.80 - 160.75 168.00 12.40 - 160.80 173.00 12.30 - 160.85 202.00 13.60 - 160.90 145.00 11.30 - 160.95 162.00 12.20 - 161.00 180.00 12.50 - 161.05 186.00 13.10 - 161.10 166.00 12.10 - 161.15 177.00 12.70 - 161.20 194.00 13.10 - 161.25 177.00 12.80 - 161.30 178.00 12.50 - 161.35 190.00 13.20 - 161.40 160.00 11.90 - 161.45 173.00 12.60 - 161.50 191.00 12.90 - 161.55 161.00 12.20 - 161.60 181.00 12.60 - 161.65 152.00 11.80 - 161.70 195.00 13.00 - 161.75 171.00 12.50 - 161.80 188.00 12.80 - 161.85 164.00 12.20 - 161.90 185.00 12.70 - 161.95 173.00 12.60 - 162.00 162.00 11.90 - 162.05 166.00 12.30 - 162.10 201.00 13.20 - 162.15 173.00 12.60 - 162.20 172.00 12.20 - 162.25 181.00 12.80 - 162.30 159.00 11.70 - 162.35 185.00 13.00 - 162.40 170.00 12.10 - 162.45 200.00 13.50 - 162.50 196.00 13.00 - 162.55 176.00 12.60 - 162.60 197.00 13.00 - 162.65 176.00 12.60 - 162.70 181.00 12.50 - 162.75 176.00 12.60 - 162.80 184.00 12.60 - 162.85 179.00 12.70 - 162.90 165.00 11.90 - 162.95 146.00 11.50 - 163.00 165.00 11.90 - 163.05 151.00 11.70 - 163.10 164.00 11.90 - 163.15 179.00 12.80 - 163.20 186.00 12.70 - 163.25 182.00 13.00 - 163.30 168.00 12.20 - 163.35 193.00 13.50 - 163.40 177.00 12.60 - 163.45 180.00 13.10 - 163.50 171.00 12.40 - 163.55 207.00 14.10 - 163.60 180.00 12.90 - 163.65 159.00 12.40 - 163.70 165.00 12.40 - 163.75 178.00 13.20 - 163.80 150.00 11.80 - 163.85 177.00 13.20 - 163.90 174.00 12.80 - 163.95 180.00 13.40 - 164.00 184.00 13.20 - 164.05 166.00 13.60 - 164.10 182.00 13.90 - 164.15 188.00 15.60 - 164.20 186.00 15.00 - 164.25 152.00 15.20 - 164.30 200.00 16.90 - 164.35 177.00 18.00 - 164.40 202.00 18.50 - 164.45 178.00 20.40 - 164.50 153.00 18.00 - 164.55 197.00 25.30 - 164.60 153.00 20.70 - 164.65 173.00 30.10 - 164.70 187.00 27.90 - 164.75 175.00 38.20 - 164.80 168.00 30.90 - 164.85 109.00 41.20 From 742589ac82c8e628b944aa2a9ccb30c990ef2585 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Mon, 30 Mar 2026 16:03:42 +0200 Subject: [PATCH 4/5] Improve joint-fit support and add parameter-evolution plotting (#130) * Create and use load_numeric_block method in io/ascii.py * Disable some rules for docs/ * Initial implementation of the new tutorial * Add add_from_zip_path to Experiments collection * Update data index hash * Use data from web repo * Use another dataset * Save project after every fit * Clean up * Initial implementation of plotting parameter evolution * Replace conditions category with diffrn using CIF naming * Simplify extract_metadata API and make data loading explicit * Fix fit_results to store snapshot parameters per experiment * Clean up tutorial ed-17.py duplicate plot_param calls * Fix test to use save_as() matching current ProjectInfo.path default * Add integer index support to CollectionBase.__getitem__ * Rename plot_param to plot_param_series with versus descriptor * Clean up tutorial * Update docs with new notebook * Simplify constraints API to single expression string * Fix docstring lint errors in CollectionBase.__getitem__ * New dataset and update tutorials * Update T-scan notebook * Fix Windows CI UnicodeDecodeError in script test runner --- .github/copilot-instructions.md | 7 + docs/architecture/architecture.md | 6 + docs/docs/tutorials/ed-13.ipynb | 18 +- docs/docs/tutorials/ed-13.py | 18 +- docs/docs/tutorials/ed-17.ipynb | 601 ++++++++++++++++++ docs/docs/tutorials/ed-17.py | 304 +++++++++ docs/docs/tutorials/ed-3.ipynb | 5 +- docs/docs/tutorials/ed-3.py | 5 +- docs/docs/tutorials/ed-5.ipynb | 3 +- docs/docs/tutorials/ed-5.py | 3 +- docs/docs/tutorials/index.json | 7 + docs/docs/tutorials/index.md | 3 + .../user-guide/analysis-workflow/analysis.md | 33 +- .../user-guide/analysis-workflow/project.md | 7 +- docs/mkdocs.yml | 1 + pixi.lock | 4 +- pyproject.toml | 4 + src/easydiffraction/__init__.py | 4 +- src/easydiffraction/analysis/analysis.py | 54 +- .../analysis/categories/aliases/default.py | 2 +- .../categories/constraints/default.py | 91 +-- src/easydiffraction/core/collection.py | 34 +- src/easydiffraction/core/singleton.py | 4 +- .../experiment/categories/diffrn/__init__.py | 4 + .../experiment/categories/diffrn/default.py | 136 ++++ .../experiment/categories/diffrn/factory.py | 15 + .../datablocks/experiment/item/base.py | 51 ++ .../datablocks/experiment/item/bragg_pd.py | 27 +- .../datablocks/experiment/item/bragg_sc.py | 14 +- .../datablocks/experiment/item/factory.py | 1 + src/easydiffraction/display/plotters/ascii.py | 23 + src/easydiffraction/display/plotters/base.py | 30 + .../display/plotters/plotly.py | 42 ++ src/easydiffraction/display/plotting.py | 101 +++ src/easydiffraction/io/__init__.py | 5 + src/easydiffraction/io/ascii.py | 182 ++++++ src/easydiffraction/project/project.py | 32 +- src/easydiffraction/project/project_info.py | 4 +- src/easydiffraction/utils/utils.py | 36 +- ..._powder-diffraction_constant-wavelength.py | 4 +- .../analysis/categories/test_constraints.py | 10 +- .../easydiffraction/core/test_collection.py | 43 ++ .../project/test_project_save.py | 6 +- tests/unit/easydiffraction/test___init__.py | 2 +- .../unit/easydiffraction/utils/test_utils.py | 20 +- tools/test_scripts.py | 1 + 46 files changed, 1799 insertions(+), 208 deletions(-) create mode 100644 docs/docs/tutorials/ed-17.ipynb create mode 100644 docs/docs/tutorials/ed-17.py create mode 100644 src/easydiffraction/datablocks/experiment/categories/diffrn/__init__.py create mode 100644 src/easydiffraction/datablocks/experiment/categories/diffrn/default.py create mode 100644 src/easydiffraction/datablocks/experiment/categories/diffrn/factory.py create mode 100644 src/easydiffraction/io/ascii.py diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d29a15d1..6c342493 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -100,6 +100,13 @@ case. - Don't add dependencies without asking. +## Tutorials + +- Jupyter notebooks (`docs/docs/tutorials/*.ipynb`) are **generated + artifacts** — never edit them by hand. Edit only the corresponding + `*.py` script, then run `pixi run notebook-convert` followed by + `pixi run notebook-prepare` to regenerate the notebook. + ## Changes - Before implementing any structural or design change (new categories, diff --git a/docs/architecture/architecture.md b/docs/architecture/architecture.md index 884ed4f1..65187b07 100644 --- a/docs/architecture/architecture.md +++ b/docs/architecture/architecture.md @@ -727,6 +727,12 @@ project_dir/ All examples below are drawn from the actual tutorials (`tutorials/`). +> **Notebook workflow:** Jupyter notebooks (`*.ipynb`) in +> `docs/docs/tutorials/` are generated artifacts. Edit only the +> corresponding `*.py` script, then run `pixi run notebook-convert` +> followed by `pixi run notebook-prepare` to regenerate the notebook. +> Never edit `*.ipynb` files by hand. + ### 8.1 Project Setup ```python diff --git a/docs/docs/tutorials/ed-13.ipynb b/docs/docs/tutorials/ed-13.ipynb index d35f3628..a31467a1 100644 --- a/docs/docs/tutorials/ed-13.ipynb +++ b/docs/docs/tutorials/ed-13.ipynb @@ -396,7 +396,7 @@ "TOF, respectively.\n", "\n", "You can set them manually, but it is more convenient to use the\n", - "`get_value_from_xye_header` function from the EasyDiffraction library." + "`extract_metadata` function from the EasyDiffraction library." ] }, { @@ -420,11 +420,11 @@ "metadata": {}, "outputs": [], "source": [ - "project_1.experiments['sim_si'].instrument.setup_twotheta_bank = ed.get_value_from_xye_header(\n", - " si_xye_path, 'two_theta'\n", + "project_1.experiments['sim_si'].instrument.setup_twotheta_bank = ed.extract_metadata(\n", + " si_xye_path, r'two_theta\\s*=\\s*([-+]?\\d*\\.?\\d+(?:[eE][-+]?\\d+)?)'\n", ")\n", - "project_1.experiments['sim_si'].instrument.calib_d_to_tof_linear = ed.get_value_from_xye_header(\n", - " si_xye_path, 'DIFC'\n", + "project_1.experiments['sim_si'].instrument.calib_d_to_tof_linear = ed.extract_metadata(\n", + " si_xye_path, r'DIFC\\s*=\\s*([-+]?\\d*\\.?\\d+(?:[eE][-+]?\\d+)?)'\n", ")" ] }, @@ -1485,11 +1485,11 @@ }, "outputs": [], "source": [ - "project_2.experiments['sim_lbco'].instrument.setup_twotheta_bank = ed.get_value_from_xye_header(\n", - " lbco_xye_path, 'two_theta'\n", + "project_2.experiments['sim_lbco'].instrument.setup_twotheta_bank = ed.extract_metadata(\n", + " lbco_xye_path, r'two_theta\\s*=\\s*([-+]?\\d*\\.?\\d+(?:[eE][-+]?\\d+)?)'\n", ")\n", - "project_2.experiments['sim_lbco'].instrument.calib_d_to_tof_linear = ed.get_value_from_xye_header(\n", - " lbco_xye_path, 'DIFC'\n", + "project_2.experiments['sim_lbco'].instrument.calib_d_to_tof_linear = ed.extract_metadata(\n", + " lbco_xye_path, r'DIFC\\s*=\\s*([-+]?\\d*\\.?\\d+(?:[eE][-+]?\\d+)?)'\n", ")" ] }, diff --git a/docs/docs/tutorials/ed-13.py b/docs/docs/tutorials/ed-13.py index e29aad49..42996532 100644 --- a/docs/docs/tutorials/ed-13.py +++ b/docs/docs/tutorials/ed-13.py @@ -212,7 +212,7 @@ # TOF, respectively. # # You can set them manually, but it is more convenient to use the -# `get_value_from_xye_header` function from the EasyDiffraction library. +# `extract_metadata` function from the EasyDiffraction library. # %% [markdown] tags=["doc-link"] # 📖 See @@ -220,11 +220,11 @@ # for more details about the instrument parameters. # %% -project_1.experiments['sim_si'].instrument.setup_twotheta_bank = ed.get_value_from_xye_header( - si_xye_path, 'two_theta' +project_1.experiments['sim_si'].instrument.setup_twotheta_bank = ed.extract_metadata( + si_xye_path, r'two_theta\s*=\s*([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)' ) -project_1.experiments['sim_si'].instrument.calib_d_to_tof_linear = ed.get_value_from_xye_header( - si_xye_path, 'DIFC' +project_1.experiments['sim_si'].instrument.calib_d_to_tof_linear = ed.extract_metadata( + si_xye_path, r'DIFC\s*=\s*([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)' ) # %% [markdown] @@ -804,11 +804,11 @@ # **Solution:** # %% tags=["solution", "hide-input"] -project_2.experiments['sim_lbco'].instrument.setup_twotheta_bank = ed.get_value_from_xye_header( - lbco_xye_path, 'two_theta' +project_2.experiments['sim_lbco'].instrument.setup_twotheta_bank = ed.extract_metadata( + lbco_xye_path, r'two_theta\s*=\s*([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)' ) -project_2.experiments['sim_lbco'].instrument.calib_d_to_tof_linear = ed.get_value_from_xye_header( - lbco_xye_path, 'DIFC' +project_2.experiments['sim_lbco'].instrument.calib_d_to_tof_linear = ed.extract_metadata( + lbco_xye_path, r'DIFC\s*=\s*([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)' ) # %% [markdown] diff --git a/docs/docs/tutorials/ed-17.ipynb b/docs/docs/tutorials/ed-17.ipynb new file mode 100644 index 00000000..566c1783 --- /dev/null +++ b/docs/docs/tutorials/ed-17.ipynb @@ -0,0 +1,601 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Structure Refinement: Co2SiO4, D20 (T-scan)\n", + "\n", + "This example demonstrates a Rietveld refinement of Co2SiO4 crystal\n", + "structure using constant wavelength neutron powder diffraction data\n", + "from D20 at ILL. A sequential refinement of the same structure against\n", + "a temperature scan is performed to show how to manage multiple\n", + "experiments in a project." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as ed" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Step 1: Define Project\n", + "\n", + "The project object is used to manage the structure, experiment, and\n", + "analysis." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "# Create minimal project without name and description\n", + "project = ed.Project()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## Step 2: Define Crystal Structure\n", + "\n", + "This section shows how to add structures and modify their\n", + "parameters.\n", + "\n", + "#### Create Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.create(name='cosio')\n", + "structure = project.structures['cosio']" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "structure.space_group.name_h_m = 'P n m a'\n", + "structure.space_group.it_coordinate_system_code = 'abc'" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a = 10.31\n", + "structure.cell.length_b = 6.0\n", + "structure.cell.length_c = 4.79" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "structure.atom_sites.create(\n", + " label='Co1',\n", + " type_symbol='Co',\n", + " fract_x=0,\n", + " fract_y=0,\n", + " fract_z=0,\n", + " wyckoff_letter='a',\n", + " b_iso=0.3,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Co2',\n", + " type_symbol='Co',\n", + " fract_x=0.279,\n", + " fract_y=0.25,\n", + " fract_z=0.985,\n", + " wyckoff_letter='c',\n", + " b_iso=0.3,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='Si',\n", + " type_symbol='Si',\n", + " fract_x=0.094,\n", + " fract_y=0.25,\n", + " fract_z=0.429,\n", + " wyckoff_letter='c',\n", + " b_iso=0.34,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O1',\n", + " type_symbol='O',\n", + " fract_x=0.091,\n", + " fract_y=0.25,\n", + " fract_z=0.771,\n", + " wyckoff_letter='c',\n", + " b_iso=0.63,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O2',\n", + " type_symbol='O',\n", + " fract_x=0.448,\n", + " fract_y=0.25,\n", + " fract_z=0.217,\n", + " wyckoff_letter='c',\n", + " b_iso=0.59,\n", + ")\n", + "structure.atom_sites.create(\n", + " label='O3',\n", + " type_symbol='O',\n", + " fract_x=0.164,\n", + " fract_y=0.032,\n", + " fract_z=0.28,\n", + " wyckoff_letter='d',\n", + " b_iso=0.83,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "## Define Experiment\n", + "\n", + "This section shows how to add experiments, configure their parameters,\n", + "and link the structures defined in the previous step.\n", + "\n", + "#### Download Measured Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "file_path = ed.download_data(id=27, destination='data')" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "#### Create Experiments and Set Temperature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "data_paths = ed.extract_data_paths_from_zip(file_path)\n", + "for i, data_path in enumerate(data_paths, start=1):\n", + " name = f'd20_{i}'\n", + " project.experiments.add_from_data_path(\n", + " name=name,\n", + " data_path=data_path,\n", + " )\n", + " expt = project.experiments[name]\n", + " expt.diffrn.ambient_temperature = ed.extract_metadata(\n", + " file_path=data_path,\n", + " pattern=r'^TEMP\\s+([0-9.]+)',\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Instrument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "for expt in project.experiments:\n", + " expt.instrument.setup_wavelength = 1.87\n", + " expt.instrument.calib_twotheta_offset = 0.29" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "#### Set Peak Profile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "for expt in project.experiments:\n", + " expt.peak.broad_gauss_u = 0.24\n", + " expt.peak.broad_gauss_v = -0.53\n", + " expt.peak.broad_gauss_w = 0.38\n", + " expt.peak.broad_lorentz_y = 0.02" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "#### Set Excluded Regions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "for expt in project.experiments:\n", + " expt.excluded_regions.create(id='1', start=0, end=8)\n", + " expt.excluded_regions.create(id='2', start=150, end=180)" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "#### Set Background" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "for expt in project.experiments:\n", + " expt.background.create(id='1', x=8, y=609)\n", + " expt.background.create(id='2', x=9, y=581)\n", + " expt.background.create(id='3', x=10, y=563)\n", + " expt.background.create(id='4', x=11, y=540)\n", + " expt.background.create(id='5', x=12, y=520)\n", + " expt.background.create(id='6', x=15, y=507)\n", + " expt.background.create(id='7', x=25, y=463)\n", + " expt.background.create(id='8', x=30, y=434)\n", + " expt.background.create(id='9', x=50, y=451)\n", + " expt.background.create(id='10', x=70, y=431)\n", + " expt.background.create(id='11', x=90, y=414)\n", + " expt.background.create(id='12', x=110, y=361)\n", + " expt.background.create(id='13', x=130, y=292)\n", + " expt.background.create(id='14', x=150, y=241)" + ] + }, + { + "cell_type": "markdown", + "id": "25", + "metadata": {}, + "source": [ + "#### Set Linked Phases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "for expt in project.experiments:\n", + " expt.linked_phases.create(id='cosio', scale=1.2)" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "## Perform Analysis\n", + "\n", + "This section shows the analysis process, including how to set up\n", + "calculation and fitting engines." + ] + }, + { + "cell_type": "markdown", + "id": "28", + "metadata": {}, + "source": [ + "#### Set Free Parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29", + "metadata": {}, + "outputs": [], + "source": [ + "structure.cell.length_a.free = True\n", + "structure.cell.length_b.free = True\n", + "structure.cell.length_c.free = True\n", + "\n", + "structure.atom_sites['Co2'].fract_x.free = True\n", + "structure.atom_sites['Co2'].fract_z.free = True\n", + "structure.atom_sites['Si'].fract_x.free = True\n", + "structure.atom_sites['Si'].fract_z.free = True\n", + "structure.atom_sites['O1'].fract_x.free = True\n", + "structure.atom_sites['O1'].fract_z.free = True\n", + "structure.atom_sites['O2'].fract_x.free = True\n", + "structure.atom_sites['O2'].fract_z.free = True\n", + "structure.atom_sites['O3'].fract_x.free = True\n", + "structure.atom_sites['O3'].fract_y.free = True\n", + "structure.atom_sites['O3'].fract_z.free = True\n", + "\n", + "structure.atom_sites['Co1'].b_iso.free = True\n", + "structure.atom_sites['Co2'].b_iso.free = True\n", + "structure.atom_sites['Si'].b_iso.free = True\n", + "structure.atom_sites['O1'].b_iso.free = True\n", + "structure.atom_sites['O2'].b_iso.free = True\n", + "structure.atom_sites['O3'].b_iso.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "for expt in project.experiments:\n", + " expt.linked_phases['cosio'].scale.free = True\n", + "\n", + " expt.instrument.calib_twotheta_offset.free = True\n", + "\n", + " expt.peak.broad_gauss_u.free = True\n", + " expt.peak.broad_gauss_v.free = True\n", + " expt.peak.broad_gauss_w.free = True\n", + " expt.peak.broad_lorentz_y.free = True\n", + "\n", + " for point in expt.background:\n", + " point.y.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "31", + "metadata": {}, + "source": [ + "#### Set Constraints\n", + "\n", + "Set aliases for parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.aliases.create(\n", + " label='biso_Co1',\n", + " param_uid=structure.atom_sites['Co1'].b_iso.uid,\n", + ")\n", + "project.analysis.aliases.create(\n", + " label='biso_Co2',\n", + " param_uid=structure.atom_sites['Co2'].b_iso.uid,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "33", + "metadata": {}, + "source": [ + "Set constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.constraints.create(\n", + " expression='biso_Co2 = biso_Co1',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "35", + "metadata": {}, + "source": [ + "Apply constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.apply_constraints()" + ] + }, + { + "cell_type": "markdown", + "id": "37", + "metadata": {}, + "source": [ + "#### Set Fit Mode and Weights" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit_mode.mode = 'single'" + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": {}, + "source": [ + "#### Run Fitting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "#### Plot Measured vs Calculated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "last_expt_name = project.experiments.names[-1]\n", + "project.plot_meas_vs_calc(expt_name=last_expt_name, show_residual=True)" + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "#### Plot parameters evolution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], + "source": [ + "temperature = project.experiments[0].diffrn.ambient_temperature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_param_series(structure.cell.length_a, versus=temperature)\n", + "project.plot_param_series(structure.cell.length_b, versus=temperature)\n", + "project.plot_param_series(structure.cell.length_c, versus=temperature)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_param_series(structure.atom_sites['Co1'].b_iso, versus=temperature)\n", + "project.plot_param_series(structure.atom_sites['Si'].b_iso, versus=temperature)\n", + "project.plot_param_series(structure.atom_sites['O1'].b_iso, versus=temperature)\n", + "project.plot_param_series(structure.atom_sites['O2'].b_iso, versus=temperature)\n", + "project.plot_param_series(structure.atom_sites['O3'].b_iso, versus=temperature)" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/tutorials/ed-17.py b/docs/docs/tutorials/ed-17.py new file mode 100644 index 00000000..0f059849 --- /dev/null +++ b/docs/docs/tutorials/ed-17.py @@ -0,0 +1,304 @@ +# %% [markdown] +# # Structure Refinement: Co2SiO4, D20 (T-scan) +# +# This example demonstrates a Rietveld refinement of Co2SiO4 crystal +# structure using constant wavelength neutron powder diffraction data +# from D20 at ILL. A sequential refinement of the same structure against +# a temperature scan is performed to show how to manage multiple +# experiments in a project. + +# %% [markdown] +# ## Import Library + +# %% +import easydiffraction as ed + +# %% [markdown] +# ## Step 1: Define Project +# +# The project object is used to manage the structure, experiment, and +# analysis. + +# %% +# Create minimal project without name and description +project = ed.Project() + +# %% [markdown] +# ## Step 2: Define Crystal Structure +# +# This section shows how to add structures and modify their +# parameters. +# +# #### Create Structure + +# %% +project.structures.create(name='cosio') +structure = project.structures['cosio'] + +# %% [markdown] +# #### Set Space Group + +# %% +structure.space_group.name_h_m = 'P n m a' +structure.space_group.it_coordinate_system_code = 'abc' + +# %% [markdown] +# #### Set Unit Cell + +# %% +structure.cell.length_a = 10.31 +structure.cell.length_b = 6.0 +structure.cell.length_c = 4.79 + +# %% [markdown] +# #### Set Atom Sites + +# %% +structure.atom_sites.create( + label='Co1', + type_symbol='Co', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.3, +) +structure.atom_sites.create( + label='Co2', + type_symbol='Co', + fract_x=0.279, + fract_y=0.25, + fract_z=0.985, + wyckoff_letter='c', + b_iso=0.3, +) +structure.atom_sites.create( + label='Si', + type_symbol='Si', + fract_x=0.094, + fract_y=0.25, + fract_z=0.429, + wyckoff_letter='c', + b_iso=0.34, +) +structure.atom_sites.create( + label='O1', + type_symbol='O', + fract_x=0.091, + fract_y=0.25, + fract_z=0.771, + wyckoff_letter='c', + b_iso=0.63, +) +structure.atom_sites.create( + label='O2', + type_symbol='O', + fract_x=0.448, + fract_y=0.25, + fract_z=0.217, + wyckoff_letter='c', + b_iso=0.59, +) +structure.atom_sites.create( + label='O3', + type_symbol='O', + fract_x=0.164, + fract_y=0.032, + fract_z=0.28, + wyckoff_letter='d', + b_iso=0.83, +) + +# %% [markdown] +# ## Define Experiment +# +# This section shows how to add experiments, configure their parameters, +# and link the structures defined in the previous step. +# +# #### Download Measured Data + +# %% +file_path = ed.download_data(id=27, destination='data') + +# %% [markdown] +# #### Create Experiments and Set Temperature + +# %% +data_paths = ed.extract_data_paths_from_zip(file_path) +for i, data_path in enumerate(data_paths, start=1): + name = f'd20_{i}' + project.experiments.add_from_data_path( + name=name, + data_path=data_path, + ) + expt = project.experiments[name] + expt.diffrn.ambient_temperature = ed.extract_metadata( + file_path=data_path, + pattern=r'^TEMP\s+([0-9.]+)', + ) + +# %% [markdown] +# #### Set Instrument + +# %% +for expt in project.experiments: + expt.instrument.setup_wavelength = 1.87 + expt.instrument.calib_twotheta_offset = 0.29 + +# %% [markdown] +# #### Set Peak Profile + +# %% +for expt in project.experiments: + expt.peak.broad_gauss_u = 0.24 + expt.peak.broad_gauss_v = -0.53 + expt.peak.broad_gauss_w = 0.38 + expt.peak.broad_lorentz_y = 0.02 + +# %% [markdown] +# #### Set Excluded Regions + +# %% +for expt in project.experiments: + expt.excluded_regions.create(id='1', start=0, end=8) + expt.excluded_regions.create(id='2', start=150, end=180) + +# %% [markdown] +# #### Set Background + +# %% +for expt in project.experiments: + expt.background.create(id='1', x=8, y=609) + expt.background.create(id='2', x=9, y=581) + expt.background.create(id='3', x=10, y=563) + expt.background.create(id='4', x=11, y=540) + expt.background.create(id='5', x=12, y=520) + expt.background.create(id='6', x=15, y=507) + expt.background.create(id='7', x=25, y=463) + expt.background.create(id='8', x=30, y=434) + expt.background.create(id='9', x=50, y=451) + expt.background.create(id='10', x=70, y=431) + expt.background.create(id='11', x=90, y=414) + expt.background.create(id='12', x=110, y=361) + expt.background.create(id='13', x=130, y=292) + expt.background.create(id='14', x=150, y=241) + +# %% [markdown] +# #### Set Linked Phases + +# %% +for expt in project.experiments: + expt.linked_phases.create(id='cosio', scale=1.2) + +# %% [markdown] +# ## Perform Analysis +# +# This section shows the analysis process, including how to set up +# calculation and fitting engines. + +# %% [markdown] +# #### Set Free Parameters + +# %% +structure.cell.length_a.free = True +structure.cell.length_b.free = True +structure.cell.length_c.free = True + +structure.atom_sites['Co2'].fract_x.free = True +structure.atom_sites['Co2'].fract_z.free = True +structure.atom_sites['Si'].fract_x.free = True +structure.atom_sites['Si'].fract_z.free = True +structure.atom_sites['O1'].fract_x.free = True +structure.atom_sites['O1'].fract_z.free = True +structure.atom_sites['O2'].fract_x.free = True +structure.atom_sites['O2'].fract_z.free = True +structure.atom_sites['O3'].fract_x.free = True +structure.atom_sites['O3'].fract_y.free = True +structure.atom_sites['O3'].fract_z.free = True + +structure.atom_sites['Co1'].b_iso.free = True +structure.atom_sites['Co2'].b_iso.free = True +structure.atom_sites['Si'].b_iso.free = True +structure.atom_sites['O1'].b_iso.free = True +structure.atom_sites['O2'].b_iso.free = True +structure.atom_sites['O3'].b_iso.free = True + +# %% +for expt in project.experiments: + expt.linked_phases['cosio'].scale.free = True + + expt.instrument.calib_twotheta_offset.free = True + + 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 + + for point in expt.background: + point.y.free = True + +# %% [markdown] +# #### Set Constraints +# +# Set aliases for parameters. + +# %% +project.analysis.aliases.create( + label='biso_Co1', + param_uid=structure.atom_sites['Co1'].b_iso.uid, +) +project.analysis.aliases.create( + label='biso_Co2', + param_uid=structure.atom_sites['Co2'].b_iso.uid, +) + +# %% [markdown] +# Set constraints. + +# %% +project.analysis.constraints.create( + expression='biso_Co2 = biso_Co1', +) + +# %% [markdown] +# Apply constraints. + +# %% +project.analysis.apply_constraints() + +# %% [markdown] +# #### Set Fit Mode and Weights + +# %% +project.analysis.fit_mode.mode = 'single' + +# %% [markdown] +# #### Run Fitting + +# %% +project.analysis.fit() + +# %% [markdown] +# #### Plot Measured vs Calculated + +# %% +last_expt_name = project.experiments.names[-1] +project.plot_meas_vs_calc(expt_name=last_expt_name, show_residual=True) + +# %% [markdown] +# #### Plot parameters evolution + +# %% +temperature = project.experiments[0].diffrn.ambient_temperature + +# %% +project.plot_param_series(structure.cell.length_a, versus=temperature) +project.plot_param_series(structure.cell.length_b, versus=temperature) +project.plot_param_series(structure.cell.length_c, versus=temperature) + +# %% +project.plot_param_series(structure.atom_sites['Co1'].b_iso, versus=temperature) +project.plot_param_series(structure.atom_sites['Si'].b_iso, versus=temperature) +project.plot_param_series(structure.atom_sites['O1'].b_iso, versus=temperature) +project.plot_param_series(structure.atom_sites['O2'].b_iso, versus=temperature) +project.plot_param_series(structure.atom_sites['O3'].b_iso, versus=temperature) diff --git a/docs/docs/tutorials/ed-3.ipynb b/docs/docs/tutorials/ed-3.ipynb index 0fadb404..5104fcab 100644 --- a/docs/docs/tutorials/ed-3.ipynb +++ b/docs/docs/tutorials/ed-3.ipynb @@ -1429,7 +1429,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.constraints.create(lhs_alias='biso_Ba', rhs_expr='biso_La')" + "project.analysis.constraints.create(expression='biso_Ba = biso_La')" ] }, { @@ -1614,8 +1614,7 @@ "outputs": [], "source": [ "project.analysis.constraints.create(\n", - " lhs_alias='occ_Ba',\n", - " rhs_expr='1 - occ_La',\n", + " expression='occ_Ba = 1 - occ_La',\n", ")" ] }, diff --git a/docs/docs/tutorials/ed-3.py b/docs/docs/tutorials/ed-3.py index 051faae0..23b60d88 100644 --- a/docs/docs/tutorials/ed-3.py +++ b/docs/docs/tutorials/ed-3.py @@ -578,7 +578,7 @@ # Set constraints. # %% -project.analysis.constraints.create(lhs_alias='biso_Ba', rhs_expr='biso_La') +project.analysis.constraints.create(expression='biso_Ba = biso_La') # %% [markdown] # Show defined constraints. @@ -648,8 +648,7 @@ # %% project.analysis.constraints.create( - lhs_alias='occ_Ba', - rhs_expr='1 - occ_La', + expression='occ_Ba = 1 - occ_La', ) # %% [markdown] diff --git a/docs/docs/tutorials/ed-5.ipynb b/docs/docs/tutorials/ed-5.ipynb index 2ee5ec1a..903fe2ae 100644 --- a/docs/docs/tutorials/ed-5.ipynb +++ b/docs/docs/tutorials/ed-5.ipynb @@ -527,8 +527,7 @@ "outputs": [], "source": [ "project.analysis.constraints.create(\n", - " lhs_alias='biso_Co2',\n", - " rhs_expr='biso_Co1',\n", + " expression='biso_Co2 = biso_Co1',\n", ")" ] }, diff --git a/docs/docs/tutorials/ed-5.py b/docs/docs/tutorials/ed-5.py index f7a30da2..74e1d887 100644 --- a/docs/docs/tutorials/ed-5.py +++ b/docs/docs/tutorials/ed-5.py @@ -267,8 +267,7 @@ # %% project.analysis.constraints.create( - lhs_alias='biso_Co2', - rhs_expr='biso_Co1', + expression='biso_Co2 = biso_Co1', ) # %% [markdown] diff --git a/docs/docs/tutorials/index.json b/docs/docs/tutorials/index.json index d40d1896..3f2f223c 100644 --- a/docs/docs/tutorials/index.json +++ b/docs/docs/tutorials/index.json @@ -110,5 +110,12 @@ "title": "Advanced: Si Joint Bragg+PDF Fit", "description": "Joint refinement of Si crystal structure combining Bragg diffraction (SEPD) and pair distribution function (NOMAD) analysis", "level": "advanced" + }, + "17": { + "url": "https://easyscience.github.io/diffraction-lib/{version}/tutorials/ed-17/ed-17.ipynb", + "original_name": "", + "title": "Structure Refinement: Co2SiO4, D20 (Temperature scan)", + "description": "Sequential Rietveld refinement of Co2SiO4 using constant wavelength neutron powder diffraction data from D20 at ILL across a temperature scan", + "level": "advanced" } } diff --git a/docs/docs/tutorials/index.md b/docs/docs/tutorials/index.md index 5e406e59..ef22e949 100644 --- a/docs/docs/tutorials/index.md +++ b/docs/docs/tutorials/index.md @@ -85,6 +85,9 @@ The tutorials are organized into the following categories. diffraction (SEPD) and pair distribution function (NOMAD) analysis. A single shared structure is refined simultaneously against both datasets. +- [Co2SiO4 Temperature scan](ed-17.ipynb) – Sequential Rietveld + refinement of Co2SiO4 using constant wavelength neutron powder + diffraction data from D20 at ILL across a temperature scan. ## Workshops & Schools diff --git a/docs/docs/user-guide/analysis-workflow/analysis.md b/docs/docs/user-guide/analysis-workflow/analysis.md index f0341519..76aaa699 100644 --- a/docs/docs/user-guide/analysis-workflow/analysis.md +++ b/docs/docs/user-guide/analysis-workflow/analysis.md @@ -294,23 +294,17 @@ project.analysis.aliases.create( ### Setting Constraints Now that you have set the aliases, you can define constraints using the -`add` method of the `constraints` object. Constraints are defined by -specifying the **left-hand side (lhs) alias** and the **right-hand side -(rhs) expression**. The rhs expression can be a simple alias or a more -complex expression involving other aliases. +`create` method of the `constraints` object. Each constraint is a single +expression string of the form `lhs = rhs`, where the left-hand side is +an alias and the right-hand side is an expression involving other +aliases. An example of setting constraints for the aliases defined above: ```python -project.analysis.constraints.create( - lhs_alias='biso_Ba', - rhs_expr='biso_La', -) +project.analysis.constraints.create(expression='biso_Ba = biso_La') -project.analysis.constraints.create( - lhs_alias='occ_Ba', - rhs_expr='1 - occ_La', -) +project.analysis.constraints.create(expression='occ_Ba = 1 - occ_La') ``` These constraints ensure that the `biso_Ba` parameter is equal to @@ -332,10 +326,10 @@ The example of the output is: User defined constraints -| lhs_alias | rhs_expr | full expression | -| --------- | ---------- | ------------------- | -| biso_Ba | biso_La | biso_Ba = biso_La | -| occ_Ba | 1 - occ_La | occ_Ba = 1 - occ_La | +| expression | +| ------------------- | +| biso_Ba = biso_La | +| occ_Ba = 1 - occ_La | ## Analysis as CIF @@ -363,10 +357,9 @@ Example output: │ occ_Ba lbco.atom_site.Ba.occupancy │ │ │ │ loop_ │ -│ _constraint.lhs_alias │ -│ _constraint.rhs_expr │ -│ biso_Ba biso_La │ -│ occ_Ba "1 - occ_La" │ +│ _constraint.expression │ +│ "biso_Ba = biso_La" │ +│ "occ_Ba = 1 - occ_La" │ ╘════════════════════════════════════════════════╛ ``` diff --git a/docs/docs/user-guide/analysis-workflow/project.md b/docs/docs/user-guide/analysis-workflow/project.md index ad41f852..60c944b9 100644 --- a/docs/docs/user-guide/analysis-workflow/project.md +++ b/docs/docs/user-guide/analysis-workflow/project.md @@ -260,10 +260,9 @@ occ_La lbco.atom_site.La.occupancy occ_Ba lbco.atom_site.Ba.occupancy loop_ -_constraint.lhs_alias -_constraint.rhs_expr -biso_Ba biso_La -occ_Ba "1 - occ_La" +_constraint.expression +"biso_Ba = biso_La" +"occ_Ba = 1 - occ_La"
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 55ca5f22..c272bdec 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -211,6 +211,7 @@ nav: - PbSO4 NPD+XRD: tutorials/ed-4.ipynb - LBCO+Si McStas: tutorials/ed-9.ipynb - Si Bragg+PDF: tutorials/ed-16.ipynb + - Co2SiO4 T-scan: tutorials/ed-17.ipynb - Workshops & Schools: - DMSC Summer School: tutorials/ed-13.ipynb - API Reference: diff --git a/pixi.lock b/pixi.lock index 1b3db850..5195fec7 100644 --- a/pixi.lock +++ b/pixi.lock @@ -4869,8 +4869,8 @@ packages: requires_python: '>=3.5' - pypi: ./ name: easydiffraction - version: 0.10.2+dev46 - sha256: 9ada6af993ed7303fd58593648683db9afb40a7c191b5f5f7384f37934362c42 + version: 0.10.2+dev7 + sha256: 322f1ccfe97af5f9fa88e7c14eb8b8e12221deb7058cef6320de02e52560d1ad requires_dist: - asciichartpy - asteval diff --git a/pyproject.toml b/pyproject.toml index 16d18621..88036948 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -322,6 +322,10 @@ ignore = [ 'docs/**' = [ 'INP001', # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ 'T201', # https://docs.astral.sh/ruff/rules/print/ + # Temporary: + 'ANN', + 'D', + 'W', ] # Specific options for certain rules diff --git a/src/easydiffraction/__init__.py b/src/easydiffraction/__init__.py index e2fb3c4e..10308402 100644 --- a/src/easydiffraction/__init__.py +++ b/src/easydiffraction/__init__.py @@ -3,6 +3,9 @@ from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory from easydiffraction.datablocks.structure.item.factory import StructureFactory +from easydiffraction.io.ascii import extract_data_paths_from_dir +from easydiffraction.io.ascii import extract_data_paths_from_zip +from easydiffraction.io.ascii import extract_metadata from easydiffraction.project.project import Project from easydiffraction.utils.logging import Logger from easydiffraction.utils.logging import console @@ -10,6 +13,5 @@ from easydiffraction.utils.utils import download_all_tutorials from easydiffraction.utils.utils import download_data from easydiffraction.utils.utils import download_tutorial -from easydiffraction.utils.utils import get_value_from_xye_header from easydiffraction.utils.utils import list_tutorials from easydiffraction.utils.utils import show_version diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index 874017cb..f97c2e13 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -33,11 +33,6 @@ class Analysis: This class wires calculators and minimizers, exposes a compact interface for parameters, constraints and results, and coordinates computations across the project's structures and experiments. - - Typical usage: - - Display or filter parameters to fit. - - Select a calculator/minimizer implementation. - - Calculate patterns and run single or joint fits. """ def __init__(self, project: object) -> None: @@ -59,6 +54,8 @@ def __init__(self, project: object) -> None: self._fit_mode = FitModeFactory.create(self._fit_mode_type) self._joint_fit_experiments = JointFitExperiments() self.fitter = Fitter('lmfit') + self.fit_results = None + self._parameter_snapshots: dict[str, dict[str, dict]] = {} def help(self) -> None: """Print a summary of analysis properties and methods.""" @@ -554,29 +551,18 @@ def joint_fit_experiments(self) -> object: def show_constraints(self) -> None: """Print a table of all user-defined symbolic constraints.""" - constraints_dict = dict(self.constraints) - if not self.constraints._items: log.warning('No constraints defined.') return rows = [] - for constraint in constraints_dict.values(): - row = { - 'lhs_alias': constraint.lhs_alias.value, - 'rhs_expr': constraint.rhs_expr.value, - 'full expression': f'{constraint.lhs_alias.value} = {constraint.rhs_expr.value}', - } - rows.append(row) - - headers = ['lhs_alias', 'rhs_expr', 'full expression'] - alignments = ['left', 'left', 'left'] - rows = [[row[header] for header in headers] for row in rows] + for constraint in self.constraints: + rows.append([constraint.expression.value]) console.paragraph('User defined constraints') render_table( - columns_headers=headers, - columns_alignment=alignments, + columns_headers=['expression'], + columns_alignment=['left'], columns_data=rows, ) @@ -638,6 +624,10 @@ def fit(self) -> None: weights=self._joint_fit_experiments, analysis=self, ) + + # After fitting, get the results + self.fit_results = self.fitter.results + elif mode is FitModeEnum.SINGLE: # TODO: Find a better way without creating dummy # experiments? @@ -657,11 +647,29 @@ def fit(self) -> None: dummy_experiments, analysis=self, ) + + # After fitting, snapshot parameter values before + # they get overwritten by the next experiment's fit + results = self.fitter.results + snapshot: dict[str, dict] = {} + for param in results.parameters: + snapshot[param.unique_name] = { + 'value': param.value, + 'uncertainty': param.uncertainty, + 'units': param.units, + } + self._parameter_snapshots[expt_name] = snapshot + self.fit_results = results + else: raise NotImplementedError(f'Fit mode {mode.value} not implemented yet.') - # After fitting, get the results - self.fit_results = self.fitter.results + # After fitting, save the project + # TODO: Consider saving individual data during sequential + # (single) fitting, instead of waiting until the end and save + # only the last one + if self.project.info.path is not None: + self.project.save() def show_fit_results(self) -> None: """ @@ -678,7 +686,7 @@ def show_fit_results(self) -> None: project.analysis.fit() project.analysis.show_fit_results() """ - if not hasattr(self, 'fit_results') or self.fit_results is None: + if self.fit_results is None: log.warning('No fit results available. Run fit() first.') return diff --git a/src/easydiffraction/analysis/categories/aliases/default.py b/src/easydiffraction/analysis/categories/aliases/default.py index 63cc5f28..7b1e0df0 100644 --- a/src/easydiffraction/analysis/categories/aliases/default.py +++ b/src/easydiffraction/analysis/categories/aliases/default.py @@ -34,7 +34,7 @@ def __init__(self) -> None: name='label', description='...', # TODO value_spec=AttributeSpec( - default='_', + default='_', # TODO, Maybe None? validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), ), cif_handler=CifHandler(names=['_alias.label']), diff --git a/src/easydiffraction/analysis/categories/constraints/default.py b/src/easydiffraction/analysis/categories/constraints/default.py index 71d61d95..3bb1b77e 100644 --- a/src/easydiffraction/analysis/categories/constraints/default.py +++ b/src/easydiffraction/analysis/categories/constraints/default.py @@ -3,8 +3,9 @@ """ 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. +Represents an equation of the form ``lhs_alias = rhs_expr`` stored as a +single expression string. The left- and right-hand sides are derived by +splitting the expression at the ``=`` sign. """ from __future__ import annotations @@ -21,66 +22,70 @@ class Constraint(CategoryItem): - """Single constraint item.""" + """Single constraint item stored as ``lhs = rhs`` expression.""" def __init__(self) -> None: super().__init__() - self._lhs_alias = StringDescriptor( - name='lhs_alias', - description='Left-hand side of the equation.', # TODO + self._expression = StringDescriptor( + name='expression', + description='Constraint equation, e.g. "occ_Ba = 1 - occ_La".', value_spec=AttributeSpec( - default='...', # TODO + default='_', # TODO, Maybe None? validator=RegexValidator(pattern=r'.*'), ), - cif_handler=CifHandler(names=['_constraint.lhs_alias']), - ) - self._rhs_expr = StringDescriptor( - name='rhs_expr', - description='Right-hand side expression.', # TODO - value_spec=AttributeSpec( - default='...', # TODO - validator=RegexValidator(pattern=r'.*'), - ), - cif_handler=CifHandler(names=['_constraint.rhs_expr']), + cif_handler=CifHandler(names=['_constraint.expression']), ) self._identity.category_code = 'constraint' - self._identity.category_entry_name = lambda: str(self.lhs_alias.value) + self._identity.category_entry_name = lambda: self.lhs_alias # ------------------------------------------------------------------ # Public properties # ------------------------------------------------------------------ @property - def lhs_alias(self) -> StringDescriptor: + def expression(self) -> StringDescriptor: """ - Left-hand side of the equation. + Full constraint equation (e.g. ``'occ_Ba = 1 - occ_La'``). Reading this property returns the underlying - ``StringDescriptor`` object. Assigning to it updates the - parameter value. + ``StringDescriptor`` object. Assigning to it updates the value. """ - return self._lhs_alias + return self._expression - @lhs_alias.setter - def lhs_alias(self, value: str) -> None: - self._lhs_alias.value = value + @expression.setter + def expression(self, value: str) -> None: + self._expression.value = value @property - def rhs_expr(self) -> StringDescriptor: - """ - Right-hand side expression. + def lhs_alias(self) -> str: + """Left-hand side alias derived from the expression.""" + return self._split_expression()[0] - Reading this property returns the underlying - ``StringDescriptor`` object. Assigning to it updates the - parameter value. + @property + def rhs_expr(self) -> str: + """Right-hand side expression derived from the expression.""" + return self._split_expression()[1] + + # ------------------------------------------------------------------ + # Internal helpers + # ------------------------------------------------------------------ + + def _split_expression(self) -> tuple[str, str]: """ - return self._rhs_expr + Split the expression at the first ``=`` sign. - @rhs_expr.setter - def rhs_expr(self, value: str) -> None: - self._rhs_expr.value = value + Returns + ------- + tuple[str, str] + ``(lhs_alias, rhs_expr)`` with whitespace stripped. + """ + raw = self._expression.value or '' + if '=' not in raw: + return (raw.strip(), '') + lhs, rhs = raw.split('=', 1) + return (lhs.strip(), rhs.strip()) @ConstraintsFactory.register @@ -98,6 +103,20 @@ def __init__(self) -> None: """Create an empty constraints collection.""" super().__init__(item_type=Constraint) + def create(self, *, expression: str) -> None: + """ + Create a constraint from an expression string. + + Parameters + ---------- + expression : str + Constraint equation, e.g. ``'biso_Co2 = biso_Co1'`` or + ``'occ_Ba = 1 - occ_La'``. + """ + item = Constraint() + item.expression = expression + self.add(item) + def _update(self, called_by_minimizer: bool = False) -> None: del called_by_minimizer diff --git a/src/easydiffraction/core/collection.py b/src/easydiffraction/core/collection.py index 0560a37e..8f590b07 100644 --- a/src/easydiffraction/core/collection.py +++ b/src/easydiffraction/core/collection.py @@ -33,18 +33,34 @@ def __init__(self, item_type: type) -> None: self._index: dict = {} self._item_type = item_type - def __getitem__(self, name: str) -> GuardedBase: + def __getitem__(self, key: str | int) -> GuardedBase: """ - Return an item by its identity key. + Return an item by name or positional index. - Rebuilds the internal index on a cache miss to stay consistent - with recent mutations. + Parameters + ---------- + key : str | int + Identity key (str) or zero-based positional index (int). + + Returns + ------- + GuardedBase + The item matching the given key or index. + + Raises + ------ + TypeError + If *key* is neither ``str`` nor ``int``. """ - try: - return self._index[name] - except KeyError: - self._rebuild_index() - return self._index[name] + if isinstance(key, int): + return self._items[key] + if isinstance(key, str): + try: + return self._index[key] + except KeyError: + self._rebuild_index() + return self._index[key] + raise TypeError(f'Collection indices must be str or int, not {type(key).__name__}') def __setitem__(self, name: str, item: GuardedBase) -> None: """Insert or replace an item under the given identity key.""" diff --git a/src/easydiffraction/core/singleton.py b/src/easydiffraction/core/singleton.py index 9033822a..d40e62bc 100644 --- a/src/easydiffraction/core/singleton.py +++ b/src/easydiffraction/core/singleton.py @@ -129,8 +129,8 @@ def _parse_constraints(self) -> None: self._parsed_constraints = [] for expr_obj in self._constraints: - lhs_alias = expr_obj.lhs_alias.value - rhs_expr = expr_obj.rhs_expr.value + lhs_alias = expr_obj.lhs_alias + rhs_expr = expr_obj.rhs_expr if lhs_alias and rhs_expr: constraint = (lhs_alias.strip(), rhs_expr.strip()) diff --git a/src/easydiffraction/datablocks/experiment/categories/diffrn/__init__.py b/src/easydiffraction/datablocks/experiment/categories/diffrn/__init__.py new file mode 100644 index 00000000..6c11ee7e --- /dev/null +++ b/src/easydiffraction/datablocks/experiment/categories/diffrn/__init__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.datablocks.experiment.categories.diffrn.default import DefaultDiffrn diff --git a/src/easydiffraction/datablocks/experiment/categories/diffrn/default.py b/src/easydiffraction/datablocks/experiment/categories/diffrn/default.py new file mode 100644 index 00000000..9635fc2e --- /dev/null +++ b/src/easydiffraction/datablocks/experiment/categories/diffrn/default.py @@ -0,0 +1,136 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Default diffraction ambient-conditions category.""" + +from __future__ import annotations + +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.metadata import TypeInfo +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import RangeValidator +from easydiffraction.core.variable import NumericDescriptor +from easydiffraction.datablocks.experiment.categories.diffrn.factory import DiffrnFactory +from easydiffraction.io.cif.handler import CifHandler + + +@DiffrnFactory.register +class DefaultDiffrn(CategoryItem): + """Ambient conditions recorded during diffraction measurement.""" + + type_info = TypeInfo( + tag='default', + description='Diffraction ambient conditions', + ) + + def __init__(self) -> None: + super().__init__() + + self._ambient_temperature = NumericDescriptor( + name='ambient_temperature', + description='Mean temperature during measurement', + units='K', + value_spec=AttributeSpec( + default=None, + allow_none=True, + validator=RangeValidator(), + ), + cif_handler=CifHandler(names=['_diffrn.ambient_temperature']), + ) + + self._ambient_pressure = NumericDescriptor( + name='ambient_pressure', + description='Mean hydrostatic pressure during measurement', + units='kPa', + value_spec=AttributeSpec( + default=None, + allow_none=True, + validator=RangeValidator(), + ), + cif_handler=CifHandler(names=['_diffrn.ambient_pressure']), + ) + + self._ambient_magnetic_field = NumericDescriptor( + name='ambient_magnetic_field', + description='Mean magnetic field during measurement', + units='T', + value_spec=AttributeSpec( + default=None, + allow_none=True, + validator=RangeValidator(), + ), + cif_handler=CifHandler(names=['_diffrn.ambient_magnetic_field']), + ) + + self._ambient_electric_field = NumericDescriptor( + name='ambient_electric_field', + description='Mean electric field during measurement', + units='V/m', + value_spec=AttributeSpec( + default=None, + allow_none=True, + validator=RangeValidator(), + ), + cif_handler=CifHandler(names=['_diffrn.ambient_electric_field']), + ) + + self._identity.category_code = 'diffrn' + + # ------------------------------------------------------------------ + # Public properties + # ------------------------------------------------------------------ + + @property + def ambient_temperature(self) -> NumericDescriptor: + """ + Mean temperature during measurement (K). + + Reading this property returns the underlying + ``NumericDescriptor`` object. Assigning to it updates the value. + """ + return self._ambient_temperature + + @ambient_temperature.setter + def ambient_temperature(self, value: float) -> None: + self._ambient_temperature.value = value + + @property + def ambient_pressure(self) -> NumericDescriptor: + """ + Mean hydrostatic pressure during measurement (kPa). + + Reading this property returns the underlying + ``NumericDescriptor`` object. Assigning to it updates the value. + """ + return self._ambient_pressure + + @ambient_pressure.setter + def ambient_pressure(self, value: float) -> None: + self._ambient_pressure.value = value + + @property + def ambient_magnetic_field(self) -> NumericDescriptor: + """ + Mean magnetic field during measurement (T). + + Reading this property returns the underlying + ``NumericDescriptor`` object. Assigning to it updates the value. + """ + return self._ambient_magnetic_field + + @ambient_magnetic_field.setter + def ambient_magnetic_field(self, value: float) -> None: + self._ambient_magnetic_field.value = value + + @property + def ambient_electric_field(self) -> NumericDescriptor: + """ + Mean electric field during measurement (V/m). + + Reading this property returns the underlying + ``NumericDescriptor`` object. Assigning to it updates the value. + """ + return self._ambient_electric_field + + @ambient_electric_field.setter + def ambient_electric_field(self, value: float) -> None: + self._ambient_electric_field.value = value diff --git a/src/easydiffraction/datablocks/experiment/categories/diffrn/factory.py b/src/easydiffraction/datablocks/experiment/categories/diffrn/factory.py new file mode 100644 index 00000000..ef5fb719 --- /dev/null +++ b/src/easydiffraction/datablocks/experiment/categories/diffrn/factory.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Factory for diffraction ambient-conditions categories.""" + +from __future__ import annotations + +from easydiffraction.core.factory import FactoryBase + + +class DiffrnFactory(FactoryBase): + """Create diffraction ambient-conditions category instances.""" + + _default_rules = { + frozenset(): 'default', + } diff --git a/src/easydiffraction/datablocks/experiment/item/base.py b/src/easydiffraction/datablocks/experiment/item/base.py index 2dddee44..bc42728f 100644 --- a/src/easydiffraction/datablocks/experiment/item/base.py +++ b/src/easydiffraction/datablocks/experiment/item/base.py @@ -11,6 +11,7 @@ from easydiffraction.core.datablock import DatablockItem from easydiffraction.datablocks.experiment.categories.data.factory import DataFactory +from easydiffraction.datablocks.experiment.categories.diffrn.factory import DiffrnFactory from easydiffraction.datablocks.experiment.categories.excluded_regions.factory import ( ExcludedRegionsFactory, ) @@ -49,6 +50,9 @@ def __init__( self._calculator_type: str | None = None self._identity.datablock_entry_name = lambda: self.name + self._diffrn_type: str = DiffrnFactory.default_tag() + self._diffrn = DiffrnFactory.create(self._diffrn_type) + @property def name(self) -> str: """Human-readable name of the experiment.""" @@ -71,6 +75,53 @@ def type(self) -> object: # TODO: Consider another name """Experiment type: sample form, probe, beam mode.""" return self._type + # ------------------------------------------------------------------ + # Diffrn conditions (switchable-category pattern) + # ------------------------------------------------------------------ + + @property + def diffrn(self) -> object: + """Ambient conditions recorded during measurement.""" + return self._diffrn + + @property + def diffrn_type(self) -> str: + """Tag of the active diffraction conditions type.""" + return self._diffrn_type + + @diffrn_type.setter + def diffrn_type(self, new_type: str) -> None: + """ + Switch to a different diffraction conditions type. + + Parameters + ---------- + new_type : str + Diffrn conditions tag (e.g. ``'default'``). + """ + supported_tags = DiffrnFactory.supported_tags() + if new_type not in supported_tags: + log.warning( + f"Unsupported diffrn type '{new_type}'. " + f'Supported: {supported_tags}. ' + f"For more information, use 'show_supported_diffrn_types()'", + ) + return + + self._diffrn = DiffrnFactory.create(new_type) + self._diffrn_type = new_type + console.paragraph(f"Diffrn type for experiment '{self.name}' changed to") + console.print(new_type) + + def show_supported_diffrn_types(self) -> None: + """Print a table of supported diffraction conditions types.""" + DiffrnFactory.show_supported() + + def show_current_diffrn_type(self) -> None: + """Print the currently used diffraction conditions type.""" + console.paragraph('Current diffrn type') + console.print(self.diffrn_type) + @property def as_cif(self) -> str: """Serialize this experiment to a CIF fragment.""" diff --git a/src/easydiffraction/datablocks/experiment/item/bragg_pd.py b/src/easydiffraction/datablocks/experiment/item/bragg_pd.py index d5b469cf..01c7aebe 100644 --- a/src/easydiffraction/datablocks/experiment/item/bragg_pd.py +++ b/src/easydiffraction/datablocks/experiment/item/bragg_pd.py @@ -16,6 +16,7 @@ from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory +from easydiffraction.io.ascii import load_numeric_block from easydiffraction.utils.logging import console from easydiffraction.utils.logging import log @@ -54,7 +55,10 @@ def __init__( self._background_type: str = BackgroundFactory.default_tag() self._background = BackgroundFactory.create(self._background_type) - def _load_ascii_data_to_experiment(self, data_path: str) -> None: + def _load_ascii_data_to_experiment( + self, + data_path: str, + ) -> None: """ Load (x, y, sy) data from an ASCII file into the data category. @@ -65,16 +69,17 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: If ``sy`` has values smaller than ``0.0001``, they are replaced with ``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 + data = load_numeric_block(data_path) if data.shape[1] < 2: - raise ValueError('Data file must have at least two columns: x and y.') + log.error( + 'Data file must have at least two columns: x and y.', + exc_type=ValueError, + ) + return if data.shape[1] < 3: - print('Warning: No uncertainty (sy) column provided. Defaulting to sqrt(y).') + log.warning('No uncertainty (sy) column provided. Defaulting to sqrt(y).') # Extract x, y data x: np.ndarray = data[:, 0] @@ -95,8 +100,14 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: self.data._set_intensity_meas(y) self.data._set_intensity_meas_su(sy) + temperature = '' + if self.diffrn.ambient_temperature.value is not None: + temperature = f' Temperature: {self.diffrn.ambient_temperature.value:.3f} K.' + console.paragraph('Data loaded successfully') - console.print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}") + console.print( + f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}.{temperature}" + ) # ------------------------------------------------------------------ # Instrument (switchable-category pattern) diff --git a/src/easydiffraction/datablocks/experiment/item/bragg_sc.py b/src/easydiffraction/datablocks/experiment/item/bragg_sc.py index 3cb3f8a1..4cc372d4 100644 --- a/src/easydiffraction/datablocks/experiment/item/bragg_sc.py +++ b/src/easydiffraction/datablocks/experiment/item/bragg_sc.py @@ -14,6 +14,7 @@ from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory +from easydiffraction.io.ascii import load_numeric_block from easydiffraction.utils.logging import console from easydiffraction.utils.logging import log @@ -50,14 +51,7 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: The file format is space/column separated with 5 columns: ``h k l Iobs sIobs``. """ - try: - data = np.loadtxt(data_path) - except Exception as e: - log.error( - f'Failed to read data from {data_path}: {e}', - exc_type=IOError, - ) - return + data = load_numeric_block(data_path) if data.shape[1] < 5: log.error( @@ -114,8 +108,8 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: l Iobs sIobs wavelength``. """ try: - data = np.loadtxt(data_path) - except Exception as e: + data = load_numeric_block(data_path) + except IOError as e: log.error( f'Failed to read data from {data_path}: {e}', exc_type=IOError, diff --git a/src/easydiffraction/datablocks/experiment/item/factory.py b/src/easydiffraction/datablocks/experiment/item/factory.py index 6406ed30..c23f60f5 100644 --- a/src/easydiffraction/datablocks/experiment/item/factory.py +++ b/src/easydiffraction/datablocks/experiment/item/factory.py @@ -260,5 +260,6 @@ def from_data_path( radiation_probe=radiation_probe, scattering_type=scattering_type, ) + expt_obj._load_ascii_data_to_experiment(data_path) return expt_obj diff --git a/src/easydiffraction/display/plotters/ascii.py b/src/easydiffraction/display/plotters/ascii.py index 8735a8a0..32ee45ed 100644 --- a/src/easydiffraction/display/plotters/ascii.py +++ b/src/easydiffraction/display/plotters/ascii.py @@ -181,3 +181,26 @@ def plot_single_crystal( print(f' {line}') print(f' {x_axis}') console.print(f'{" " * (width - 3)}{axes_labels[0]}') + + def plot_scatter( + self, + x: object, + y: object, + sy: object, + axes_labels: object, + title: str, + height: int | None = None, + ) -> None: + """Render a scatter plot with error bars in ASCII.""" + _ = x, sy # ASCII backend does not use x ticks or error bars + + if height is None: + height = DEFAULT_HEIGHT + + config = {'height': height, 'colors': [asciichartpy.blue]} + chart = asciichartpy.plot([list(y)], config) + + console.paragraph(f'{title}') + console.print(f'{axes_labels[1]} vs {axes_labels[0]}') + padded = '\n'.join(' ' + line for line in chart.splitlines()) + print(padded) diff --git a/src/easydiffraction/display/plotters/base.py b/src/easydiffraction/display/plotters/base.py index d7ca594c..67fea11f 100644 --- a/src/easydiffraction/display/plotters/base.py +++ b/src/easydiffraction/display/plotters/base.py @@ -229,3 +229,33 @@ def plot_single_crystal( Backend-specific height (text rows or pixels). """ pass + + @abstractmethod + def plot_scatter( + self, + x: object, + y: object, + sy: object, + axes_labels: object, + title: str, + height: int | None, + ) -> None: + """ + Render a scatter plot with error bars. + + Parameters + ---------- + x : object + 1-D array of x-axis values. + y : object + 1-D array of y-axis values. + sy : object + 1-D array of y uncertainties. + axes_labels : object + Pair of strings for x and y axis titles. + title : str + Figure title. + height : int | None + Backend-specific height (text rows or pixels). + """ + pass diff --git a/src/easydiffraction/display/plotters/plotly.py b/src/easydiffraction/display/plotters/plotly.py index 4df79cf0..9d559d02 100644 --- a/src/easydiffraction/display/plotters/plotly.py +++ b/src/easydiffraction/display/plotters/plotly.py @@ -369,3 +369,45 @@ def plot_single_crystal( fig = self._get_figure(data, layout) self._show_figure(fig) + + def plot_scatter( + self, + x: object, + y: object, + sy: object, + axes_labels: object, + title: str, + height: int | None = None, + ) -> None: + """Render a scatter plot with error bars via Plotly.""" + _ = height # not used by Plotly backend + + trace = go.Scatter( + x=x, + y=y, + mode='markers+lines', + marker=dict( + symbol='circle', + size=10, + line=dict(width=0.5), + color=DEFAULT_COLORS['meas'], + ), + line=dict( + width=1, + color=DEFAULT_COLORS['meas'], + ), + error_y=dict( + type='data', + array=sy, + visible=True, + ), + hovertemplate='x: %{x}
y: %{y}
', + ) + + layout = self._get_layout( + title, + axes_labels, + ) + + fig = self._get_figure(trace, layout) + self._show_figure(fig) diff --git a/src/easydiffraction/display/plotting.py b/src/easydiffraction/display/plotting.py index ec8e1d5c..e4b1ad34 100644 --- a/src/easydiffraction/display/plotting.py +++ b/src/easydiffraction/display/plotting.py @@ -569,6 +569,107 @@ def plot_meas_vs_calc( height=self.height, ) + def plot_param_series( + self, + unique_name: str, + versus_name: str | None, + experiments: object, + parameter_snapshots: dict[str, dict[str, dict]], + ) -> None: + """ + Plot a parameter's value across sequential fit results. + + Parameters + ---------- + unique_name : str + Unique name of the parameter to plot. + versus_name : str | None + Name of the diffrn descriptor to use as the x-axis (e.g. + ``'ambient_temperature'``). When ``None``, the experiment + sequence index is used instead. + experiments : object + Experiments collection for accessing diffrn conditions. + parameter_snapshots : dict[str, dict[str, dict]] + Per-experiment parameter value snapshots keyed by experiment + name, then by parameter unique name. + """ + x = [] + y = [] + sy = [] + axes_labels = [] + title = '' + + for idx, expt_name in enumerate(parameter_snapshots, start=1): + experiment = experiments[expt_name] + diffrn = experiment.diffrn + + x_axis_param = self._resolve_diffrn_descriptor(diffrn, versus_name) + + if x_axis_param is not None and x_axis_param.value is not None: + value = x_axis_param.value + else: + value = idx + x.append(value) + + param_data = parameter_snapshots[expt_name][unique_name] + y.append(param_data['value']) + sy.append(param_data['uncertainty']) + + if x_axis_param is not None: + axes_labels = [ + x_axis_param.description or x_axis_param.name, + f'Parameter value ({param_data["units"]})', + ] + else: + axes_labels = [ + 'Experiment No.', + f'Parameter value ({param_data["units"]})', + ] + + title = f"Parameter '{unique_name}' across fit results" + + self._backend.plot_scatter( + x=x, + y=y, + sy=sy, + axes_labels=axes_labels, + title=title, + height=self.height, + ) + + @staticmethod + def _resolve_diffrn_descriptor( + diffrn: object, + name: str | None, + ) -> object | None: + """ + Return the diffrn descriptor matching *name*, or ``None``. + + Parameters + ---------- + diffrn : object + The diffrn category of an experiment. + name : str | None + Descriptor name (e.g. ``'ambient_temperature'``). + + Returns + ------- + object | None + The matching ``NumericDescriptor``, or ``None`` when *name* + is ``None`` or unrecognised. + """ + if name is None: + return None + if name == 'ambient_temperature': + return diffrn.ambient_temperature + if name == 'ambient_pressure': + return diffrn.ambient_pressure + if name == 'ambient_magnetic_field': + return diffrn.ambient_magnetic_field + if name == 'ambient_electric_field': + return diffrn.ambient_electric_field + return None + class PlotterFactory(RendererFactoryBase): """Factory for plotter implementations.""" diff --git a/src/easydiffraction/io/__init__.py b/src/easydiffraction/io/__init__.py index 4e798e20..6ce45a95 100644 --- a/src/easydiffraction/io/__init__.py +++ b/src/easydiffraction/io/__init__.py @@ -1,2 +1,7 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.io.ascii import extract_data_paths_from_dir +from easydiffraction.io.ascii import extract_data_paths_from_zip +from easydiffraction.io.ascii import extract_metadata +from easydiffraction.io.ascii import load_numeric_block diff --git a/src/easydiffraction/io/ascii.py b/src/easydiffraction/io/ascii.py new file mode 100644 index 00000000..6987d217 --- /dev/null +++ b/src/easydiffraction/io/ascii.py @@ -0,0 +1,182 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Helpers for loading numeric data from ASCII files.""" + +from __future__ import annotations + +import tempfile +import zipfile +from io import StringIO +from pathlib import Path + +import numpy as np + + +def extract_data_paths_from_zip(zip_path: str | Path) -> list[str]: + """ + Extract all files from a ZIP archive and return their paths. + + Files are extracted into a temporary directory that persists for the + lifetime of the process. The returned paths are sorted + lexicographically by file name so that numbered data files (e.g. + ``scan_001.dat``, ``scan_002.dat``) appear in natural order. Hidden + files and directories (names starting with ``'.'`` or ``'__'``) are + excluded. + + Parameters + ---------- + zip_path : str | Path + Path to the ZIP archive. + + Returns + ------- + list[str] + Sorted absolute paths to the extracted data files. + + Raises + ------ + FileNotFoundError + If *zip_path* does not exist. + ValueError + If the archive contains no usable data files. + """ + zip_path = Path(zip_path) + if not zip_path.exists(): + raise FileNotFoundError(f'ZIP file not found: {zip_path}') + + # TODO: Unify mkdir with other uses in the code + extract_dir = Path(tempfile.mkdtemp(prefix='ed_zip_')) + + with zipfile.ZipFile(zip_path, 'r') as zf: + zf.extractall(extract_dir) + + paths = sorted( + str(p) + for p in extract_dir.rglob('*') + if p.is_file() and not p.name.startswith('.') and not p.name.startswith('__') + ) + + if not paths: + raise ValueError(f'No data files found in ZIP archive: {zip_path}') + + return paths + + +def extract_data_paths_from_dir( + dir_path: str | Path, + file_pattern: str = '*', +) -> list[str]: + """ + List data files in a directory and return their sorted paths. + + Hidden files (names starting with ``'.'`` or ``'__'``) are excluded. + The returned paths are sorted lexicographically by file name. + + Parameters + ---------- + dir_path : str | Path + Path to the directory containing data files. + file_pattern : str, default='*' + Glob pattern to filter files (e.g. ``'*.dat'``, ``'*.xye'``). + + Returns + ------- + list[str] + Sorted absolute paths to the matching data files. + + Raises + ------ + FileNotFoundError + If *dir_path* does not exist or is not a directory. + ValueError + If no matching data files are found. + """ + dir_path = Path(dir_path) + if not dir_path.is_dir(): + raise FileNotFoundError(f'Directory not found: {dir_path}') + + paths = sorted( + str(p) + for p in dir_path.glob(file_pattern) + if p.is_file() and not p.name.startswith('.') and not p.name.startswith('__') + ) + + if not paths: + raise ValueError(f"No files matching '{file_pattern}' found in directory: {dir_path}") + + return paths + + +def extract_metadata( + file_path: str | Path, + pattern: str, +) -> float | None: + """ + Extract a single numeric value from a file using a regex pattern. + + The entire file content is searched (not just the header). The + **first** match is used. The regex must contain exactly one capture + group whose match is convertible to ``float``. + + Parameters + ---------- + file_path : str | Path + Path to the input file. + pattern : str + Regex with one capture group that matches the numeric value. + + Returns + ------- + float | None + The extracted value, or ``None`` if the pattern did not match or + the captured text could not be converted to float. + """ + import re + + content = Path(file_path).read_text(encoding='utf-8', errors='ignore') + match = re.search(pattern, content, re.MULTILINE) + if match is None: + return None + try: + return float(match.group(1)) + except (ValueError, IndexError): + return None + + +def load_numeric_block(data_path: str | Path) -> np.ndarray: + """ + Load a numeric block from an ASCII file, skipping header lines. + + Read the file and try ``numpy.loadtxt`` starting from the first + line, then the second, etc., until the load succeeds. This allows + files with an arbitrary number of non-numeric header lines to be + parsed without prior knowledge of the format. + + Parameters + ---------- + data_path : str | Path + Path to the ASCII data file. + + Returns + ------- + np.ndarray + 2-D array of the parsed numeric data. + + Raises + ------ + IOError + If no contiguous numeric block can be found in the file. + """ + data_path = Path(data_path) + lines = data_path.read_text().splitlines() + + last_error: Exception | None = None + for start in range(len(lines)): + try: + return np.loadtxt(StringIO('\n'.join(lines[start:]))) + except Exception as e: # noqa: BLE001 + last_error = e + + raise IOError( + f'Failed to read numeric data from {data_path}: {last_error}', + ) from last_error diff --git a/src/easydiffraction/project/project.py b/src/easydiffraction/project/project.py index af5a5922..ace0fc95 100644 --- a/src/easydiffraction/project/project.py +++ b/src/easydiffraction/project/project.py @@ -156,7 +156,7 @@ def load(self, dir_path: str) -> None: def save(self) -> None: """Save the project into the existing project directory.""" - if not self._info.path: + if self._info.path is None: log.error('Project path not specified. Use save_as() to define the path first.') return @@ -174,12 +174,10 @@ def save(self) -> None: # Save structures sm_dir = self._info.path / 'structures' sm_dir.mkdir(parents=True, exist_ok=True) - # Iterate over structure objects (MutableMapping iter gives - # keys) + console.print('├── 📁 structures/') for structure in self.structures.values(): file_name: str = f'{structure.name}.cif' file_path = sm_dir / file_name - console.print('├── 📁 structures') with file_path.open('w') as f: f.write(structure.as_cif) console.print(f'│ └── 📄 {file_name}') @@ -187,10 +185,10 @@ def save(self) -> None: # Save experiments expt_dir = self._info.path / 'experiments' expt_dir.mkdir(parents=True, exist_ok=True) + console.print('├── 📁 experiments/') for experiment in self.experiments.values(): file_name: str = f'{experiment.name}.cif' file_path = expt_dir / file_name - console.print('├── 📁 experiments') with file_path.open('w') as f: f.write(experiment.as_cif) console.print(f'│ └── 📄 {file_name}') @@ -333,3 +331,27 @@ def plot_meas_vs_calc( show_residual=show_residual, x=x, ) + + def plot_param_series(self, param: object, versus: object | None = None) -> None: + """ + Plot a parameter's value across sequential fit results. + + Parameters + ---------- + param : object + Parameter descriptor whose ``unique_name`` identifies the + values to plot. + versus : object | None, default=None + A diffrn descriptor (e.g. + ``expt.diffrn.ambient_temperature``) whose value is used as + the x-axis for each experiment. When ``None``, the + experiment sequence number is used instead. + """ + unique_name = param.unique_name + versus_name = versus.name if versus is not None else None + self.plotter.plot_param_series( + unique_name, + versus_name, + self.experiments, + self.analysis._parameter_snapshots, + ) diff --git a/src/easydiffraction/project/project_info.py b/src/easydiffraction/project/project_info.py index d062d522..dcba2fba 100644 --- a/src/easydiffraction/project/project_info.py +++ b/src/easydiffraction/project/project_info.py @@ -25,7 +25,7 @@ def __init__( self._name = name self._title = title self._description = description - self._path: pathlib.Path = pathlib.Path.cwd() + self._path: pathlib.Path | None = None # pathlib.Path.cwd() self._created: datetime.datetime = datetime.datetime.now() self._last_modified: datetime.datetime = datetime.datetime.now() @@ -86,7 +86,7 @@ def description(self, value: str) -> None: self._description = ' '.join(value.split()) @property - def path(self) -> pathlib.Path: + def path(self) -> pathlib.Path | None: """Return the project path as a Path object.""" return self._path diff --git a/src/easydiffraction/utils/utils.py b/src/easydiffraction/utils/utils.py index 3e8c5f1e..5527629f 100644 --- a/src/easydiffraction/utils/utils.py +++ b/src/easydiffraction/utils/utils.py @@ -6,7 +6,6 @@ import functools import json import pathlib -import re import urllib.request from importlib.metadata import PackageNotFoundError from importlib.metadata import version @@ -75,7 +74,7 @@ def _fetch_data_index() -> dict: _validate_url(index_url) # macOS: sha256sum index.json - index_hash = 'sha256:9aceaf51d298992058c80903283c9a83543329a063692d49b7aaee1156e76884' + index_hash = 'sha256:f421aab32ec532782dc62f4440a97320e5cec23b9e64f5ae3f8a3e818d013430' destination_dirname = 'easydiffraction' destination_fname = 'data-index.json' cache_dir = pooch.os_cache(destination_dirname) @@ -696,39 +695,6 @@ def sin_theta_over_lambda_to_d_spacing(sin_theta_over_lambda: object) -> object: return d -def get_value_from_xye_header(file_path: str, key: str) -> float: - """ - Extract a float from the first line of the file by key. - - Parameters - ---------- - file_path : str - Path to the input file. - key : str - The key to extract ('DIFC' or 'two_theta'). - - Returns - ------- - float - The extracted value. - - Raises - ------ - ValueError - If the key is not found. - """ - pattern = rf'{key}\s*=\s*([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)' - - with pathlib.Path(file_path).open('r') as f: - first_line = f.readline() - - match = re.search(pattern, first_line) - if match: - 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. diff --git a/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py b/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py index 5045dd27..7a98c15d 100644 --- a/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py +++ b/tests/integration/fitting/test_powder-diffraction_constant-wavelength.py @@ -293,8 +293,8 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None: ) # Set constraints - project.analysis.constraints.create(lhs_alias='biso_Ba', rhs_expr='biso_La') - project.analysis.constraints.create(lhs_alias='occ_Ba', rhs_expr='1 - occ_La') + project.analysis.constraints.create(expression='biso_Ba = biso_La') + project.analysis.constraints.create(expression='occ_Ba = 1 - occ_La') # Apply constraints project.analysis.apply_constraints() diff --git a/tests/unit/easydiffraction/analysis/categories/test_constraints.py b/tests/unit/easydiffraction/analysis/categories/test_constraints.py index 443f9b1b..15dddc4f 100644 --- a/tests/unit/easydiffraction/analysis/categories/test_constraints.py +++ b/tests/unit/easydiffraction/analysis/categories/test_constraints.py @@ -7,10 +7,10 @@ def test_constraint_creation_and_collection(): c = Constraint() - c.lhs_alias = 'a' - c.rhs_expr = 'b + c' - assert c.lhs_alias.value == 'a' + c.expression = 'a = b + c' + assert c.lhs_alias == 'a' + assert c.rhs_expr == 'b + c' coll = Constraints() - coll.create(lhs_alias='a', rhs_expr='b + c') + coll.create(expression='a = b + c') assert 'a' in coll.names - assert coll['a'].rhs_expr.value == 'b + c' + assert coll['a'].rhs_expr == 'b + c' diff --git a/tests/unit/easydiffraction/core/test_collection.py b/tests/unit/easydiffraction/core/test_collection.py index 817b76dd..bbbd9c65 100644 --- a/tests/unit/easydiffraction/core/test_collection.py +++ b/tests/unit/easydiffraction/core/test_collection.py @@ -84,6 +84,49 @@ def as_cif(self) -> str: c.remove('nonexistent') +def test_collection_getitem_by_int_index(): + """Verify items can be retrieved by positional index.""" + import pytest + + 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 + + # Forward indexing + assert c[0] is a + assert c[1] is b + + # Negative indexing + assert c[-1] is b + assert c[-2] is a + + # Out of range + with pytest.raises(IndexError): + c[2] + + # Invalid key type + with pytest.raises(TypeError): + c[3.14] + + def test_collection_datablock_keyed_items(): """Verify __setitem__/__delitem__/__contains__ work for datablock-keyed items.""" from easydiffraction.core.collection import CollectionBase diff --git a/tests/unit/easydiffraction/project/test_project_save.py b/tests/unit/easydiffraction/project/test_project_save.py index 421e8928..ac8b9895 100644 --- a/tests/unit/easydiffraction/project/test_project_save.py +++ b/tests/unit/easydiffraction/project/test_project_save.py @@ -3,14 +3,14 @@ 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 + # ProjectInfo.path defaults to None; save() requires save_as() first from easydiffraction.project.project import Project monkeypatch.chdir(tmp_path) p = Project() - p.save() + p.save_as(str(tmp_path)) out = capsys.readouterr().out - # It should announce saving and create the three core files in cwd + # It should announce saving and create the three core files assert 'Saving project' in out assert (tmp_path / 'project.cif').exists() assert (tmp_path / 'analysis.cif').exists() diff --git a/tests/unit/easydiffraction/test___init__.py b/tests/unit/easydiffraction/test___init__.py index 75f273e1..e42e801c 100644 --- a/tests/unit/easydiffraction/test___init__.py +++ b/tests/unit/easydiffraction/test___init__.py @@ -17,7 +17,7 @@ def test_lazy_attributes_resolve_and_are_accessible(): # Access utility functions from utils via lazy getattr assert callable(ed.show_version) - assert callable(ed.get_value_from_xye_header) + assert callable(ed.extract_metadata) # Import once to exercise __getattr__; subsequent access should be cached by Python _ = ed.Project diff --git a/tests/unit/easydiffraction/utils/test_utils.py b/tests/unit/easydiffraction/utils/test_utils.py index 48d5a182..ab2c9bab 100644 --- a/tests/unit/easydiffraction/utils/test_utils.py +++ b/tests/unit/easydiffraction/utils/test_utils.py @@ -68,20 +68,18 @@ def test_str_to_ufloat_no_esd_defaults_nan(): 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 +def test_extract_metadata(tmp_path): + import easydiffraction.io.ascii as ascii_io - text = 'DIFC = 123.45 two_theta = 67.89\nrest of file\n' + 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) + difc = ascii_io.extract_metadata(str(p), r'DIFC\s*=\s*([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)') + two_theta = ascii_io.extract_metadata( + str(p), r'two_theta\s*=\s*([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)' + ) + assert np.isclose(difc, 123.45) + assert np.isclose(two_theta, 67.89) def test_validate_url_rejects_non_http_https(): diff --git a/tools/test_scripts.py b/tools/test_scripts.py index e24b28e2..2dcca083 100644 --- a/tools/test_scripts.py +++ b/tools/test_scripts.py @@ -51,6 +51,7 @@ def test_script_runs(script_path: Path): env=env, capture_output=True, text=True, + encoding='utf-8', ) if result.returncode != 0: details = (result.stdout or '') + (result.stderr or '') From 5a82427971384906bd7ab47a69e56335cfec45a2 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 31 Mar 2026 09:37:32 +0200 Subject: [PATCH 5/5] Add output verbosity control (#131) * Create and use load_numeric_block method in io/ascii.py * Disable some rules for docs/ * Initial implementation of the new tutorial * Add add_from_zip_path to Experiments collection * Update data index hash * Use data from web repo * Use another dataset * Save project after every fit * Clean up * Initial implementation of plotting parameter evolution * Replace conditions category with diffrn using CIF naming * Simplify extract_metadata API and make data loading explicit * Fix fit_results to store snapshot parameters per experiment * Clean up tutorial ed-17.py duplicate plot_param calls * Fix test to use save_as() matching current ProjectInfo.path default * Add integer index support to CollectionBase.__getitem__ * Rename plot_param to plot_param_series with versus descriptor * Clean up tutorial * Update docs with new notebook * Simplify constraints API to single expression string * Fix docstring lint errors in CollectionBase.__getitem__ * New dataset and update tutorials * Update T-scan notebook * Add fit verbosity parameter with full, short and silent modes * Update notebook * Add project-level verbosity with full, short and silent modes * Improve grammar and consistency in ed-17 tutorial * Fix docstring lint errors in data loading methods * Increase notebook cell timeout to 1200s for T-scan tutorial --- .github/workflows/tutorial-tests-colab.yaml | 2 +- docs/architecture/architecture.md | 42 +++++ docs/docs/tutorials/ed-17.ipynb | 163 ++++++++++++------ docs/docs/tutorials/ed-17.py | 47 +++-- pixi.toml | 4 +- src/easydiffraction/analysis/analysis.py | 84 +++++++-- .../analysis/fit_helpers/tracking.py | 14 +- src/easydiffraction/analysis/fitting.py | 6 +- .../analysis/minimizers/base.py | 15 +- .../datablocks/experiment/collection.py | 10 ++ .../datablocks/experiment/item/base.py | 7 +- .../datablocks/experiment/item/bragg_pd.py | 23 +-- .../datablocks/experiment/item/bragg_sc.py | 37 ++-- .../datablocks/experiment/item/factory.py | 14 +- .../datablocks/experiment/item/total_pd.py | 25 ++- src/easydiffraction/project/project.py | 27 +++ src/easydiffraction/utils/enums.py | 30 ++++ .../easydiffraction/analysis/test_fitting.py | 4 +- .../datablocks/experiment/item/test_base.py | 4 +- .../experiment/item/test_bragg_sc.py | 4 +- .../datablocks/experiment/test_collection.py | 4 +- .../easydiffraction/project/test_project.py | 29 ++++ .../unit/easydiffraction/utils/test_enums.py | 27 +++ 23 files changed, 506 insertions(+), 116 deletions(-) create mode 100644 src/easydiffraction/utils/enums.py create mode 100644 tests/unit/easydiffraction/utils/test_enums.py diff --git a/.github/workflows/tutorial-tests-colab.yaml b/.github/workflows/tutorial-tests-colab.yaml index 35c4a753..30966aaa 100644 --- a/.github/workflows/tutorial-tests-colab.yaml +++ b/.github/workflows/tutorial-tests-colab.yaml @@ -41,5 +41,5 @@ jobs: - name: Check if Jupyter Notebooks run without errors run: > - python -m pytest --nbmake docs/tutorials/ --nbmake-timeout=600 --color=yes + python -m pytest --nbmake docs/tutorials/ --nbmake-timeout=1200 --color=yes -n=auto diff --git a/docs/architecture/architecture.md b/docs/architecture/architecture.md index 65187b07..3067e6a6 100644 --- a/docs/architecture/architecture.md +++ b/docs/architecture/architecture.md @@ -689,6 +689,7 @@ It owns and coordinates all components: | `project.analysis` | `Analysis` | Calculator, minimiser, fitting | | `project.summary` | `Summary` | Report generation | | `project.plotter` | `Plotter` | Visualisation | +| `project.verbosity` | `str` | Console output level (full/short/silent) | ### 7.1 Data Flow @@ -721,6 +722,47 @@ project_dir/ └── hrpt.cif # One file per experiment ``` +### 7.3 Verbosity + +`Project.verbosity` controls how much console output operations produce. +It is backed by `VerbosityEnum` (in `utils/enums.py`) and accepts three +values: + +| Level | Enum member | Behaviour | +| -------- | ---------------------- | -------------------------------------------------- | +| `full` | `VerbosityEnum.FULL` | Multi-line output with headers, tables, and detail | +| `short` | `VerbosityEnum.SHORT` | One-line status message per action | +| `silent` | `VerbosityEnum.SILENT` | No console output | + +The default is `'full'`. + +```python +project.verbosity = 'short' +``` + +**Resolution order:** methods that produce console output (e.g. +`analysis.fit()`, `experiments.add_from_data_path()`) accept an optional +`verbosity` keyword argument. When the argument is `None` (the default), +the method reads `project.verbosity`. When a string is passed, it +overrides the project-level setting for that single call. + +```python +# Use project-level default for all operations +project.verbosity = 'short' +project.analysis.fit() # → short mode + +# Override for a single call +project.analysis.fit(verbosity='silent') # → silent, project stays short +``` + +**Output styles per level:** + +- **Data loading** — `full`: paragraph header + detail line; `short`: + `✅ Data loaded: Experiment 🔬 'name'. N points.`; `silent`: nothing. +- **Fitting** — `full`: per-iteration progress table with improvement + percentages; `short`: one-row-per-experiment summary table; `silent`: + nothing. + --- ## 8. User-Facing API Patterns diff --git a/docs/docs/tutorials/ed-17.ipynb b/docs/docs/tutorials/ed-17.ipynb index 566c1783..a6500f67 100644 --- a/docs/docs/tutorials/ed-17.ipynb +++ b/docs/docs/tutorials/ed-17.ipynb @@ -7,8 +7,8 @@ "source": [ "# Structure Refinement: Co2SiO4, D20 (T-scan)\n", "\n", - "This example demonstrates a Rietveld refinement of Co2SiO4 crystal\n", - "structure using constant wavelength neutron powder diffraction data\n", + "This example demonstrates a Rietveld refinement of the Co2SiO4 crystal\n", + "structure using constant-wavelength neutron powder diffraction data\n", "from D20 at ILL. A sequential refinement of the same structure against\n", "a temperature scan is performed to show how to manage multiple\n", "experiments in a project." @@ -39,8 +39,7 @@ "source": [ "## Step 1: Define Project\n", "\n", - "The project object is used to manage the structure, experiment, and\n", - "analysis." + "The project object manages structures, experiments, and analysis." ] }, { @@ -50,7 +49,6 @@ "metadata": {}, "outputs": [], "source": [ - "# Create minimal project without name and description\n", "project = ed.Project()" ] }, @@ -58,6 +56,25 @@ "cell_type": "markdown", "id": "5", "metadata": {}, + "source": [ + "Set output verbosity level to \"short\" to show only one-line status\n", + "messages during the analysis process." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "project.verbosity = 'short'" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, "source": [ "## Step 2: Define Crystal Structure\n", "\n", @@ -70,7 +87,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6", + "id": "8", "metadata": {}, "outputs": [], "source": [ @@ -80,7 +97,7 @@ }, { "cell_type": "markdown", - "id": "7", + "id": "9", "metadata": {}, "source": [ "#### Set Space Group" @@ -89,7 +106,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8", + "id": "10", "metadata": {}, "outputs": [], "source": [ @@ -99,7 +116,7 @@ }, { "cell_type": "markdown", - "id": "9", + "id": "11", "metadata": {}, "source": [ "#### Set Unit Cell" @@ -108,7 +125,7 @@ { "cell_type": "code", "execution_count": null, - "id": "10", + "id": "12", "metadata": {}, "outputs": [], "source": [ @@ -119,7 +136,7 @@ }, { "cell_type": "markdown", - "id": "11", + "id": "13", "metadata": {}, "source": [ "#### Set Atom Sites" @@ -128,7 +145,7 @@ { "cell_type": "code", "execution_count": null, - "id": "12", + "id": "14", "metadata": {}, "outputs": [], "source": [ @@ -190,13 +207,13 @@ }, { "cell_type": "markdown", - "id": "13", + "id": "15", "metadata": {}, "source": [ - "## Define Experiment\n", + "## Step 3: Define Experiments\n", "\n", "This section shows how to add experiments, configure their parameters,\n", - "and link the structures defined in the previous step.\n", + "and link the structures defined above.\n", "\n", "#### Download Measured Data" ] @@ -204,7 +221,7 @@ { "cell_type": "code", "execution_count": null, - "id": "14", + "id": "16", "metadata": {}, "outputs": [], "source": [ @@ -213,7 +230,7 @@ }, { "cell_type": "markdown", - "id": "15", + "id": "17", "metadata": {}, "source": [ "#### Create Experiments and Set Temperature" @@ -222,7 +239,7 @@ { "cell_type": "code", "execution_count": null, - "id": "16", + "id": "18", "metadata": {}, "outputs": [], "source": [ @@ -242,7 +259,7 @@ }, { "cell_type": "markdown", - "id": "17", + "id": "19", "metadata": {}, "source": [ "#### Set Instrument" @@ -251,7 +268,7 @@ { "cell_type": "code", "execution_count": null, - "id": "18", + "id": "20", "metadata": {}, "outputs": [], "source": [ @@ -262,7 +279,7 @@ }, { "cell_type": "markdown", - "id": "19", + "id": "21", "metadata": {}, "source": [ "#### Set Peak Profile" @@ -271,7 +288,7 @@ { "cell_type": "code", "execution_count": null, - "id": "20", + "id": "22", "metadata": {}, "outputs": [], "source": [ @@ -284,7 +301,7 @@ }, { "cell_type": "markdown", - "id": "21", + "id": "23", "metadata": {}, "source": [ "#### Set Excluded Regions" @@ -293,7 +310,7 @@ { "cell_type": "code", "execution_count": null, - "id": "22", + "id": "24", "metadata": {}, "outputs": [], "source": [ @@ -304,7 +321,7 @@ }, { "cell_type": "markdown", - "id": "23", + "id": "25", "metadata": {}, "source": [ "#### Set Background" @@ -313,7 +330,7 @@ { "cell_type": "code", "execution_count": null, - "id": "24", + "id": "26", "metadata": {}, "outputs": [], "source": [ @@ -336,7 +353,7 @@ }, { "cell_type": "markdown", - "id": "25", + "id": "27", "metadata": {}, "source": [ "#### Set Linked Phases" @@ -345,7 +362,7 @@ { "cell_type": "code", "execution_count": null, - "id": "26", + "id": "28", "metadata": {}, "outputs": [], "source": [ @@ -355,18 +372,18 @@ }, { "cell_type": "markdown", - "id": "27", + "id": "29", "metadata": {}, "source": [ - "## Perform Analysis\n", + "## Step 4: Perform Analysis\n", "\n", - "This section shows the analysis process, including how to set up\n", - "calculation and fitting engines." + "This section shows how to set free parameters, define constraints,\n", + "and run the refinement." ] }, { "cell_type": "markdown", - "id": "28", + "id": "30", "metadata": {}, "source": [ "#### Set Free Parameters" @@ -375,7 +392,7 @@ { "cell_type": "code", "execution_count": null, - "id": "29", + "id": "31", "metadata": {}, "outputs": [], "source": [ @@ -406,7 +423,7 @@ { "cell_type": "code", "execution_count": null, - "id": "30", + "id": "32", "metadata": {}, "outputs": [], "source": [ @@ -426,7 +443,7 @@ }, { "cell_type": "markdown", - "id": "31", + "id": "33", "metadata": {}, "source": [ "#### Set Constraints\n", @@ -437,7 +454,7 @@ { "cell_type": "code", "execution_count": null, - "id": "32", + "id": "34", "metadata": {}, "outputs": [], "source": [ @@ -453,7 +470,7 @@ }, { "cell_type": "markdown", - "id": "33", + "id": "35", "metadata": {}, "source": [ "Set constraints." @@ -462,7 +479,7 @@ { "cell_type": "code", "execution_count": null, - "id": "34", + "id": "36", "metadata": {}, "outputs": [], "source": [ @@ -473,7 +490,7 @@ }, { "cell_type": "markdown", - "id": "35", + "id": "37", "metadata": {}, "source": [ "Apply constraints." @@ -482,7 +499,7 @@ { "cell_type": "code", "execution_count": null, - "id": "36", + "id": "38", "metadata": {}, "outputs": [], "source": [ @@ -491,16 +508,16 @@ }, { "cell_type": "markdown", - "id": "37", + "id": "39", "metadata": {}, "source": [ - "#### Set Fit Mode and Weights" + "#### Set Fit Mode" ] }, { "cell_type": "code", "execution_count": null, - "id": "38", + "id": "40", "metadata": {}, "outputs": [], "source": [ @@ -509,7 +526,7 @@ }, { "cell_type": "markdown", - "id": "39", + "id": "41", "metadata": {}, "source": [ "#### Run Fitting" @@ -518,7 +535,7 @@ { "cell_type": "code", "execution_count": null, - "id": "40", + "id": "42", "metadata": {}, "outputs": [], "source": [ @@ -527,7 +544,7 @@ }, { "cell_type": "markdown", - "id": "41", + "id": "43", "metadata": {}, "source": [ "#### Plot Measured vs Calculated" @@ -536,7 +553,7 @@ { "cell_type": "code", "execution_count": null, - "id": "42", + "id": "44", "metadata": {}, "outputs": [], "source": [ @@ -546,26 +563,36 @@ }, { "cell_type": "markdown", - "id": "43", + "id": "45", "metadata": {}, "source": [ - "#### Plot parameters evolution" + "#### Plot Parameter Evolution\n", + "\n", + "Define the quantity to use as the x-axis in the following plots." ] }, { "cell_type": "code", "execution_count": null, - "id": "44", + "id": "46", "metadata": {}, "outputs": [], "source": [ "temperature = project.experiments[0].diffrn.ambient_temperature" ] }, + { + "cell_type": "markdown", + "id": "47", + "metadata": {}, + "source": [ + "Plot unit cell parameters vs. temperature." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "45", + "id": "48", "metadata": {}, "outputs": [], "source": [ @@ -574,10 +601,18 @@ "project.plot_param_series(structure.cell.length_c, versus=temperature)" ] }, + { + "cell_type": "markdown", + "id": "49", + "metadata": {}, + "source": [ + "Plot isotropic displacement parameters vs. temperature." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "46", + "id": "50", "metadata": {}, "outputs": [], "source": [ @@ -587,6 +622,28 @@ "project.plot_param_series(structure.atom_sites['O2'].b_iso, versus=temperature)\n", "project.plot_param_series(structure.atom_sites['O3'].b_iso, versus=temperature)" ] + }, + { + "cell_type": "markdown", + "id": "51", + "metadata": {}, + "source": [ + "Plot selected fractional coordinates vs. temperature." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52", + "metadata": {}, + "outputs": [], + "source": [ + "project.plot_param_series(structure.atom_sites['Co2'].fract_x, versus=temperature)\n", + "project.plot_param_series(structure.atom_sites['Co2'].fract_z, versus=temperature)\n", + "project.plot_param_series(structure.atom_sites['O1'].fract_z, versus=temperature)\n", + "project.plot_param_series(structure.atom_sites['O2'].fract_z, versus=temperature)\n", + "project.plot_param_series(structure.atom_sites['O3'].fract_z, versus=temperature)" + ] } ], "metadata": { diff --git a/docs/docs/tutorials/ed-17.py b/docs/docs/tutorials/ed-17.py index 0f059849..af06031f 100644 --- a/docs/docs/tutorials/ed-17.py +++ b/docs/docs/tutorials/ed-17.py @@ -1,8 +1,8 @@ # %% [markdown] # # Structure Refinement: Co2SiO4, D20 (T-scan) # -# This example demonstrates a Rietveld refinement of Co2SiO4 crystal -# structure using constant wavelength neutron powder diffraction data +# This example demonstrates a Rietveld refinement of the Co2SiO4 crystal +# structure using constant-wavelength neutron powder diffraction data # from D20 at ILL. A sequential refinement of the same structure against # a temperature scan is performed to show how to manage multiple # experiments in a project. @@ -16,13 +16,18 @@ # %% [markdown] # ## Step 1: Define Project # -# The project object is used to manage the structure, experiment, and -# analysis. +# The project object manages structures, experiments, and analysis. # %% -# Create minimal project without name and description project = ed.Project() +# %% [markdown] +# Set output verbosity level to "short" to show only one-line status +# messages during the analysis process. + +# %% +project.verbosity = 'short' + # %% [markdown] # ## Step 2: Define Crystal Structure # @@ -110,10 +115,10 @@ ) # %% [markdown] -# ## Define Experiment +# ## Step 3: Define Experiments # # This section shows how to add experiments, configure their parameters, -# and link the structures defined in the previous step. +# and link the structures defined above. # # #### Download Measured Data @@ -191,10 +196,10 @@ expt.linked_phases.create(id='cosio', scale=1.2) # %% [markdown] -# ## Perform Analysis +# ## Step 4: Perform Analysis # -# This section shows the analysis process, including how to set up -# calculation and fitting engines. +# This section shows how to set free parameters, define constraints, +# and run the refinement. # %% [markdown] # #### Set Free Parameters @@ -267,7 +272,7 @@ project.analysis.apply_constraints() # %% [markdown] -# #### Set Fit Mode and Weights +# #### Set Fit Mode # %% project.analysis.fit_mode.mode = 'single' @@ -286,19 +291,37 @@ project.plot_meas_vs_calc(expt_name=last_expt_name, show_residual=True) # %% [markdown] -# #### Plot parameters evolution +# #### Plot Parameter Evolution +# +# Define the quantity to use as the x-axis in the following plots. # %% temperature = project.experiments[0].diffrn.ambient_temperature +# %% [markdown] +# Plot unit cell parameters vs. temperature. + # %% project.plot_param_series(structure.cell.length_a, versus=temperature) project.plot_param_series(structure.cell.length_b, versus=temperature) project.plot_param_series(structure.cell.length_c, versus=temperature) +# %% [markdown] +# Plot isotropic displacement parameters vs. temperature. + # %% project.plot_param_series(structure.atom_sites['Co1'].b_iso, versus=temperature) project.plot_param_series(structure.atom_sites['Si'].b_iso, versus=temperature) project.plot_param_series(structure.atom_sites['O1'].b_iso, versus=temperature) project.plot_param_series(structure.atom_sites['O2'].b_iso, versus=temperature) project.plot_param_series(structure.atom_sites['O3'].b_iso, versus=temperature) + +# %% [markdown] +# Plot selected fractional coordinates vs. temperature. + +# %% +project.plot_param_series(structure.atom_sites['Co2'].fract_x, versus=temperature) +project.plot_param_series(structure.atom_sites['Co2'].fract_z, versus=temperature) +project.plot_param_series(structure.atom_sites['O1'].fract_z, versus=temperature) +project.plot_param_series(structure.atom_sites['O2'].fract_z, versus=temperature) +project.plot_param_series(structure.atom_sites['O3'].fract_z, versus=temperature) diff --git a/pixi.toml b/pixi.toml index 2cbf09c5..37952beb 100644 --- a/pixi.toml +++ b/pixi.toml @@ -95,7 +95,7 @@ default = { features = ['default', 'py-max'] } unit-tests = 'python -m pytest tests/unit/ --color=yes -v' integration-tests = 'python -m pytest tests/integration/ --color=yes -n auto -v' script-tests = 'python -m pytest tools/test_scripts.py --color=yes -n auto -v' -notebook-tests = 'python -m pytest --nbmake docs/docs/tutorials/ --nbmake-timeout=600 --color=yes -n auto -v' +notebook-tests = 'python -m pytest --nbmake docs/docs/tutorials/ --nbmake-timeout=1200 --color=yes -n auto -v' test = { depends-on = ['unit-tests'] } @@ -169,7 +169,7 @@ cov = { depends-on = [ notebook-convert = 'jupytext docs/docs/tutorials/*.py --from py:percent --to ipynb' notebook-strip = 'nbstripout docs/docs/tutorials/*.ipynb' notebook-tweak = 'python tools/tweak_notebooks.py tutorials/' -notebook-exec = 'python -m pytest --nbmake docs/docs/tutorials/ --nbmake-timeout=600 --overwrite --color=yes -n auto -v' +notebook-exec = 'python -m pytest --nbmake docs/docs/tutorials/ --nbmake-timeout=1200 --overwrite --color=yes -n auto -v' notebook-prepare = { depends-on = [ #'notebook-convert', diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index f97c2e13..c87fc545 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -20,6 +20,7 @@ from easydiffraction.core.variable import StringDescriptor from easydiffraction.datablocks.experiment.collection import Experiments from easydiffraction.display.tables import TableRenderer +from easydiffraction.utils.enums import VerbosityEnum from easydiffraction.utils.logging import console from easydiffraction.utils.logging import log from easydiffraction.utils.utils import render_cif @@ -576,7 +577,7 @@ def apply_constraints(self) -> None: self.constraints_handler.set_constraints(self.constraints) self.constraints_handler.apply() - def fit(self) -> None: + def fit(self, verbosity: str | None = None) -> None: """ Execute fitting for all experiments. @@ -593,11 +594,21 @@ def fit(self) -> None: programmatically (e.g., ``analysis.fit_results.reduced_chi_square``). - Example:: - - project.analysis.fit() project.analysis.show_fit_results() # - Display results + Parameters + ---------- + verbosity : str | None, default=None + Console output verbosity: ``'full'`` for detailed per- + experiment progress, ``'short'`` for a + one-row-per-experiment summary table, or ``'silent'`` for no + output. When ``None``, uses ``project.verbosity``. + + Raises + ------ + NotImplementedError + If the fit mode is not ``'single'`` or ``'joint'``. """ + verb = VerbosityEnum(verbosity if verbosity is not None else self.project.verbosity) + structures = self.project.structures if not structures: log.warning('No structures found in the project. Cannot run fit.') @@ -615,24 +626,52 @@ def fit(self) -> None: if not len(self._joint_fit_experiments): for id in experiments.names: self._joint_fit_experiments.create(id=id, weight=0.5) - console.paragraph( - f"Using all experiments 🔬 {experiments.names} for '{mode.value}' fitting" - ) + if verb is not VerbosityEnum.SILENT: + console.paragraph( + f"Using all experiments 🔬 {experiments.names} for '{mode.value}' fitting" + ) self.fitter.fit( structures, experiments, weights=self._joint_fit_experiments, analysis=self, + verbosity=verb, ) # After fitting, get the results self.fit_results = self.fitter.results elif mode is FitModeEnum.SINGLE: + expt_names = experiments.names + num_expts = len(expt_names) + + # Short mode: print header and create display handle once + short_headers = ['experiment', 'χ²', 'iterations', 'status'] + short_alignments = ['left', 'right', 'right', 'center'] + short_rows: list[list[str]] = [] + short_display_handle: object | None = None + if verb is VerbosityEnum.SHORT: + from easydiffraction.analysis.fit_helpers.tracking import _make_display_handle + + first = expt_names[0] + last = expt_names[-1] + minimizer_name = self.fitter.selection + console.paragraph( + f"Using {num_expts} experiments 🔬 from '{first}' to " + f"'{last}' for '{mode.value}' fitting" + ) + console.print(f"🚀 Starting fit process with '{minimizer_name}'...") + console.print('📈 Goodness-of-fit (reduced χ²) per experiment:') + short_display_handle = _make_display_handle() + # TODO: Find a better way without creating dummy # experiments? - for expt_name in experiments.names: - console.paragraph(f"Using experiment 🔬 '{expt_name}' for '{mode.value}' fitting") + for _idx, expt_name in enumerate(expt_names, start=1): + if verb is VerbosityEnum.FULL: + console.paragraph( + f"Using experiment 🔬 '{expt_name}' for '{mode.value}' fitting" + ) + experiment = experiments[expt_name] dummy_experiments = Experiments() # TODO: Find a better name @@ -646,6 +685,7 @@ def fit(self) -> None: structures, dummy_experiments, analysis=self, + verbosity=verb, ) # After fitting, snapshot parameter values before @@ -661,6 +701,30 @@ def fit(self) -> None: self._parameter_snapshots[expt_name] = snapshot self.fit_results = results + # Short mode: append one summary row and update in-place + if verb is VerbosityEnum.SHORT: + chi2_str = ( + f'{results.reduced_chi_square:.2f}' + if results.reduced_chi_square is not None + else '—' + ) + iters = str(self.fitter.minimizer.tracker.best_iteration or 0) + status = '✅' if results.success else '❌' + short_rows.append([expt_name, chi2_str, iters, status]) + render_table( + columns_headers=short_headers, + columns_alignment=short_alignments, + columns_data=short_rows, + display_handle=short_display_handle, + ) + + # Short mode: close the display handle + if short_display_handle is not None and hasattr(short_display_handle, 'close'): + from contextlib import suppress + + with suppress(Exception): + short_display_handle.close() + else: raise NotImplementedError(f'Fit mode {mode.value} not implemented yet.') diff --git a/src/easydiffraction/analysis/fit_helpers/tracking.py b/src/easydiffraction/analysis/fit_helpers/tracking.py index eb32f3ea..804dc536 100644 --- a/src/easydiffraction/analysis/fit_helpers/tracking.py +++ b/src/easydiffraction/analysis/fit_helpers/tracking.py @@ -19,6 +19,7 @@ clear_output = None from easydiffraction.analysis.fit_helpers.metrics import calculate_reduced_chi_square +from easydiffraction.utils.enums import VerbosityEnum from easydiffraction.utils.environment import in_jupyter from easydiffraction.utils.utils import render_table @@ -101,6 +102,7 @@ def __init__(self) -> None: self._best_chi2: Optional[float] = None self._best_iteration: Optional[int] = None self._fitting_time: Optional[float] = None + self._verbosity: VerbosityEnum = VerbosityEnum.FULL self._df_rows: List[List[str]] = [] self._display_handle: Optional[object] = None @@ -223,6 +225,11 @@ def start_tracking(self, minimizer_name: str) -> None: minimizer_name : str Name of the minimizer used for the run. """ + if self._verbosity is VerbosityEnum.SILENT: + return + if self._verbosity is VerbosityEnum.SHORT: + return + console.print(f"🚀 Starting fit process with '{minimizer_name}'...") console.print('📈 Goodness-of-fit (reduced χ²) change:') @@ -247,9 +254,11 @@ def add_tracking_info(self, row: List[str]) -> None: row : List[str] Columns corresponding to DEFAULT_HEADERS. """ + self._df_rows.append(row) + if self._verbosity is not VerbosityEnum.FULL: + return # Append and update via the active handle (Jupyter or # terminal live) - self._df_rows.append(row) render_table( columns_headers=DEFAULT_HEADERS, columns_alignment=DEFAULT_ALIGNMENTS, @@ -267,6 +276,9 @@ def finish_tracking(self) -> None: ] self.add_tracking_info(row) + if self._verbosity is not VerbosityEnum.FULL: + return + # Close terminal live if used if self._display_handle is not None and hasattr(self._display_handle, 'close'): with suppress(Exception): diff --git a/src/easydiffraction/analysis/fitting.py b/src/easydiffraction/analysis/fitting.py index fa6c0368..de6dcdaa 100644 --- a/src/easydiffraction/analysis/fitting.py +++ b/src/easydiffraction/analysis/fitting.py @@ -14,6 +14,7 @@ from easydiffraction.core.variable import Parameter from easydiffraction.datablocks.experiment.collection import Experiments from easydiffraction.datablocks.structure.collection import Structures +from easydiffraction.utils.enums import VerbosityEnum if TYPE_CHECKING: from easydiffraction.analysis.fit_helpers.reporting import FitResults @@ -34,6 +35,7 @@ def fit( experiments: Experiments, weights: Optional[np.array] = None, analysis: object = None, + verbosity: VerbosityEnum = VerbosityEnum.FULL, ) -> None: """ Run the fitting process. @@ -53,6 +55,8 @@ def fit( analysis : object, default=None Optional Analysis object to update its categories during fitting. + verbosity : VerbosityEnum, default=VerbosityEnum.FULL + Console output verbosity. """ params = structures.free_parameters + experiments.free_parameters @@ -87,7 +91,7 @@ def objective_function(engine_params: Dict[str, Any]) -> np.ndarray: ) # Perform fitting - self.results = self.minimizer.fit(params, objective_function) + self.results = self.minimizer.fit(params, objective_function, verbosity=verbosity) def _process_fit_results( self, diff --git a/src/easydiffraction/analysis/minimizers/base.py b/src/easydiffraction/analysis/minimizers/base.py index f8499dad..63b6bc27 100644 --- a/src/easydiffraction/analysis/minimizers/base.py +++ b/src/easydiffraction/analysis/minimizers/base.py @@ -13,6 +13,7 @@ from easydiffraction.analysis.fit_helpers.reporting import FitResults from easydiffraction.analysis.fit_helpers.tracking import FitProgressTracker +from easydiffraction.utils.enums import VerbosityEnum class MinimizerBase(ABC): @@ -42,7 +43,11 @@ def __init__( self._fitting_time: Optional[float] = None self.tracker: FitProgressTracker = FitProgressTracker() - def _start_tracking(self, minimizer_name: str) -> None: + def _start_tracking( + self, + minimizer_name: str, + verbosity: VerbosityEnum = VerbosityEnum.FULL, + ) -> None: """ Initialize progress tracking and timer. @@ -50,8 +55,11 @@ def _start_tracking(self, minimizer_name: str) -> None: ---------- minimizer_name : str Human-readable name shown in progress. + verbosity : VerbosityEnum, default=VerbosityEnum.FULL + Console output verbosity. """ self.tracker.reset() + self.tracker._verbosity = verbosity self.tracker.start_tracking(minimizer_name) self.tracker.start_timer() @@ -136,6 +144,7 @@ def fit( self, parameters: List[object], objective_function: Callable[..., object], + verbosity: VerbosityEnum = VerbosityEnum.FULL, ) -> FitResults: """ Run the full minimization workflow. @@ -147,6 +156,8 @@ def fit( objective_function : Callable[..., object] Callable returning residuals for a given set of engine arguments. + verbosity : VerbosityEnum, default=VerbosityEnum.FULL + Console output verbosity. Returns ------- @@ -157,7 +168,7 @@ def fit( if self.method is not None: minimizer_name += f' ({self.method})' - self._start_tracking(minimizer_name) + self._start_tracking(minimizer_name, verbosity=verbosity) solver_args = self._prepare_solver_args(parameters) raw_result = self._run_solver(objective_function, **solver_args) diff --git a/src/easydiffraction/datablocks/experiment/collection.py b/src/easydiffraction/datablocks/experiment/collection.py index 5cdf3f6b..fb30d013 100644 --- a/src/easydiffraction/datablocks/experiment/collection.py +++ b/src/easydiffraction/datablocks/experiment/collection.py @@ -7,6 +7,7 @@ from easydiffraction.core.datablock import DatablockCollection from easydiffraction.datablocks.experiment.item.base import ExperimentBase from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory +from easydiffraction.utils.enums import VerbosityEnum from easydiffraction.utils.logging import console @@ -105,6 +106,7 @@ def add_from_data_path( beam_mode: str | None = None, radiation_probe: str | None = None, scattering_type: str | None = None, + verbosity: str | None = None, ) -> None: """ Add an experiment from a data file path. @@ -123,7 +125,14 @@ def add_from_data_path( Radiation probe (e.g. ``'neutron'``). scattering_type : str | None, default=None Scattering type (e.g. ``'bragg'``). + verbosity : str | None, default=None + Console output verbosity: ``'full'`` for multi-line output, + ``'short'`` for a one-line status message, or ``'silent'`` + for no output. When ``None``, uses ``project.verbosity``. """ + if verbosity is None and self._parent is not None: + verbosity = self._parent.verbosity + verb = VerbosityEnum(verbosity) if verbosity is not None else VerbosityEnum.FULL experiment = ExperimentFactory.from_data_path( name=name, data_path=data_path, @@ -131,6 +140,7 @@ def add_from_data_path( beam_mode=beam_mode, radiation_probe=radiation_probe, scattering_type=scattering_type, + verbosity=verb, ) self.add(experiment) diff --git a/src/easydiffraction/datablocks/experiment/item/base.py b/src/easydiffraction/datablocks/experiment/item/base.py index bc42728f..5c426a94 100644 --- a/src/easydiffraction/datablocks/experiment/item/base.py +++ b/src/easydiffraction/datablocks/experiment/item/base.py @@ -562,7 +562,7 @@ def _get_valid_linked_phases( return valid_linked_phases @abstractmethod - def _load_ascii_data_to_experiment(self, data_path: str) -> None: + def _load_ascii_data_to_experiment(self, data_path: str) -> int: """ Load powder diffraction data from an ASCII file. @@ -571,6 +571,11 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: data_path : str Path to data file with columns compatible with the beam mode (e.g. 2θ/I/σ for CWL, TOF/I/σ for TOF). + + Returns + ------- + int + Number of loaded data points. """ pass diff --git a/src/easydiffraction/datablocks/experiment/item/bragg_pd.py b/src/easydiffraction/datablocks/experiment/item/bragg_pd.py index 01c7aebe..6da9ffe7 100644 --- a/src/easydiffraction/datablocks/experiment/item/bragg_pd.py +++ b/src/easydiffraction/datablocks/experiment/item/bragg_pd.py @@ -58,7 +58,7 @@ def __init__( def _load_ascii_data_to_experiment( self, data_path: str, - ) -> None: + ) -> int: """ Load (x, y, sy) data from an ASCII file into the data category. @@ -68,6 +68,16 @@ def _load_ascii_data_to_experiment( If ``sy`` has values smaller than ``0.0001``, they are replaced with ``1.0``. + + Parameters + ---------- + data_path : str + Path to the ASCII data file. + + Returns + ------- + int + Number of loaded data points. """ data = load_numeric_block(data_path) @@ -76,7 +86,7 @@ def _load_ascii_data_to_experiment( 'Data file must have at least two columns: x and y.', exc_type=ValueError, ) - return + return 0 if data.shape[1] < 3: log.warning('No uncertainty (sy) column provided. Defaulting to sqrt(y).') @@ -100,14 +110,7 @@ def _load_ascii_data_to_experiment( self.data._set_intensity_meas(y) self.data._set_intensity_meas_su(sy) - temperature = '' - if self.diffrn.ambient_temperature.value is not None: - temperature = f' Temperature: {self.diffrn.ambient_temperature.value:.3f} K.' - - console.paragraph('Data loaded successfully') - console.print( - f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}.{temperature}" - ) + return len(x) # ------------------------------------------------------------------ # Instrument (switchable-category pattern) diff --git a/src/easydiffraction/datablocks/experiment/item/bragg_sc.py b/src/easydiffraction/datablocks/experiment/item/bragg_sc.py index 4cc372d4..f77d3af6 100644 --- a/src/easydiffraction/datablocks/experiment/item/bragg_sc.py +++ b/src/easydiffraction/datablocks/experiment/item/bragg_sc.py @@ -15,7 +15,6 @@ from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory from easydiffraction.io.ascii import load_numeric_block -from easydiffraction.utils.logging import console from easydiffraction.utils.logging import log if TYPE_CHECKING: @@ -44,12 +43,22 @@ def __init__( ) -> None: super().__init__(name=name, type=type) - def _load_ascii_data_to_experiment(self, data_path: str) -> None: + def _load_ascii_data_to_experiment(self, data_path: str) -> int: """ Load measured data from an ASCII file into the data category. The file format is space/column separated with 5 columns: ``h k l Iobs sIobs``. + + Parameters + ---------- + data_path : str + Path to the ASCII data file. + + Returns + ------- + int + Number of loaded data points. """ data = load_numeric_block(data_path) @@ -58,7 +67,7 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: 'Data file must have at least 5 columns: h, k, l, Iobs, sIobs.', exc_type=ValueError, ) - return + return 0 # Extract Miller indices h, k, l indices_h: np.ndarray = data[:, 0].astype(int) @@ -74,8 +83,7 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: self.data._set_intensity_meas(integrated_intensities) self.data._set_intensity_meas_su(integrated_intensities_su) - console.paragraph('Data loaded successfully') - console.print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(indices_h)}") + return len(indices_h) @ExperimentFactory.register @@ -100,12 +108,22 @@ def __init__( ) -> None: super().__init__(name=name, type=type) - def _load_ascii_data_to_experiment(self, data_path: str) -> None: + def _load_ascii_data_to_experiment(self, data_path: str) -> int: """ Load measured data from an ASCII file into the data category. The file format is space/column separated with 6 columns: ``h k l Iobs sIobs wavelength``. + + Parameters + ---------- + data_path : str + Path to the ASCII data file. + + Returns + ------- + int + Number of loaded data points. """ try: data = load_numeric_block(data_path) @@ -114,14 +132,14 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: f'Failed to read data from {data_path}: {e}', exc_type=IOError, ) - return + return 0 if data.shape[1] < 6: log.error( 'Data file must have at least 6 columns: h, k, l, Iobs, sIobs, wavelength.', exc_type=ValueError, ) - return + return 0 # Extract Miller indices h, k, l indices_h: np.ndarray = data[:, 0].astype(int) @@ -141,5 +159,4 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: self.data._set_intensity_meas_su(integrated_intensities_su) self.data._set_wavelength(wavelength) - console.paragraph('Data loaded successfully') - console.print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(indices_h)}") + return len(indices_h) diff --git a/src/easydiffraction/datablocks/experiment/item/factory.py b/src/easydiffraction/datablocks/experiment/item/factory.py index c23f60f5..5c0b3094 100644 --- a/src/easydiffraction/datablocks/experiment/item/factory.py +++ b/src/easydiffraction/datablocks/experiment/item/factory.py @@ -23,6 +23,8 @@ from easydiffraction.io.cif.parse import document_from_string from easydiffraction.io.cif.parse import name_from_block from easydiffraction.io.cif.parse import pick_sole_block +from easydiffraction.utils.enums import VerbosityEnum +from easydiffraction.utils.logging import console from easydiffraction.utils.logging import log if TYPE_CHECKING: @@ -229,6 +231,7 @@ def from_data_path( beam_mode: str | None = None, radiation_probe: str | None = None, scattering_type: str | None = None, + verbosity: VerbosityEnum = VerbosityEnum.FULL, ) -> ExperimentBase: """ Create an experiment from a raw data ASCII file. @@ -247,6 +250,8 @@ def from_data_path( Radiation probe (e.g. ``'neutron'``). scattering_type : str | None, default=None Scattering type (e.g. ``'bragg'``). + verbosity : VerbosityEnum, default=VerbosityEnum.FULL + Console output verbosity. Returns ------- @@ -261,5 +266,12 @@ def from_data_path( scattering_type=scattering_type, ) - expt_obj._load_ascii_data_to_experiment(data_path) + num_points = expt_obj._load_ascii_data_to_experiment(data_path) + + if verbosity is VerbosityEnum.FULL: + console.paragraph('Data loaded successfully') + console.print(f"Experiment 🔬 '{name}'. Number of data points: {num_points}.") + elif verbosity is VerbosityEnum.SHORT: + console.print(f"✅ Data loaded: Experiment 🔬 '{name}'. {num_points} points.") + return expt_obj diff --git a/src/easydiffraction/datablocks/experiment/item/total_pd.py b/src/easydiffraction/datablocks/experiment/item/total_pd.py index ade11e6f..2ed856ba 100644 --- a/src/easydiffraction/datablocks/experiment/item/total_pd.py +++ b/src/easydiffraction/datablocks/experiment/item/total_pd.py @@ -14,7 +14,6 @@ from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory -from easydiffraction.utils.logging import console if TYPE_CHECKING: from easydiffraction.datablocks.experiment.categories.experiment_type import ExperimentType @@ -41,11 +40,30 @@ def __init__( ) -> None: super().__init__(name=name, type=type) - def _load_ascii_data_to_experiment(self, data_path: str) -> None: + def _load_ascii_data_to_experiment(self, data_path: str) -> int: """ Load x, y, sy values from an ASCII file into the experiment. The file must be structured as: x y sy + + Parameters + ---------- + data_path : str + Path to the ASCII data file. + + Returns + ------- + int + Number of loaded data points. + + Raises + ------ + ImportError + If the ``diffpy`` package is not installed. + IOError + If the data file cannot be read. + ValueError + If the data file has fewer than two columns. """ try: from diffpy.utils.parsers.loaddata import loadData @@ -71,5 +89,4 @@ def _load_ascii_data_to_experiment(self, data_path: str) -> None: self.data._set_g_r_meas(y) self.data._set_g_r_meas_su(sy) - console.paragraph('Data loaded successfully') - console.print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}") + return len(x) diff --git a/src/easydiffraction/project/project.py b/src/easydiffraction/project/project.py index ace0fc95..61b2cd5d 100644 --- a/src/easydiffraction/project/project.py +++ b/src/easydiffraction/project/project.py @@ -17,6 +17,7 @@ from easydiffraction.io.cif.serialize import project_to_cif from easydiffraction.project.project_info import ProjectInfo from easydiffraction.summary.summary import Summary +from easydiffraction.utils.enums import VerbosityEnum from easydiffraction.utils.logging import console from easydiffraction.utils.logging import log @@ -48,6 +49,7 @@ def __init__( self._summary = Summary(self) self._saved = False self._varname = varname() + self._verbosity: VerbosityEnum = VerbosityEnum.FULL # ------------------------------------------------------------------ # Dunder methods @@ -141,6 +143,31 @@ def as_cif(self) -> str: # Concatenate sections using centralized CIF serializers return project_to_cif(self) + @property + def verbosity(self) -> str: + """ + Project-wide console output verbosity. + + Returns + ------- + str + One of ``'full'``, ``'short'``, or ``'silent'``. + """ + return self._verbosity.value + + @verbosity.setter + def verbosity(self, value: str) -> None: + """ + Set project-wide console output verbosity. + + Parameters + ---------- + value : str + ``'full'`` for multi-line output, ``'short'`` for one-line + status messages, or ``'silent'`` for no output. + """ + self._verbosity = VerbosityEnum(value) + # ------------------------------------------ # Project File I/O # ------------------------------------------ diff --git a/src/easydiffraction/utils/enums.py b/src/easydiffraction/utils/enums.py new file mode 100644 index 00000000..c4aac164 --- /dev/null +++ b/src/easydiffraction/utils/enums.py @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""General-purpose enumerations shared across the library.""" + +from __future__ import annotations + +from enum import Enum + + +class VerbosityEnum(str, Enum): + """ + Console output verbosity level. + + Controls how much information is printed during operations such as + data loading, fitting, and saving. + + Members + ------- + FULL Multi-line output with headers, tables, and details. SHORT + Single-line status messages per action. SILENT No console output. + """ + + FULL = 'full' + SHORT = 'short' + SILENT = 'silent' + + @classmethod + def default(cls) -> VerbosityEnum: + """Return the default verbosity (FULL).""" + return cls.FULL diff --git a/tests/unit/easydiffraction/analysis/test_fitting.py b/tests/unit/easydiffraction/analysis/test_fitting.py index 2f703f77..ed9ea419 100644 --- a/tests/unit/easydiffraction/analysis/test_fitting.py +++ b/tests/unit/easydiffraction/analysis/test_fitting.py @@ -26,7 +26,7 @@ def names(self): class DummyMin: tracker = type('T', (), {'track': staticmethod(lambda a, b: a)})() - def fit(self, params, obj): + def fit(self, params, obj, verbosity=None): return None f = Fitter() @@ -66,7 +66,7 @@ class MockFitResults: class DummyMin: tracker = type('T', (), {'track': staticmethod(lambda a, b: a)})() - def fit(self, params, obj): + def fit(self, params, obj, verbosity=None): return MockFitResults() def _sync_result_to_parameters(self, params, engine_params): diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_base.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_base.py index c2a0ab92..b666374a 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/item/test_base.py +++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_base.py @@ -19,8 +19,8 @@ def test_pd_experiment_peak_profile_type_switch(capsys): from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum class ConcretePd(PdExperimentBase): - def _load_ascii_data_to_experiment(self, data_path: str) -> None: - pass + def _load_ascii_data_to_experiment(self, data_path: str) -> int: + return 0 et = ExperimentType() et._set_sample_form(SampleFormEnum.POWDER.value) diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_sc.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_sc.py index a69dd1bd..014f65fe 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_sc.py +++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_bragg_sc.py @@ -22,9 +22,9 @@ def _mk_type_sc_bragg(): class _ConcreteCwlSc(CwlScExperiment): - def _load_ascii_data_to_experiment(self, data_path: str) -> None: + def _load_ascii_data_to_experiment(self, data_path: str) -> int: # Not used in this test - pass + return 0 def test_init_and_placeholder_no_crash(monkeypatch: pytest.MonkeyPatch): diff --git a/tests/unit/easydiffraction/datablocks/experiment/test_collection.py b/tests/unit/easydiffraction/datablocks/experiment/test_collection.py index bcb9f822..3d5819f6 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/test_collection.py +++ b/tests/unit/easydiffraction/datablocks/experiment/test_collection.py @@ -23,8 +23,8 @@ 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 + def _load_ascii_data_to_experiment(self, data_path: str) -> int: + return 0 exps = Experiments() exps.add(DummyExp('a')) diff --git a/tests/unit/easydiffraction/project/test_project.py b/tests/unit/easydiffraction/project/test_project.py index c6a64055..aedd24a8 100644 --- a/tests/unit/easydiffraction/project/test_project.py +++ b/tests/unit/easydiffraction/project/test_project.py @@ -20,3 +20,32 @@ def test_project_help(capsys): assert 'experiments' in out assert 'analysis' in out assert 'summary' in out + + +def test_project_verbosity_default(): + from easydiffraction.project.project import Project + + p = Project() + assert p.verbosity == 'full' + + +def test_project_verbosity_setter(): + from easydiffraction.project.project import Project + + p = Project() + p.verbosity = 'short' + assert p.verbosity == 'short' + p.verbosity = 'silent' + assert p.verbosity == 'silent' + p.verbosity = 'full' + assert p.verbosity == 'full' + + +def test_project_verbosity_invalid(): + import pytest + + from easydiffraction.project.project import Project + + p = Project() + with pytest.raises(ValueError): + p.verbosity = 'verbose' diff --git a/tests/unit/easydiffraction/utils/test_enums.py b/tests/unit/easydiffraction/utils/test_enums.py new file mode 100644 index 00000000..9d970540 --- /dev/null +++ b/tests/unit/easydiffraction/utils/test_enums.py @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + +from easydiffraction.utils.enums import VerbosityEnum + + +def test_verbosity_enum_members(): + assert VerbosityEnum.FULL == 'full' + assert VerbosityEnum.SHORT == 'short' + assert VerbosityEnum.SILENT == 'silent' + + +def test_verbosity_enum_from_string(): + assert VerbosityEnum('full') is VerbosityEnum.FULL + assert VerbosityEnum('short') is VerbosityEnum.SHORT + assert VerbosityEnum('silent') is VerbosityEnum.SILENT + + +def test_verbosity_enum_invalid_string(): + with pytest.raises(ValueError): + VerbosityEnum('verbose') + + +def test_verbosity_enum_default(): + assert VerbosityEnum.default() is VerbosityEnum.FULL